diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 5e2a815c972b..c1c0d0a39e1d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -370,14 +370,16 @@ trait TypeOps { this: Context => // TODO: Make standalone object. * In fact the current treatment for this sitiuation can so far only be classified as "not obviously wrong", * (maybe it still needs to be revised). */ - def boundsViolations(args: List[Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type, app: Type)(implicit ctx: Context): List[BoundsViolation] = { + def boundsViolations(args: List[Tree], boundss: List[TypeBounds], + instantiate: (Type, List[Type]) => Type, app: Type)( + implicit ctx: Context): List[BoundsViolation] = { val argTypes = args.tpes /** Replace all wildcards in `tps` with `#` where `` is the * type parameter corresponding to the wildcard. */ def skolemizeWildcardArgs(tps: List[Type], app: Type) = app match { - case AppliedType(tycon, args) if tycon.typeSymbol.isClass && !scala2CompatMode => + case AppliedType(tycon: TypeRef, args) if tycon.typeSymbol.isClass && !scala2CompatMode => tps.zipWithConserve(tycon.typeSymbol.typeParams) { (tp, tparam) => tp match { case _: TypeBounds => app.select(tparam) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.scala index d079e0ce14c1..43583e4730b0 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.scala @@ -51,7 +51,7 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] { ExpectedTokenButFoundID, MixedLeftAndRightAssociativeOpsID, CantInstantiateAbstractClassOrTraitID, - DUMMY_AVAILABLE_1, + UnreducibleApplicationID, OverloadedOrRecursiveMethodNeedsResultTypeID, RecursiveValueNeedsResultTypeID, CyclicReferenceInvolvingID, diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index b8eb546c1d24..b3b9a12067ad 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -70,6 +70,11 @@ abstract class Message(val errorId: ErrorMessageID) { self => val kind = self.kind val explanation = self.explanation } + + def appendExplanation(suffix: => String): Message = new Message(errorId): + val msg = self.msg + val kind = self.kind + val explanation = self.explanation ++ suffix } /** An extended message keeps the contained message from being evaluated, while diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 648cf17c654b..1051e213bdaf 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1235,6 +1235,14 @@ object messages { |""".stripMargin } + case class UnreducibleApplication(tycon: Type)(using Context) extends Message(UnreducibleApplicationID): + val kind = "Type" + val msg = em"unreducible application of higher-kinded type $tycon to wildcard arguments" + val explanation = + em"""|An abstract type constructor cannot be applied to wildcard arguments. + |Such applications are equivalent to existential types, which are not + |supported in Scala 3.""" + case class OverloadedOrRecursiveMethodNeedsResultType(cycleSym: Symbol)(implicit ctx: Context) extends Message(OverloadedOrRecursiveMethodNeedsResultTypeID) { val kind: String = "Cyclic" @@ -1464,13 +1472,13 @@ object messages { val parameters = if (numParams == 1) "parameter" else "parameters" val msg: String = em"Missing type $parameters for $tpe" val kind: String = "Type Mismatch" - val explanation: String = em"A fully applied type is expected but $tpe takes $numParams $parameters." + val explanation: String = em"A fully applied type is expected but $tpe takes $numParams $parameters" } case class DoesNotConformToBound(tpe: Type, which: String, bound: Type)( err: Errors)(implicit ctx: Context) extends Message(DoesNotConformToBoundID) { - val msg: String = em"Type argument ${tpe} does not conform to $which bound $bound ${err.whyNoMatchStr(tpe, bound)}" + val msg: String = em"Type argument ${tpe} does not conform to $which bound $bound${err.whyNoMatchStr(tpe, bound)}" val kind: String = "Type Mismatch" val explanation: String = "" } diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index aeee4a35fc7e..994527cc9c58 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -142,6 +142,24 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase tree } + private def processValOrDefDef(tree: Tree)(using Context): tree.type = + tree match + case tree: ValOrDefDef if !tree.symbol.is(Synthetic) => + checkInferredWellFormed(tree.tpt) + case _ => + processMemberDef(tree) + + private def checkInferredWellFormed(tree: Tree)(using ctx: Context): Unit = tree match + case tree: TypeTree + if tree.span.isZeroExtent + // don't check TypeTrees with non-zero extent; + // these are derived from explicit types + && !ctx.reporter.errorsReported + // don't check if errors were already reported; this avoids follow-on errors + // for inferred types if explicit types are already ill-formed + => Checking.checkAppliedTypesIn(tree) + case _ => + private def transformSelect(tree: Select, targs: List[Tree])(implicit ctx: Context): Tree = { val qual = tree.qualifier qual.symbol.moduleClass.denot match { @@ -226,17 +244,26 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase else dropInlines.transform(arg))) else tree - methPart(app) match { + def app1 = + // reverse order of transforming args and fun. This way, we get a chance to see other + // well-formedness errors before reporting errors in possible inferred type args of fun. + val args1 = transform(app.args) + cpy.Apply(app)(transform(app.fun), args1) + methPart(app) match case Select(nu: New, nme.CONSTRUCTOR) if isCheckable(nu) => // need to check instantiability here, because the type of the New itself // might be a type constructor. Checking.checkInstantiable(tree.tpe, nu.posd) - withNoCheckNews(nu :: Nil)(super.transform(app)) + withNoCheckNews(nu :: Nil)(app1) case _ => - super.transform(app) - } + app1 + case UnApply(fun, implicits, patterns) => + // Reverse transform order for the same reason as in `app1` above. + val patterns1 = transform(patterns) + cpy.UnApply(tree)(transform(fun), transform(implicits), patterns1) case tree: TypeApply => val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) + args.foreach(checkInferredWellFormed) if (fn.symbol != defn.ChildAnnot.primaryConstructor) // Make an exception for ChildAnnot, which should really have AnyKind bounds Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType]) @@ -262,11 +289,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } case tree: ValDef => val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) - processMemberDef(super.transform(tree1)) + processValOrDefDef(super.transform(tree1)) case tree: DefDef => annotateContextResults(tree) val tree1 = cpy.DefDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) - processMemberDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef])) + processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef])) case tree: TypeDef => val sym = tree.symbol if (sym.isClass) @@ -298,7 +325,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase else if (tree.tpt.symbol == defn.orType) () // nothing to do else - Checking.checkAppliedType(tree, boundsCheck = !ctx.mode.is(Mode.Pattern)) + Checking.checkAppliedType(tree) super.transform(tree) case SingletonTypeTree(ref) => Checking.checkRealizable(ref.tpe, ref.posd) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 165949fc5981..f332679b7f8e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -42,21 +42,37 @@ import scala.internal.Chars.isOperatorPart object Checking { import tpd._ + /** Add further information for error messages involving applied types if the + * type is inferred: + * 1. the full inferred type is a TypeTree node + * 2. the applied type causing the error, if different from (1) + */ + private def showInferred(msg: Message, app: Type, tpt: Tree)(using ctx: Context): Message = + if tpt.isInstanceOf[TypeTree] then + def subPart = if app eq tpt.tpe then "" else i" subpart $app of" + msg.append(i" in$subPart inferred type ${tpt}") + .appendExplanation("\n\nTo fix the problem, provide an explicit type.") + else msg + /** A general checkBounds method that can be used for TypeApply nodes as * well as for AppliedTypeTree nodes. Also checks that type arguments to * *-type parameters are fully applied. - * See TypeOps.boundsViolations for an explanation of the parameters. + * @param tpt If bounds are checked for an AppliedType, the type tree representing + * or (in case it is inferred) containing the type. + * See TypeOps.boundsViolations for an explanation of the first four parameters. */ - def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type, app: Type = NoType)(implicit ctx: Context): Unit = { + def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], + instantiate: (Type, List[Type]) => Type, app: Type = NoType, tpt: Tree = EmptyTree)(implicit ctx: Context): Unit = args.lazyZip(boundss).foreach { (arg, bound) => - if (!bound.isLambdaSub && !arg.tpe.hasSimpleKind) - errorTree(arg, MissingTypeParameterInTypeApp(arg.tpe)) + if !bound.isLambdaSub && !arg.tpe.hasSimpleKind then + errorTree(arg, + showInferred(MissingTypeParameterInTypeApp(arg.tpe), app, tpt)) } - for ((arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate, app)) + for (arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate, app) do ctx.error( - DoesNotConformToBound(arg.tpe, which, bound)(err), + showInferred(DoesNotConformToBound(arg.tpe, which, bound)(err), + app, tpt), arg.sourcePos.focus) - } /** Check that type arguments `args` conform to corresponding bounds in `tl` * Note: This does not check the bounds of AppliedTypeTrees. These @@ -71,17 +87,15 @@ object Checking { * check that it or one of its supertypes can be reduced to a normal application. * Unreducible applications correspond to general existentials, and we * cannot handle those. + * @param tree The applied type tree to check + * @param tpt If `tree` is synthesized from a type in a TypeTree, + * the original TypeTree, or EmptyTree otherwise. */ - def checkAppliedType(tree: AppliedTypeTree, boundsCheck: Boolean)(implicit ctx: Context): Unit = { + def checkAppliedType(tree: AppliedTypeTree, tpt: Tree = EmptyTree)(using ctx: Context): Unit = { val AppliedTypeTree(tycon, args) = tree // If `args` is a list of named arguments, return corresponding type parameters, // otherwise return type parameters unchanged val tparams = tycon.tpe.typeParams - def argNamed(tparam: ParamInfo) = args.find { - case NamedArg(name, _) => name == tparam.paramName - case _ => false - }.getOrElse(TypeTree(tparam.paramRef)) - val orderedArgs = if (hasNamedArg(args)) tparams.map(argNamed) else args val bounds = tparams.map(_.paramInfoAsSeenFrom(tree.tpe).bounds) def instantiate(bound: Type, args: List[Type]) = tparams match @@ -89,13 +103,17 @@ object Checking { HKTypeLambda.fromParams(tparams, bound).appliedTo(args) case _ => bound // paramInfoAsSeenFrom already took care of instantiation in this case - if (boundsCheck) checkBounds(orderedArgs, bounds, instantiate, tree.tpe) + if !ctx.mode.is(Mode.Pattern) // no bounds checking in patterns + && tycon.symbol != defn.TypeBoxClass // TypeBox types are generated for capture + // conversion, may contain AnyKind as arguments + then + checkBounds(args, bounds, instantiate, tree.tpe, tpt) def checkWildcardApply(tp: Type): Unit = tp match { case tp @ AppliedType(tycon, _) => if (tycon.isLambdaSub && tp.hasWildcardArg) ctx.errorOrMigrationWarning( - ex"unreducible application of higher-kinded type $tycon to wildcard arguments", + showInferred(UnreducibleApplication(tycon), tp, tpt), tree.sourcePos) case _ => } @@ -104,6 +122,20 @@ object Checking { checkValidIfApply(ctx.addMode(Mode.AllowLambdaWildcardApply)) } + /** Check all applied type trees in inferred type `tpt` for well-formedness */ + def checkAppliedTypesIn(tpt: TypeTree)(implicit ctx: Context): Unit = + val checker = new TypeTraverser: + def traverse(tp: Type) = + tp match + case AppliedType(tycon, argTypes) => + checkAppliedType( + untpd.AppliedTypeTree(TypeTree(tycon), argTypes.map(TypeTree)) + .withType(tp).withSpan(tpt.span.toSynthetic), + tpt) + case _ => + traverseChildren(tp) + checker.traverse(tpt.tpe) + def checkNoWildcard(tree: Tree)(implicit ctx: Context): Tree = tree.tpe match { case tpe: TypeBounds => errorTree(tree, "no wildcard type allowed here") case _ => tree diff --git a/compiler/src/dotty/tools/dotc/util/Spans.scala b/compiler/src/dotty/tools/dotc/util/Spans.scala index b2a4c7c0514f..b8d97a193e3d 100644 --- a/compiler/src/dotty/tools/dotc/util/Spans.scala +++ b/compiler/src/dotty/tools/dotc/util/Spans.scala @@ -95,7 +95,7 @@ object Spans { def isSourceDerived: Boolean = !isSynthetic /** Is this a zero-extent span? */ - def isZeroExtent: Boolean = start == end + def isZeroExtent: Boolean = exists && start == end /** A span where all components are shifted by a given `offset` * relative to this span. diff --git a/tests/neg/i4382.check b/tests/neg/i4382.check index 24a47d0c6073..3d21702ffe0f 100644 --- a/tests/neg/i4382.check +++ b/tests/neg/i4382.check @@ -1,16 +1,24 @@ --- Error: tests/neg/i4382.scala:3:10 ----------------------------------------------------------------------------------- +-- [E043] Type Error: tests/neg/i4382.scala:3:10 ----------------------------------------------------------------------- 3 | def v1: Id[_] = ??? // error | ^^^^^ | unreducible application of higher-kinded type App.Id to wildcard arguments --- Error: tests/neg/i4382.scala:6:10 ----------------------------------------------------------------------------------- + +longer explanation available when compiling with `-explain` +-- [E043] Type Error: tests/neg/i4382.scala:6:10 ----------------------------------------------------------------------- 6 | def v2: HkL[_] = ??? // error | ^^^^^^ | unreducible application of higher-kinded type App.HkL to wildcard arguments --- Error: tests/neg/i4382.scala:9:10 ----------------------------------------------------------------------------------- + +longer explanation available when compiling with `-explain` +-- [E043] Type Error: tests/neg/i4382.scala:9:10 ----------------------------------------------------------------------- 9 | def v3: HkU[_] = ??? // error | ^^^^^^ | unreducible application of higher-kinded type App.HkU to wildcard arguments --- Error: tests/neg/i4382.scala:12:10 ---------------------------------------------------------------------------------- + +longer explanation available when compiling with `-explain` +-- [E043] Type Error: tests/neg/i4382.scala:12:10 ---------------------------------------------------------------------- 12 | def v4: HkAbs[_] = ??? // error | ^^^^^^^^ | unreducible application of higher-kinded type App.HkAbs to wildcard arguments + +longer explanation available when compiling with `-explain` diff --git a/tests/neg/i5302.scala b/tests/neg/i5302.scala new file mode 100644 index 000000000000..43485ff7e61b --- /dev/null +++ b/tests/neg/i5302.scala @@ -0,0 +1,3 @@ +type L[X] +def foo = { class A; null.asInstanceOf[L[A]] } // error +def bar(x: L[_]) = x // error \ No newline at end of file diff --git a/tests/neg/i6205.check b/tests/neg/i6205.check new file mode 100644 index 000000000000..caca487ee2db --- /dev/null +++ b/tests/neg/i6205.check @@ -0,0 +1,6 @@ +-- [E057] Type Mismatch Error: tests/neg/i6205.scala:4:9 --------------------------------------------------------------- +4 | def foo = // error + | ^ + | Type argument Nothing does not conform to lower bound Null in inferred type Contra[Nothing] + +longer explanation available when compiling with `-explain` diff --git a/tests/neg/i6205.scala b/tests/neg/i6205.scala new file mode 100644 index 000000000000..32537e5b2fbb --- /dev/null +++ b/tests/neg/i6205.scala @@ -0,0 +1,8 @@ +class Contra[-T >: Null] + +object Test: + def foo = // error + class A + new Contra[A] + + val x = foo