Skip to content

Commit b64b8a9

Browse files
authored
Merge pull request scala#8462 from dotty-staging/fix-#5302
Fix scala#5302: Check well-formedness of inferred types
2 parents b60276b + 1f354e9 commit b64b8a9

File tree

11 files changed

+131
-32
lines changed

11 files changed

+131
-32
lines changed

compiler/src/dotty/tools/dotc/core/TypeOps.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,14 +370,16 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
370370
* In fact the current treatment for this sitiuation can so far only be classified as "not obviously wrong",
371371
* (maybe it still needs to be revised).
372372
*/
373-
def boundsViolations(args: List[Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type, app: Type)(implicit ctx: Context): List[BoundsViolation] = {
373+
def boundsViolations(args: List[Tree], boundss: List[TypeBounds],
374+
instantiate: (Type, List[Type]) => Type, app: Type)(
375+
implicit ctx: Context): List[BoundsViolation] = {
374376
val argTypes = args.tpes
375377

376378
/** Replace all wildcards in `tps` with `<app>#<tparam>` where `<tparam>` is the
377379
* type parameter corresponding to the wildcard.
378380
*/
379381
def skolemizeWildcardArgs(tps: List[Type], app: Type) = app match {
380-
case AppliedType(tycon, args) if tycon.typeSymbol.isClass && !scala2CompatMode =>
382+
case AppliedType(tycon: TypeRef, args) if tycon.typeSymbol.isClass && !scala2CompatMode =>
381383
tps.zipWithConserve(tycon.typeSymbol.typeParams) {
382384
(tp, tparam) => tp match {
383385
case _: TypeBounds => app.select(tparam)

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: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,24 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
142142
tree
143143
}
144144

145+
private def processValOrDefDef(tree: Tree)(using Context): tree.type =
146+
tree match
147+
case tree: ValOrDefDef if !tree.symbol.is(Synthetic) =>
148+
checkInferredWellFormed(tree.tpt)
149+
case _ =>
150+
processMemberDef(tree)
151+
152+
private def checkInferredWellFormed(tree: Tree)(using ctx: Context): Unit = tree match
153+
case tree: TypeTree
154+
if tree.span.isZeroExtent
155+
// don't check TypeTrees with non-zero extent;
156+
// these are derived from explicit types
157+
&& !ctx.reporter.errorsReported
158+
// don't check if errors were already reported; this avoids follow-on errors
159+
// for inferred types if explicit types are already ill-formed
160+
=> Checking.checkAppliedTypesIn(tree)
161+
case _ =>
162+
145163
private def transformSelect(tree: Select, targs: List[Tree])(implicit ctx: Context): Tree = {
146164
val qual = tree.qualifier
147165
qual.symbol.moduleClass.denot match {
@@ -226,17 +244,26 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
226244
else dropInlines.transform(arg)))
227245
else
228246
tree
229-
methPart(app) match {
247+
def app1 =
248+
// reverse order of transforming args and fun. This way, we get a chance to see other
249+
// well-formedness errors before reporting errors in possible inferred type args of fun.
250+
val args1 = transform(app.args)
251+
cpy.Apply(app)(transform(app.fun), args1)
252+
methPart(app) match
230253
case Select(nu: New, nme.CONSTRUCTOR) if isCheckable(nu) =>
231254
// need to check instantiability here, because the type of the New itself
232255
// might be a type constructor.
233256
Checking.checkInstantiable(tree.tpe, nu.posd)
234-
withNoCheckNews(nu :: Nil)(super.transform(app))
257+
withNoCheckNews(nu :: Nil)(app1)
235258
case _ =>
236-
super.transform(app)
237-
}
259+
app1
260+
case UnApply(fun, implicits, patterns) =>
261+
// Reverse transform order for the same reason as in `app1` above.
262+
val patterns1 = transform(patterns)
263+
cpy.UnApply(tree)(transform(fun), transform(implicits), patterns1)
238264
case tree: TypeApply =>
239265
val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree)
266+
args.foreach(checkInferredWellFormed)
240267
if (fn.symbol != defn.ChildAnnot.primaryConstructor)
241268
// Make an exception for ChildAnnot, which should really have AnyKind bounds
242269
Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType])
@@ -262,11 +289,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
262289
}
263290
case tree: ValDef =>
264291
val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol))
265-
processMemberDef(super.transform(tree1))
292+
processValOrDefDef(super.transform(tree1))
266293
case tree: DefDef =>
267294
annotateContextResults(tree)
268295
val tree1 = cpy.DefDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol))
269-
processMemberDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef]))
296+
processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef]))
270297
case tree: TypeDef =>
271298
val sym = tree.symbol
272299
if (sym.isClass)
@@ -298,7 +325,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
298325
else if (tree.tpt.symbol == defn.orType)
299326
() // nothing to do
300327
else
301-
Checking.checkAppliedType(tree, boundsCheck = !ctx.mode.is(Mode.Pattern))
328+
Checking.checkAppliedType(tree)
302329
super.transform(tree)
303330
case SingletonTypeTree(ref) =>
304331
Checking.checkRealizable(ref.tpe, ref.posd)

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

