Skip to content

Commit 775e3db

Browse files
authored
Merge pull request #2882 from dotty-staging/fix-#2770
Fix #2771: pre-check kinds at Typer
2 parents 3ce184f + 567b91f commit 775e3db

File tree

10 files changed

+123
-21
lines changed

10 files changed

+123
-21
lines changed

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

+12-9
Original file line numberDiff line numberDiff line change
@@ -251,18 +251,21 @@ class TypeApplications(val self: Type) extends AnyVal {
251251
}
252252

253253
/** Is self type higher-kinded (i.e. of kind != "*")? */
254-
def isHK(implicit ctx: Context): Boolean = self.dealias match {
255-
case self: TypeRef => self.info.isHK
256-
case self: RefinedType => false
257-
case self: HKTypeLambda => true
258-
case self: SingletonType => false
254+
def isHK(implicit ctx: Context): Boolean = hkResult.exists
255+
256+
/** If self type is higher-kinded, its result type, otherwise NoType */
257+
def hkResult(implicit ctx: Context): Type = self.dealias match {
258+
case self: TypeRef => self.info.hkResult
259+
case self: RefinedType => NoType
260+
case self: HKTypeLambda => self.resultType
261+
case self: SingletonType => NoType
259262
case self: TypeVar =>
260263
// Using `origin` instead of `underlying`, as is done for typeParams,
261264
// avoids having to set ephemeral in some cases.
262-
self.origin.isHK
263-
case self: WildcardType => self.optBounds.isHK
264-
case self: TypeProxy => self.superType.isHK
265-
case _ => false
265+
self.origin.hkResult
266+
case self: WildcardType => self.optBounds.hkResult
267+
case self: TypeProxy => self.superType.hkResult
268+
case _ => NoType
266269
}
267270

268271
/** Dealias type if it can be done without forcing the TypeRef's info */

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -2733,20 +2733,27 @@ object Types {
27332733
protected def paramName(param: ParamInfo.Of[N])(implicit ctx: Context): N =
27342734
param.paramName
27352735

2736+
protected def toPInfo(tp: Type)(implicit ctx: Context): PInfo
2737+
27362738
def fromParams[PI <: ParamInfo.Of[N]](params: List[PI], resultType: Type)(implicit ctx: Context): Type =
27372739
if (params.isEmpty) resultType
27382740
else apply(params.map(paramName))(
2739-
tl => params.map(param => tl.integrate(params, param.paramInfo).asInstanceOf[PInfo]),
2741+
tl => params.map(param => toPInfo(tl.integrate(params, param.paramInfo))),
27402742
tl => tl.integrate(params, resultType))
27412743
}
27422744

27432745
abstract class TermLambdaCompanion[LT <: TermLambda]
27442746
extends LambdaTypeCompanion[TermName, Type, LT] {
2747+
def toPInfo(tp: Type)(implicit ctx: Context): Type = tp
27452748
def syntheticParamName(n: Int) = nme.syntheticParamName(n)
27462749
}
27472750

27482751
abstract class TypeLambdaCompanion[LT <: TypeLambda]
27492752
extends LambdaTypeCompanion[TypeName, TypeBounds, LT] {
2753+
def toPInfo(tp: Type)(implicit ctx: Context): TypeBounds = (tp: @unchecked) match {
2754+
case tp: TypeBounds => tp
2755+
case tp: ErrorType => TypeAlias(tp)
2756+
}
27502757
def syntheticParamName(n: Int) = tpnme.syntheticTypeParamName(n)
27512758
}
27522759

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

+37
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,43 @@ object Checking {
100100
checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply))
101101
}
102102

