diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index b3ab8764cd1b..d36e306998df 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -104,6 +104,7 @@ public enum ErrorMessageID { EnumCaseDefinitionInNonEnumOwnerID, ExpectedTypeBoundOrEqualsID, ClassAndCompanionNameClashID, + TailrecNotApplicableID, ; public int errorNumber() { diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 6de9a1275a2e..a172806fe4a9 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1777,6 +1777,7 @@ object messages { val explanation = hl"""A class marked with the ${"final"} keyword cannot be extended""" } + case class EnumCaseDefinitionInNonEnumOwner(owner: Symbol)(implicit ctx: Context) extends Message(EnumCaseDefinitionInNonEnumOwnerID) { val kind = "Syntax" @@ -1817,4 +1818,12 @@ object messages { | - ${other.owner} defines ${other}""" } } + + case class TailrecNotApplicable(method: Symbol)(implicit ctx: Context) + extends Message(TailrecNotApplicableID) { + val kind = "Syntax" + val msg = hl"TailRec optimisation not applicable, $method is neither ${"private"} nor ${"final"}." + val explanation = + hl"A method annotated ${"@tailrec"} must be declared ${"private"} or ${"final"} so it can't be overridden." + } } diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index 4f6d5dace29b..24fa51dc0c68 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -12,6 +12,7 @@ import Symbols._ import Types._ import NameKinds.TailLabelName import TreeTransforms.{MiniPhaseTransform, TransformerInfo} +import reporting.diagnostic.messages.TailrecNotApplicable /** * A Tail Rec Transformer @@ -161,7 +162,7 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete }) } case d: DefDef if d.symbol.hasAnnotation(defn.TailrecAnnot) || methodsWithInnerAnnots.contains(d.symbol) => - ctx.error("TailRec optimisation not applicable, method is neither private nor final so can be overridden", sym.pos) + ctx.error(TailrecNotApplicable(sym), sym.pos) d case d if d.symbol.hasAnnotation(defn.TailrecAnnot) || methodsWithInnerAnnots.contains(d.symbol) => ctx.error("TailRec optimisation not applicable, not a method", sym.pos) diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 2f650d91d996..0c3d8377fed8 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -1025,6 +1025,21 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertEquals("object Qux", owner.show) } + @Test def tailrecNotApplicableNeitherPrivateNorFinal = + checkMessagesAfter("tailrec") { + """ + |class Foo { + | @scala.annotation.tailrec + | def foo: Unit = foo + |} + """.stripMargin + }.expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val TailrecNotApplicable(method) :: Nil = messages + assertEquals(method.show, "method foo") + } + @Test def expectedTypeBoundOrEquals = checkMessagesAfter("frontend") { """object typedef {