Lines changed: 47 additions & 15 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 is 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,31 +87,33 @@ 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
7998
val tparams = tycon.tpe.typeParams
80-
def argNamed(tparam: ParamInfo) = args.find {
81-
case NamedArg(name, _) => name == tparam.paramName
82-
case _ => false
83-
}.getOrElse(TypeTree(tparam.paramRef))
84-
val orderedArgs = if (hasNamedArg(args)) tparams.map(argNamed) else args
8599
val bounds = tparams.map(_.paramInfoAsSeenFrom(tree.tpe).bounds)
86100
def instantiate(bound: Type, args: List[Type]) =
87101
tparams match
88102
case LambdaParam(lam, _) :: _ =>
89103
HKTypeLambda.fromParams(tparams, bound).appliedTo(args)
90104
case _ =>
91105
bound // paramInfoAsSeenFrom already took care of instantiation in this case
92-
if (boundsCheck) checkBounds(orderedArgs, bounds, instantiate, tree.tpe)
106+
if !ctx.mode.is(Mode.Pattern) // no bounds checking in patterns
107+
&& tycon.symbol != defn.TypeBoxClass // TypeBox types are generated for capture
108+
// conversion, may contain AnyKind as arguments
109+
then
110+
checkBounds(args, bounds, instantiate, tree.tpe, tpt)
93111

94112
def checkWildcardApply(tp: Type): Unit = tp match {
95113
case tp @ AppliedType(tycon, _) =>
96114
if (tycon.isLambdaSub && tp.hasWildcardArg)
97115
ctx.errorOrMigrationWarning(
98-
ex"unreducible application of higher-kinded type $tycon to wildcard arguments",
116+
showInferred(UnreducibleApplication(tycon), tp, tpt),
99117
tree.sourcePos)
100118
case _ =>
101119
}
@@ -104,6 +122,20 @@ object Checking {
104122
checkValidIfApply(ctx.addMode(Mode.AllowLambdaWildcardApply))
105123
}
106124

125+
/** Check all applied type trees in inferred type `tpt` for well-formedness */
126+
def checkAppliedTypesIn(tpt: TypeTree)(implicit ctx: Context): Unit =
127+
val checker = new TypeTraverser:
128+
def traverse(tp: Type) =
129+
tp match
130+
case AppliedType(tycon, argTypes) =>
131+
checkAppliedType(
132+
untpd.AppliedTypeTree(TypeTree(tycon), argTypes.map(TypeTree))
133+
.withType(tp).withSpan(tpt.span.toSynthetic),
134+
tpt)
135+
case _ =>
136+
traverseChildren(tp)
137+
checker.traverse(tpt.tpe)
138+
107139
def checkNoWildcard(tree: Tree)(implicit ctx: Context): Tree = tree.tpe match {
108140
case tpe: TypeBounds => errorTree(tree, "no wildcard type allowed here")
109141
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/i4382.check

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1-
-- Error: tests/neg/i4382.scala:3:10 -----------------------------------------------------------------------------------
1+
-- [E043] Type Error: tests/neg/i4382.scala:3:10 -----------------------------------------------------------------------
22
3 | def v1: Id[_] = ??? // error
33
| ^^^^^
44
| unreducible application of higher-kinded type App.Id to wildcard arguments
5-
-- Error: tests/neg/i4382.scala:6:10 -----------------------------------------------------------------------------------
5+
6+
longer explanation available when compiling with `-explain`
7+
-- [E043] Type Error: tests/neg/i4382.scala:6:10 -----------------------------------------------------------------------
68
6 | def v2: HkL[_] = ??? // error
79
| ^^^^^^
810
| unreducible application of higher-kinded type App.HkL to wildcard arguments
9-
-- Error: tests/neg/i4382.scala:9:10 -----------------------------------------------------------------------------------
11+
12+
longer explanation available when compiling with `-explain`
13+
-- [E043] Type Error: tests/neg/i4382.scala:9:10 -----------------------------------------------------------------------
1014
9 | def v3: HkU[_] = ??? // error
1115
| ^^^^^^
1216
| unreducible application of higher-kinded type App.HkU to wildcard arguments
13-
-- Error: tests/neg/i4382.scala:12:10 ----------------------------------------------------------------------------------
17+
18+
longer explanation available when compiling with `-explain`
19+
-- [E043] Type Error: tests/neg/i4382.scala:12:10 ----------------------------------------------------------------------
1420
12 | def v4: HkAbs[_] = ??? // error
1521
| ^^^^^^^^
1622
| unreducible application of higher-kinded type App.HkAbs to wildcard arguments
23+
24+
longer explanation available when compiling with `-explain`

tests/neg/i5302.scala

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

tests/neg/i6205.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- [E057] Type Mismatch Error: tests/neg/i6205.scala:4:9 ---------------------------------------------------------------
2+
4 | def foo = // error
3+
| ^
4+
| Type argument Nothing does not conform to lower bound Null in inferred type Contra[Nothing]
5+
6+
longer explanation available when compiling with `-explain`

tests/neg/i6205.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Contra[-T >: Null]
2+
3+
object Test:
4+
def foo = // error
5+
class A
6+
new Contra[A]
7+
8+
val x = foo

0 commit comments

Comments
 (0)