103+
/** Check that kind of `arg` has the same outline as the kind of paramBounds.
104+
* E.g. if `paramBounds` has kind * -> *, `arg` must have that kind as well,
105+
* and analogously for all other kinds. This kind checking does not take into account
106+
* variances or bounds. The more detailed kind checking is done as part of checkBounds in PostTyper.
107+
* The purpose of preCheckKind is to do a rough test earlier in Typer,
108+
* in order to prevent scenarios that lead to self application of
109+
* types. Self application needs to be avoided since it can lead to stack overflows.
110+
* Test cases are neg/i2771.scala and neg/i2771b.scala.
111+
*/
112+
def preCheckKind(arg: Tree, paramBounds: TypeBounds)(implicit ctx: Context): Tree = {
113+
def result(tp: Type): Type = tp match {
114+
case tp: HKTypeLambda => tp.resultType
115+
case tp: TypeProxy => result(tp.superType)
116+
case _ => defn.AnyType
117+
}
118+
def kindOK(argType: Type, boundType: Type): Boolean = {
119+
// println(i"check kind rank2$arg $argType $boundType") // DEBUG
120+
val argResult = argType.hkResult
121+
val boundResult = argType.hkResult
122+
if (argResult.exists)
123+
boundResult.exists &&
124+
kindOK(boundResult, argResult) &&
125+
argType.typeParams.corresponds(boundType.typeParams)((ap, bp) =>
126+
kindOK(ap.paramInfo, bp.paramInfo))
127+
else !boundResult.exists
128+
}
129+
if (kindOK(arg.tpe, paramBounds.hi)) arg
130+
else errorTree(arg, em"${arg.tpe} has wrong kind")
131+
}
132+
133+
def preCheckKinds(args: List[Tree], paramBoundss: List[TypeBounds])(implicit ctx: Context): List[Tree] = {
134+
val args1 = args.zipWithConserve(paramBoundss)(preCheckKind)
135+
args1 ++ args.drop(paramBoundss.length)
136+
// add any arguments that do not correspond to a parameter back,
137+
// so the wrong number of parameters is reported afterwards.
138+
}
139+
103140
/** Check that `tp` refers to a nonAbstract class
104141
* and that the instance conforms to the self type of the created class.
105142
*/

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

