diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index 51ab7b82ebeb..6ea61e2bf378 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -63,6 +63,20 @@ public enum ErrorMessageID { TypeDoesNotTakeParametersID, ParameterizedTypeLacksArgumentsID, VarValParametersMayNotBeCallByNameID, + MissingTypeParameterForID, + DoesNotConformToBoundID, + DoesNotConformToSelfTypeID, + DoesNotConformToSelfTypeCantBeInstantiatedID, + AbstractMemberMayNotHaveModifierID, + TopLevelCantBeImplicitID, + TypesAndTraitsCantBeImplicitID, + OnlyClassesCanBeAbstractID, + AbstractOverrideOnlyInTraitsID, + TraitsMayNotBeFinalID, + NativeMembersMayNotHaveImplementationID, + OnlyClassesCanHaveDeclaredButUndefinedMembersID, + CannotExtendAnyValID, + CannotHaveSameNameAsID, ; 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 966ffb70afc1..0c5dfdb3d83f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -20,6 +20,7 @@ import printing.Formatting import ErrorMessageID._ import Denotations.SingleDenotation import dotty.tools.dotc.ast.Trees +import dotty.tools.dotc.core.Flags.{FlagSet, Mutable} import dotty.tools.dotc.core.SymDenotations.SymDenotation object messages { @@ -1351,4 +1352,133 @@ object messages { | ${"}"} |""" } + + case class MissingTypeParameterFor(tpe: Type)(implicit ctx: Context) + extends Message(MissingTypeParameterForID) { + val msg = hl"missing type parameter for ${tpe}" + val kind = "Syntax" + val explanation = "" + } + + case class DoesNotConformToBound(tpe: Type, which: String, bound: Type)( + err: typer.ErrorReporting.Errors)(implicit ctx: Context) + extends Message(DoesNotConformToBoundID) { + val msg = hl"Type argument ${tpe} does not conform to $which bound $bound ${err.whyNoMatchStr(tpe, bound)}" + val kind = "Type Mismatch" + val explanation = "" + } + + case class DoesNotConformToSelfType(category: String, selfType: Type, cls: Symbol, + otherSelf: Type, relation: String, other: Symbol)( + implicit ctx: Context) + extends Message(DoesNotConformToSelfTypeID) { + val msg = hl"""$category: self type $selfType of $cls does not conform to self type $otherSelf + |of $relation $other""" + val kind = "Type Mismatch" + val explanation = + hl"""You mixed in $other which requires self type $otherSelf, but $cls has self type + |$selfType and does not inherit from $otherSelf. + | + |Note: Self types are indicated with the notation + | ${s"class "}$other ${"{ this: "}$otherSelf${" => "} + """ + } + + case class DoesNotConformToSelfTypeCantBeInstantiated(tp: Type, selfType: Type)( + implicit ctx: Context) + extends Message(DoesNotConformToSelfTypeCantBeInstantiatedID) { + val msg = hl"""$tp does not conform to its self type $selfType; cannot be instantiated""" + val kind = "Type Mismatch" + val explanation = + hl"""To create an instance of $tp it needs to inherit $selfType in some way. + | + |Note: Self types are indicated with the notation + | ${s"class "}$tp ${"{ this: "}$selfType${" => "} + |""" + } + + case class AbstractMemberMayNotHaveModifier(sym: Symbol, flag: FlagSet)( + implicit ctx: Context) + extends Message(AbstractMemberMayNotHaveModifierID) { + val msg = hl"""${"abstract"} $sym may not have `$flag' modifier""" + val kind = "Syntax" + val explanation = "" + } + + case class TopLevelCantBeImplicit(sym: Symbol)( + implicit ctx: Context) + extends Message(TopLevelCantBeImplicitID) { + val msg = hl"""${"implicit"} modifier cannot be used for top-level definitions""" + val kind = "Syntax" + val explanation = "" + } + + case class TypesAndTraitsCantBeImplicit(sym: Symbol)( + implicit ctx: Context) + extends Message(TypesAndTraitsCantBeImplicitID) { + val msg = hl"""${"implicit"} modifier cannot be used for types or traits""" + val kind = "Syntax" + val explanation = "" + } + + case class OnlyClassesCanBeAbstract(sym: Symbol)( + implicit ctx: Context) + extends Message(OnlyClassesCanBeAbstractID) { + val msg = hl"""${"abstract"} modifier can be used only for classes; it should be omitted for abstract members""" + val kind = "Syntax" + val explanation = "" + } + + case class AbstractOverrideOnlyInTraits(sym: Symbol)( + implicit ctx: Context) + extends Message(AbstractOverrideOnlyInTraitsID) { + val msg = hl"""${"abstract override"} modifier only allowed for members of traits""" + val kind = "Syntax" + val explanation = "" + } + + case class TraitsMayNotBeFinal(sym: Symbol)( + implicit ctx: Context) + extends Message(TraitsMayNotBeFinalID) { + val msg = hl"""$sym may not be ${"final"}""" + val kind = "Syntax" + val explanation = + "A trait can never be final since it is abstract and must be extended to be useful." + } + + case class NativeMembersMayNotHaveImplementation(sym: Symbol)( + implicit ctx: Context) + extends Message(NativeMembersMayNotHaveImplementationID) { + val msg = hl"""${"@native"} members may not have an implementation""" + val kind = "Syntax" + val explanation = "" + } + + case class OnlyClassesCanHaveDeclaredButUndefinedMembers(sym: Symbol)( + implicit ctx: Context) + extends Message(OnlyClassesCanHaveDeclaredButUndefinedMembersID) { + + private val varNote = + if (sym.is(Mutable)) "Note that variables need to be initialized to be defined." + else "" + val msg = hl"""only classes can have declared but undefined members""" + val kind = "Syntax" + val explanation = s"$varNote" + } + + case class CannotExtendAnyVal(sym: Symbol)(implicit ctx: Context) + extends Message(CannotExtendAnyValID) { + val msg = hl"""$sym cannot extend ${"AnyVal"}""" + val kind = "Syntax" + val explanation = "" + } + + case class CannotHaveSameNameAs(sym: Symbol, cls: Symbol)(implicit ctx: Context) + extends Message(CannotHaveSameNameAsID) { + val msg = hl"""$sym cannot have the same name as ${cls.showLocated} -- class definitions cannot be overridden""" + val kind = "Syntax" + val explanation = "" + } + + } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 527b0bd1f33e..f08f91041a75 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -18,18 +18,21 @@ import Constants._ import Scopes._ import CheckRealizable._ import ErrorReporting.errorTree + import annotation.unchecked import util.Positions._ -import util.{Stats, SimpleMap} +import util.{SimpleMap, Stats} import util.common._ import transform.SymUtils._ import Decorators._ import Uniques._ import ErrorReporting.{err, errorType} import config.Printers.typr + import collection.mutable import SymDenotations.NoCompleter -import dotty.tools.dotc.reporting.diagnostic.messages.CantInstantiateAbstractClassOrTrait +import dotty.tools.dotc.reporting.diagnostic.{ErrorMessageID, Message} +import dotty.tools.dotc.reporting.diagnostic.messages._ import dotty.tools.dotc.transform.ValueClasses._ object Checking { @@ -42,11 +45,12 @@ object Checking { def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): Unit = { (args, boundss).zipped.foreach { (arg, bound) => if (!bound.isHK && arg.tpe.isHK) + // see MissingTypeParameterFor ctx.error(ex"missing type parameter(s) for $arg", arg.pos) } for ((arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate)) ctx.error( - ex"Type argument ${arg.tpe} does not conform to $which bound $bound ${err.whyNoMatchStr(arg.tpe, bound)}", + DoesNotConformToBound(arg.tpe, which, bound)(err), arg.pos.focus) } @@ -111,7 +115,7 @@ object Checking { val stp = SkolemType(tp) val selfType = tref.givenSelfType.asSeenFrom(stp, cls) if (selfType.exists && !(stp <:< selfType)) - ctx.error(ex"$tp does not conform to its self type $selfType; cannot be instantiated") + ctx.error(DoesNotConformToSelfTypeCantBeInstantiated(tp, selfType), pos) } case _ => } @@ -119,10 +123,8 @@ object Checking { /** Check that type `tp` is realizable. */ def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = { val rstatus = realizability(tp) - if (rstatus ne Realizable) { - def msg = em"$tp is not a legal path\n since it${rstatus.msg}" - if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos) - } + if (rstatus ne Realizable) + ctx.errorOrMigrationWarning(em"$tp is not a legal path\n since it${rstatus.msg}", pos) } /** A type map which checks that the only cycles in a type are F-bounds @@ -304,50 +306,46 @@ object Checking { /** Check that symbol's definition is well-formed. */ def checkWellFormed(sym: Symbol)(implicit ctx: Context): Unit = { - //println(i"check wf $sym with flags ${sym.flags}") - def fail(msg: String) = ctx.error(msg, sym.pos) - def varNote = - if (sym.is(Mutable)) "\n(Note that variables need to be initialized to be defined)" - else "" + def fail(msg: Message) = ctx.error(msg, sym.pos) def checkWithDeferred(flag: FlagSet) = if (sym.is(flag)) - fail(i"abstract member may not have `$flag' modifier") + fail(AbstractMemberMayNotHaveModifier(sym, flag)) def checkNoConflict(flag1: FlagSet, flag2: FlagSet) = if (sym.is(allOf(flag1, flag2))) fail(i"illegal combination of modifiers: $flag1 and $flag2 for: $sym") if (sym.is(ImplicitCommon)) { if (sym.owner.is(Package)) - fail(i"`implicit' modifier cannot be used for top-level definitions") + fail(TopLevelCantBeImplicit(sym)) if (sym.isType) - fail(i"`implicit' modifier cannot be used for types or traits") + fail(TypesAndTraitsCantBeImplicit(sym)) } if (!sym.isClass && sym.is(Abstract)) - fail(i"`abstract' modifier can be used only for classes; it should be omitted for abstract members") + fail(OnlyClassesCanBeAbstract(sym)) if (sym.is(AbsOverride) && !sym.owner.is(Trait)) - fail(i"`abstract override' modifier only allowed for members of traits") + fail(AbstractOverrideOnlyInTraits(sym)) if (sym.is(Trait) && sym.is(Final)) - fail(i"$sym may not be `final'") + fail(TraitsMayNotBeFinal(sym)) if (sym.hasAnnotation(defn.NativeAnnot)) { if (!sym.is(Deferred)) - fail(i"`@native' members may not have implementation") + fail(NativeMembersMayNotHaveImplementation(sym)) } else if (sym.is(Deferred, butNot = Param) && !sym.isType && !sym.isSelfSym) { if (!sym.owner.isClass || sym.owner.is(Module) || sym.owner.isAnonymousClass) - fail(i"only classes can have declared but undefined members$varNote") + fail(OnlyClassesCanHaveDeclaredButUndefinedMembers(sym)) checkWithDeferred(Private) checkWithDeferred(Final) checkWithDeferred(Inline) } if (sym.isValueClass && sym.is(Trait) && !sym.isRefinementClass) - fail(i"$sym cannot extend AnyVal") + fail(CannotExtendAnyVal(sym)) checkNoConflict(Final, Sealed) checkNoConflict(Private, Protected) checkNoConflict(Abstract, Override) if (sym.isType && !sym.is(Deferred)) for (cls <- sym.allOverriddenSymbols.filter(_.isClass)) { - fail(i"$sym cannot have the same name as ${cls.showLocated} -- class definitions cannot be overridden") + fail(CannotHaveSameNameAs(sym, cls)) sym.setFlag(Private) // break the overriding relationship by making sym Private } } @@ -610,7 +608,7 @@ trait Checking { if (tpt.tpe.isHK && !ctx.compilationUnit.isJava) { // be more lenient with missing type params in Java, // needed to make pos/java-interop/t1196 work. - errorTree(tpt, ex"missing type parameter for ${tpt.tpe}") + errorTree(tpt, MissingTypeParameterFor(tpt.tpe)) } else tpt diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 86da23b1fcd1..78a9eaa2ccbd 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -97,7 +97,8 @@ object RefChecks { def checkSelfConforms(other: TypeRef, category: String, relation: String) = { val otherSelf = other.givenSelfType.asSeenFrom(cls.thisType, other.classSymbol) if (otherSelf.exists && !(cinfo.selfType <:< otherSelf)) - ctx.error(ex"$category: self type ${cinfo.selfType} of $cls does not conform to self type $otherSelf of $relation ${other.classSymbol}", cls.pos) + ctx.error(DoesNotConformToSelfType(category, cinfo.selfType, cls, otherSelf, relation, other.classSymbol), + cls.pos) } for (parent <- cinfo.classParents) checkSelfConforms(parent, "illegal inheritance", "parent") diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 4220458c1d7c..1201e00ca9ff 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -484,4 +484,210 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertEquals("noNoNo", name.show) } + @Test def missingTypeParameter = + checkMessagesAfter("frontend") { + """object Scope { + | val value: List = null + |}""".stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val MissingTypeParameterFor(tpe) :: Nil = messages + assertEquals("List", tpe.show) + } + + @Test def doesNotConformToBound = + checkMessagesAfter("refchecks") { + """class WithParam[A <: List[Int]] + |object Scope { + | val value: WithParam[Int] = null + |}""".stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val DoesNotConformToBound(tpe, which, bound) :: Nil = messages + assertEquals("Int", tpe.show) + assertEquals("upper", which) + assertEquals("scala.collection.immutable.List[Int]", bound.show) + } + + @Test def doesNotConformToSelfType = + checkMessagesAfter("refchecks") { + """class Base + |trait BlendItIn { + | this: Base => + |} + |class Blended extends BlendItIn + |""".stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val DoesNotConformToSelfType(category, selfType, cls, otherSelf, relation, other) :: Nil = messages + assertEquals("illegal inheritance", category) + assertEquals("Blended", selfType.show) + assertEquals("class Blended", cls.show) + assertEquals("Base", otherSelf.show) + assertEquals("parent", relation) + assertEquals("trait BlendItIn", other.show) + } + + @Test def doesNotConformToSelfTypeCantBeInstantiated = + checkMessagesAfter("refchecks") { + """class Base + |class RequiresBase { self: Base => } + |object Scope { + | new RequiresBase + |} + |""".stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val DoesNotConformToSelfTypeCantBeInstantiated(tpe, selfType) :: Nil = messages + assertEquals("RequiresBase", tpe.show) + assertEquals("Base", selfType.show) + } + + @Test def abstractValueMayNotHaveFinalModifier = + checkMessagesAfter("frontend") { + """abstract class Foo { + | final val s: String + |} + |""".stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val AbstractMemberMayNotHaveModifier(symbol, flags) :: Nil = messages + assertEquals("value s", symbol.show) + assertEquals("final", flags.toString) + } + + @Test def topLevelCantBeImplicit = + checkMessagesAfter("frontend") { + """package Foo { + | implicit object S + |} + |""".stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val TopLevelCantBeImplicit(symbol) :: Nil = messages + assertEquals("object S", symbol.show) + } + + @Test def typesAndTraitsCantBeImplicit = + checkMessagesAfter("frontend") { + """class Foo { + | implicit trait S + |} + |""".stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val TypesAndTraitsCantBeImplicit(symbol) :: Nil = messages + assertEquals("trait S", symbol.show) + } + + @Test def onlyClassesCanBeAbstract = + checkMessagesAfter("frontend") { + """class Foo { + | abstract val s: String + |} + |""".stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val OnlyClassesCanBeAbstract(symbol) :: Nil = messages + assertEquals("value s", symbol.show) + } + + @Test def abstractOverrideOnlyInTraits = + checkMessagesAfter("frontend") { + """class Foo { + | abstract override val s: String = "" + |} + |""".stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val AbstractOverrideOnlyInTraits(symbol) :: Nil = messages + assertEquals("value s", symbol.show) + } + + @Test def traitMayNotBeFinal = + checkMessagesAfter("frontend") { + """final trait Foo""" + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val TraitsMayNotBeFinal(symbol) :: Nil = messages + assertEquals("trait Foo", symbol.show) + } + + @Test def nativeMemberMayNotHaveImplementation = + checkMessagesAfter("frontend") { + """trait Foo { + | @native def foo() = 5 + |} + """.stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val NativeMembersMayNotHaveImplementation(symbol) :: Nil = messages + assertEquals("method foo", symbol.show) + } + + @Test def onlyClassesCanHaveDeclaredButUndefinedMembers = + checkMessagesAfter("frontend") { + """object Foo { + | def foo(): Int + |} + |""".stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val OnlyClassesCanHaveDeclaredButUndefinedMembers(symbol) :: Nil = messages + assertEquals("method foo", symbol.show) + } + + @Test def cannotExtendAnyval = + checkMessagesAfter("frontend") { + """trait Foo extends AnyVal""" + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val CannotExtendAnyVal(symbol) :: Nil = messages + assertEquals("trait Foo", symbol.show) + } + + @Test def cannotHaveSameNameAs = + checkMessagesAfter("refchecks") { + """trait Foo { + | class A + |} + |class B extends Foo { + | class A + |}""".stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val CannotHaveSameNameAs(symbol, cls) :: Nil = messages + assertEquals("class A", symbol.show) + assertEquals("class A", cls.show) + } + + } diff --git a/tests/neg/instantiateAbstract.scala b/tests/neg/instantiateAbstract.scala index a2ff38ef43da..614fabda0772 100644 --- a/tests/neg/instantiateAbstract.scala +++ b/tests/neg/instantiateAbstract.scala @@ -24,7 +24,7 @@ object Test { new TT // error - new A + new A // error "A does not conform to its self type B; cannot be instantiated" // the following are OK in Typer but would be caught later in RefChecks @@ -37,4 +37,3 @@ object Test { object OO extends AA } -// nopos-error: "A does not conform to its self type B; cannot be instantiated"