diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index a01e6568df1e..a3cde6a9d746 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -110,7 +110,8 @@ public enum ErrorMessageID { MissingEmptyArgumentListID, DuplicateNamedTypeParameterID, UndefinedNamedTypeParameterID, - IllegalStartOfStatementID + IllegalStartOfStatementID, + TraitIsExpectedID, ; 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 a64061609d40..70287cb8d791 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1888,4 +1888,32 @@ object messages { } val explanation = "A statement is either an import, a definition or an expression." } + + case class TraitIsExpected(symbol: Symbol)(implicit ctx: Context) extends Message(TraitIsExpectedID) { + val kind = "Syntax" + val msg = hl"$symbol is not a trait" + val explanation = { + val errorCodeExample = + """class A + |class B + | + |val a = new A with B // will fail with a compile error - class B is not a trait""".stripMargin + val codeExample = + """class A + |trait B + | + |val a = new A with B // compiles normally""".stripMargin + + hl"""Only traits can be mixed into classes using a ${"with"} keyword. + |Consider the following example: + | + |$errorCodeExample + | + |The example mentioned above would fail because B is not a trait. + |But if you make B a trait it will be compiled without any errors: + | + |$codeExample + |""" + } + } } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 7e16230fc098..799932f97bc8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -548,7 +548,7 @@ trait Checking { def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp.underlyingClassRef(refinementOK = false) match { case tref: TypeRef => - if (traitReq && !(tref.symbol is Trait)) ctx.error(ex"$tref is not a trait", pos) + if (traitReq && !(tref.symbol is Trait)) ctx.error(TraitIsExpected(tref.symbol), pos) if (stablePrefixReq && ctx.phase <= ctx.refchecksPhase) checkStable(tref.prefix, pos) tp case _ => diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 8238173aea65..e23c5089df88 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -1151,4 +1151,25 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertEquals(IllegalStartOfStatement(isModifier = false), err) assertEquals(IllegalStartOfStatement(isModifier = true), errWithModifier) } + + @Test def traitIsExpected = + checkMessagesAfter("frontend") { + """ + |class A + |class B + | + |object Test { + | def main(args: Array[String]): Unit = { + | val a = new A with B + | } + |} + """.stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + + assertMessageCount(1, messages) + val TraitIsExpected(symbol) :: Nil = messages + assertEquals("class B", symbol.show) + } }