diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 3dd6e784b4b4..c72bb2d6674b 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2156,7 +2156,7 @@ object Parsers { case SUPERTYPE | SUBTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF => TypeDef(name, lambdaAbstract(tparams, typeBounds())).withMods(mods).setComment(in.getDocComment(start)) case _ => - syntaxErrorOrIncomplete("`=', `>:', or `<:' expected") + syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) EmptyTree } } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index ac8a45405eac..9ae36835c9d9 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -102,6 +102,7 @@ public enum ErrorMessageID { UncheckedTypePatternID, ExtendFinalClassID, EnumCaseDefinitionInNonEnumOwnerID, + ExpectedTypeBoundOrEqualsID, ; 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 753bb76a551b..92369911810e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1770,7 +1770,7 @@ object messages { |""" } - case class ExtendFinalClass(clazz:Symbol, finalClazz: Symbol)(implicit ctx: Context) + case class ExtendFinalClass(clazz:Symbol, finalClazz: Symbol)(implicit ctx: Context) extends Message(ExtendFinalClassID) { val kind = "Syntax" val msg = hl"$clazz cannot extend ${"final"} $finalClazz" @@ -1786,4 +1786,22 @@ object messages { |If you want to create an ${"enum"} case, make sure the corresponding ${"enum class"} exists |and has the ${"enum"} keyword.""" } + + case class ExpectedTypeBoundOrEquals(found: Token)(implicit ctx: Context) + extends Message(ExpectedTypeBoundOrEqualsID) { + val kind = "Syntax" + val msg = hl"${"="}, ${">:"}, or ${"<:"} expected, but ${Tokens.showToken(found)} found" + + val explanation = + hl"""Type parameters and abstract types may be constrained by a type bound. + |Such type bounds limit the concrete values of the type variables and possibly + |reveal more information about the members of such types. + | + |A lower type bound ${"B >: A"} expresses that the type variable ${"B"} + |refers to a supertype of type ${"A"}. + | + |An upper type bound ${"T <: A"} declares that type variable ${"T"} + |refers to a subtype of type ${"A"}. + |""" + } } diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index b9846c665dba..26f26cb70ac2 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -1024,4 +1024,18 @@ class ErrorMessagesTests extends ErrorMessagesTest { val EnumCaseDefinitionInNonEnumOwner(owner) :: Nil = messages assertEquals("object Qux", owner.show) } + + @Test def expectedTypeBoundOrEquals = + checkMessagesAfter("frontend") { + """object typedef { + | type asd > Seq + |} + """.stripMargin + }.expect { (ictx, messages) => + implicit val ctx: Context = ictx + + assertMessageCount(1, messages) + val ExpectedTypeBoundOrEquals(found) :: Nil = messages + assertEquals(Tokens.IDENTIFIER, found) + } }