Skip to content

Commit c77a494

Browse files
committed
#Fix 5302: Check that applied types in inferred types are well-formed
1 parent 9b73f77 commit c77a494

File tree

7 files changed

+67
-15
lines changed

7 files changed

+67
-15
lines changed

compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
5151
ExpectedTokenButFoundID,
5252
MixedLeftAndRightAssociativeOpsID,
5353
CantInstantiateAbstractClassOrTraitID,
54-
DUMMY_AVAILABLE_1,
54+
UnreducibleApplicationID,
5555
OverloadedOrRecursiveMethodNeedsResultTypeID,
5656
RecursiveValueNeedsResultTypeID,
5757
CyclicReferenceInvolvingID,

compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ abstract class Message(val errorId: ErrorMessageID) { self =>
7070
val kind = self.kind
7171
val explanation = self.explanation
7272
}
73+
74+
def appendExplanation(suffix: => String): Message = new Message(errorId):
75+
val msg = self.msg
76+
val kind = self.kind
77+
val explanation = self.explanation ++ suffix
7378
}
7479

7580
/** An extended message keeps the contained message from being evaluated, while

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,14 @@ object messages {
12351235
|""".stripMargin
12361236
}
12371237

1238+
case class UnreducibleApplication(tycon: Type)(using Context) extends Message(UnreducibleApplicationID):
1239+
val kind = "Type"
1240+
val msg = em"unreducible application of higher-kinded type $tycon to wildcard arguments"
1241+
val explanation =
1242+
em"""|An abstract type constructor cannot be applied to wildcard arguments.
1243+
|Such applications are equivalent to existential types, which are not
1244+
|supported in Scala 3."""
1245+
12381246
case class OverloadedOrRecursiveMethodNeedsResultType(cycleSym: Symbol)(implicit ctx: Context)
12391247
extends Message(OverloadedOrRecursiveMethodNeedsResultTypeID) {
12401248
val kind: String = "Cyclic"
@@ -1464,13 +1472,13 @@ object messages {
14641472
val parameters = if (numParams == 1) "parameter" else "parameters"
14651473
val msg: String = em"Missing type $parameters for $tpe"
14661474
val kind: String = "Type Mismatch"
1467-
val explanation: String = em"A fully applied type is expected but $tpe takes $numParams $parameters."
1475+
val explanation: String = em"A fully applied type is expected but $tpe takes $numParams $parameters"
14681476
}
14691477

14701478
case class DoesNotConformToBound(tpe: Type, which: String, bound: Type)(
14711479
err: Errors)(implicit ctx: Context)
14721480
extends Message(DoesNotConformToBoundID) {
1473-
val msg: String = em"Type argument ${tpe} does not conform to $which bound $bound ${err.whyNoMatchStr(tpe, bound)}"
1481+
val msg: String = em"Type argument ${tpe} does not conform to $which bound $bound${err.whyNoMatchStr(tpe, bound)}"
14741482
val kind: String = "Type Mismatch"
14751483
val explanation: String = ""
14761484
}

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,15 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
298298
else if (tree.tpt.symbol == defn.orType)
299299
() // nothing to do
300300
else
301-
Checking.checkAppliedType(tree, boundsCheck = !ctx.mode.is(Mode.Pattern))
301+
Checking.checkAppliedType(tree)
302302
super.transform(tree)
303303
case SingletonTypeTree(ref) =>
304304
Checking.checkRealizable(ref.tpe, ref.posd)
305305
super.transform(tree)
306306
case tree: TypeTree =>
307+
if tree.span.isZeroExtent then
308+
// Don't check TypeTrees with non-zero extent; these are derived from explicit types
309+
Checking.checkAppliedTypesIn(tree)
307310
tree.withType(
308311
tree.tpe match {
309312
case AnnotatedType(tpe, annot) => AnnotatedType(tpe, transformAnnot(annot))

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,37 @@ import scala.internal.Chars.isOperatorPart
4242
object Checking {
4343
import tpd._
4444

45+
/** Add further information for error messages involving applied types if the
46+
* type is inferred:
47+
* 1. the full inferred type in a TypeTree node
48+
* 2. the applied type causing the error, if different from (1)
49+
*/
50+
private def showInferred(msg: Message, app: Type, tpt: Tree)(using ctx: Context): Message =
51+
if tpt.isInstanceOf[TypeTree] then
52+
def subPart = if app eq tpt.tpe then "" else i" subpart $app of"
53+
msg.append(i" in$subPart inferred type ${tpt}")
54+
.appendExplanation("\n\nTo fix the problem, provide an explicit type.")
55+
else msg
56+
4557
/** A general checkBounds method that can be used for TypeApply nodes as
4658
* well as for AppliedTypeTree nodes. Also checks that type arguments to
4759
* *-type parameters are fully applied.
48-
* See TypeOps.boundsViolations for an explanation of the parameters.
60+
* @param tpt If bounds are checked for an AppliedType, the type tree representing
61+
* or (in case it is inferred) containing the type.
62+
* See TypeOps.boundsViolations for an explanation of the first four parameters.
4963
*/
50-
def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type, app: Type = NoType)(implicit ctx: Context): Unit = {
64+
def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds],
65+
instantiate: (Type, List[Type]) => Type, app: Type = NoType, tpt: Tree = EmptyTree)(implicit ctx: Context): Unit =
5166
args.lazyZip(boundss).foreach { (arg, bound) =>
52-
if (!bound.isLambdaSub && !arg.tpe.hasSimpleKind)
53-
errorTree(arg, MissingTypeParameterInTypeApp(arg.tpe))
67+
if !bound.isLambdaSub && !arg.tpe.hasSimpleKind then
68+
errorTree(arg,
69+
showInferred(MissingTypeParameterInTypeApp(arg.tpe), app, tpt))
5470
}
55-
for ((arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate, app))
71+
for (arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate, app) do
5672
ctx.error(
57-
DoesNotConformToBound(arg.tpe, which, bound)(err),
73+
showInferred(DoesNotConformToBound(arg.tpe, which, bound)(err),
74+
app, tpt),
5875
arg.sourcePos.focus)
59-
}
6076