+10-4
Original file line numberDiff line numberDiff line change
@@ -795,10 +795,16 @@ class Namer { typer: Typer =>
795795
nestedCtx = localContext(sym).setNewScope
796796
myTypeParams = {
797797
implicit val ctx = nestedCtx
798-
val tparams = original.rhs match {
799-
case LambdaTypeTree(tparams, _) => tparams
798+
def typeParamTrees(tdef: Tree): List[TypeDef] = tdef match {
799+
case TypeDef(_, original) =>
800+
original match {
801+
case LambdaTypeTree(tparams, _) => tparams
802+
case original: DerivedFromParamTree => typeParamTrees(original.watched)
803+
case _ => Nil
804+
}
800805
case _ => Nil
801806
}
807+
val tparams = typeParamTrees(original)
802808
completeParams(tparams)
803809
tparams.map(symbolOfTree(_).asType)
804810
}
@@ -832,9 +838,9 @@ class Namer { typer: Typer =>
832838
* only if parent type contains uninstantiated type parameters.
833839
*/
834840
def parentType(parent: untpd.Tree)(implicit ctx: Context): Type =
835-
if (parent.isType) {
841+
if (parent.isType)
836842
typedAheadType(parent, AnyTypeConstructorProto).tpe
837-
} else {
843+
else {
838844
val (core, targs) = stripApply(parent) match {
839845
case TypeApply(core, targs) => (core, targs)
840846
case core => (core, Nil)

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

+6-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import NameOps._
1414
import collection.mutable
1515
import reporting.diagnostic.Message
1616
import reporting.diagnostic.messages._
17+
import Checking.{preCheckKind, preCheckKinds, checkNoPrivateLeaks}
1718

1819
trait TypeAssigner {
1920
import tpd._
@@ -134,8 +135,7 @@ trait TypeAssigner {
134135
avoid(expr.tpe, localSyms(bindings).filter(_.isTerm))
135136

136137
def avoidPrivateLeaks(sym: Symbol, pos: Position)(implicit ctx: Context): Type =
137-
if (!sym.is(SyntheticOrPrivate) && sym.owner.isClass)
138-
Checking.checkNoPrivateLeaks(sym, pos)
138+
if (!sym.is(SyntheticOrPrivate) && sym.owner.isClass) checkNoPrivateLeaks(sym, pos)
139139
else sym.info
140140

141141
def seqToRepeated(tree: Tree)(implicit ctx: Context): Tree =
@@ -348,6 +348,8 @@ trait TypeAssigner {
348348
case pt: TypeLambda =>
349349
val paramNames = pt.paramNames
350350
if (hasNamedArg(args)) {
351+
val paramBoundsByName = paramNames.zip(pt.paramInfos).toMap
352+
351353
// Type arguments which are specified by name (immutable after this first loop)
352354
val namedArgMap = new mutable.HashMap[Name, Type]
353355
for (NamedArg(name, arg) <- args)
@@ -356,7 +358,7 @@ trait TypeAssigner {
356358
else if (!paramNames.contains(name))
357359
ctx.error(s"undefined parameter name, required: ${paramNames.mkString(" or ")}", arg.pos)
358360
else
359-
namedArgMap(name) = arg.tpe
361+
namedArgMap(name) = preCheckKind(arg, paramBoundsByName(name.asTypeName)).tpe
360362

361363
// Holds indexes of non-named typed arguments in paramNames
362364
val gapBuf = new mutable.ListBuffer[Int]
@@ -389,7 +391,7 @@ trait TypeAssigner {
389391
}
390392
}
391393
else {
392-
val argTypes = args.tpes
394+
val argTypes = preCheckKinds(args, pt.paramInfos).tpes
393395
if (sameLength(argTypes, paramNames) || ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes)
394396
else wrongNumberOfTypeArgs(fn.tpe, pt.typeParams, args, tree.pos)
395397
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -1140,8 +1140,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
11401140
}
11411141
args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]]
11421142
}
1143+
val args2 = preCheckKinds(args1, tparams.map(_.paramInfo.bounds))
11431144
// check that arguments conform to bounds is done in phase PostTyper
1144-
assignType(cpy.AppliedTypeTree(tree)(tpt1, args1), tpt1, args1)
1145+
assignType(cpy.AppliedTypeTree(tree)(tpt1, args2), tpt1, args2)
11451146
}
11461147
}
11471148

tests/neg/i1652.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
object Test {
2-
val v: Array[Array[Array]] = Array() // error // error
2+
val v: Array[Array[Array]] = Array() // error: Array takes type parameters
33
def f[T](w: Array[Array[T]]) = { for (r <- w) () }
4-
f(v) // error
4+
f(v)
55
}

tests/neg/i2771.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
trait A { type L[X] }
2+
trait B { type L }
3+
trait C { type M <: A }
4+
trait D { type M >: B }
5+
6+
object Test {
7+
def test(x: C with D): Unit = {
8+
def f(y: x.M)(z: y.L[y.L]) = z // error: y.L has wrong kind
9+
f(new B { type L[F[_]] = F[F] })(1) // error: F has wrong kind
10+
}
11+
12+
type LB[F[_]]
13+
14+
type LL[F[_]] <: LB[F] // ok
15+
16+
def foo[X[_] <: Any]() = ()
17+
foo[Int]() // an error would be raised later, during PostTyper.
18+
19+
def bar[X, Y]() = ()
20+
bar[List, Int]() // error: List has wrong kind
21+
22+
bar[Y = List, X = Int]() // error: List has wrong kind
23+
24+
}

tests/neg/i2771b.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
trait A { type L[X[_]] }
2+
trait B { type L }
3+
trait C { type M <: A }
4+
trait D { type M >: B }
5+
6+
object Test {
7+
def test(x: C with D): Unit = {
8+
def f(y: x.M)(z: y.L[y.L]) = z // error: y.L has wrong kind
9+
f(new B { type L[F[_[_]]] = F[F] })(1) // error: F has wrong kind
10+
}
11+
}

tests/neg/i2771c.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
trait A { type L[X[_]] }
2+
trait B { type L }
3+
trait C { type M <: A }
4+
trait D { type M >: B }
5+
6+
object Test {
7+
def test(x: D with C): Unit = {
8+
def f(y: x.M)(z: y.L[y.L]) = z // error: y.L has wrong kind
9+
new B { type L[F[_[_]]] = F[F] } // error: F has wrong kind
10+
}
11+
}

0 commit comments

Comments
 (0)