6177
/** Check that type arguments `args` conform to corresponding bounds in `tl`
6278
* Note: This does not check the bounds of AppliedTypeTrees. These
@@ -71,8 +87,11 @@ object Checking {
7187
* check that it or one of its supertypes can be reduced to a normal application.
7288
* Unreducible applications correspond to general existentials, and we
7389
* cannot handle those.
90+
* @param tree The applied type tree to check
91+
* @param tpt If `tree` is synthesized from a type in a TypeTree,
92+
* the original TypeTree, or EmptyTree otherwise.
7493
*/
75-
def checkAppliedType(tree: AppliedTypeTree, boundsCheck: Boolean)(implicit ctx: Context): Unit = {
94+
def checkAppliedType(tree: AppliedTypeTree, tpt: Tree = EmptyTree)(using ctx: Context): Unit = {
7695
val AppliedTypeTree(tycon, args) = tree
7796
// If `args` is a list of named arguments, return corresponding type parameters,
7897
// otherwise return type parameters unchanged
@@ -84,13 +103,14 @@ object Checking {
84103
HKTypeLambda.fromParams(tparams, bound).appliedTo(args)
85104
case _ =>
86105
bound // paramInfoAsSeenFrom already took care of instantiation in this case
87-
if (boundsCheck) checkBounds(args, bounds, instantiate, tree.tpe)
106+
if !ctx.mode.is(Mode.Pattern) then
107+
checkBounds(args, bounds, instantiate, tree.tpe, tpt)
88108

89109
def checkWildcardApply(tp: Type): Unit = tp match {
90110
case tp @ AppliedType(tycon, _) =>
91111
if (tycon.isLambdaSub && tp.hasWildcardArg)
92112
ctx.errorOrMigrationWarning(
93-
ex"unreducible application of higher-kinded type $tycon to wildcard arguments",
113+
showInferred(UnreducibleApplication(tycon), tp, tpt),
94114
tree.sourcePos)
95115
case _ =>
96116
}
@@ -99,6 +119,20 @@ object Checking {
99119
checkValidIfApply(ctx.addMode(Mode.AllowLambdaWildcardApply))
100120
}
101121

122+
/** Check all applied type trees in inferred type `tpt` for well-formedness */
123+
def checkAppliedTypesIn(tpt: TypeTree)(implicit ctx: Context): Unit =
124+
val checker = new TypeTraverser:
125+
def traverse(tp: Type) =
126+
tp match
127+
case AppliedType(tycon, argTypes) =>
128+
checkAppliedType(
129+
untpd.AppliedTypeTree(TypeTree(tycon), argTypes.map(TypeTree))
130+
.withType(tp).withSpan(tpt.span.toSynthetic),
131+
tpt)
132+
case _ =>
133+
traverseChildren(tp)
134+
checker.traverse(tpt.tpe)
135+
102136
def checkNoWildcard(tree: Tree)(implicit ctx: Context): Tree = tree.tpe match {
103137
case tpe: TypeBounds => errorTree(tree, "no wildcard type allowed here")
104138
case _ => tree

compiler/src/dotty/tools/dotc/util/Spans.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ object Spans {
9595
def isSourceDerived: Boolean = !isSynthetic
9696

9797
/** Is this a zero-extent span? */
98-
def isZeroExtent: Boolean = start == end
98+
def isZeroExtent: Boolean = exists && start == end
9999

100100
/** A span where all components are shifted by a given `offset`
101101
* relative to this span.

tests/neg/i5302.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
type L[X]
2+
def foo = { class A; null.asInstanceOf[L[A]] } // error // error

0 commit comments

Comments
 (0)