Skip to content

Fix #2771: pre-check kinds at Typer #2882

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -251,18 +251,21 @@ class TypeApplications(val self: Type) extends AnyVal {
}

/** Is self type higher-kinded (i.e. of kind != "*")? */
def isHK(implicit ctx: Context): Boolean = self.dealias match {
case self: TypeRef => self.info.isHK
case self: RefinedType => false
case self: HKTypeLambda => true
case self: SingletonType => false
def isHK(implicit ctx: Context): Boolean = hkResult.exists

/** If self type is higher-kinded, its result type, otherwise NoType */
def hkResult(implicit ctx: Context): Type = self.dealias match {
case self: TypeRef => self.info.hkResult
case self: RefinedType => NoType
case self: HKTypeLambda => self.resultType
case self: SingletonType => NoType
case self: TypeVar =>
// Using `origin` instead of `underlying`, as is done for typeParams,
// avoids having to set ephemeral in some cases.
self.origin.isHK
case self: WildcardType => self.optBounds.isHK
case self: TypeProxy => self.superType.isHK
case _ => false
self.origin.hkResult
case self: WildcardType => self.optBounds.hkResult
case self: TypeProxy => self.superType.hkResult
case _ => NoType
}

/** Dealias type if it can be done without forcing the TypeRef's info */
Expand Down
9 changes: 8 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2732,20 +2732,27 @@ object Types {
protected def paramName(param: ParamInfo.Of[N])(implicit ctx: Context): N =
param.paramName

protected def toPInfo(tp: Type)(implicit ctx: Context): PInfo

def fromParams[PI <: ParamInfo.Of[N]](params: List[PI], resultType: Type)(implicit ctx: Context): Type =
if (params.isEmpty) resultType
else apply(params.map(paramName))(
tl => params.map(param => tl.integrate(params, param.paramInfo).asInstanceOf[PInfo]),
tl => params.map(param => toPInfo(tl.integrate(params, param.paramInfo))),
tl => tl.integrate(params, resultType))
}

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

abstract class TypeLambdaCompanion[LT <: TypeLambda]
extends LambdaTypeCompanion[TypeName, TypeBounds, LT] {
def toPInfo(tp: Type)(implicit ctx: Context): TypeBounds = (tp: @unchecked) match {
case tp: TypeBounds => tp
case tp: ErrorType => TypeAlias(tp)
}
def syntheticParamName(n: Int) = tpnme.syntheticTypeParamName(n)
}

Expand Down
37 changes: 37 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,43 @@ object Checking {
checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply))
}

/** Check that kind of `arg` has the same outline as the kind of paramBounds.
* E.g. if `paramBounds` has kind * -> *, `arg` must have that kind as well,
* and analogously for all other kinds. This kind checking does not take into account
* variances or bounds. The more detailed kind checking is done as part of checkBounds in PostTyper.
* The purpose of preCheckKind is to do a rough test earlier in Typer,
* in order to prevent scenarios that lead to self application of
* types. Self application needs to be avoided since it can lead to stack overflows.
* Test cases are neg/i2771.scala and neg/i2771b.scala.
*/
def preCheckKind(arg: Tree, paramBounds: TypeBounds)(implicit ctx: Context): Tree = {
def result(tp: Type): Type = tp match {
case tp: HKTypeLambda => tp.resultType
case tp: TypeProxy => result(tp.superType)
case _ => defn.AnyType
}
def kindOK(argType: Type, boundType: Type): Boolean = {
// println(i"check kind rank2$arg $argType $boundType") // DEBUG
val argResult = argType.hkResult
val boundResult = argType.hkResult
if (argResult.exists)
boundResult.exists &&
kindOK(boundResult, argResult) &&
argType.typeParams.corresponds(boundType.typeParams)((ap, bp) =>
kindOK(ap.paramInfo, bp.paramInfo))
else !boundResult.exists
}
if (kindOK(arg.tpe, paramBounds.hi)) arg
else errorTree(arg, em"${arg.tpe} has wrong kind")
}

def preCheckKinds(args: List[Tree], paramBoundss: List[TypeBounds])(implicit ctx: Context): List[Tree] = {
val args1 = args.zipWithConserve(paramBoundss)(preCheckKind)
args1 ++ args.drop(paramBoundss.length)
// add any arguments that do not correspond to a parameter back,
// so the wrong number of parameters is reported afterwards.
}

/** Check that `tp` refers to a nonAbstract class
* and that the instance conforms to the self type of the created class.
*/
Expand Down
14 changes: 10 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -795,10 +795,16 @@ class Namer { typer: Typer =>
nestedCtx = localContext(sym).setNewScope
myTypeParams = {
implicit val ctx = nestedCtx
val tparams = original.rhs match {
case LambdaTypeTree(tparams, _) => tparams
def typeParamTrees(tdef: Tree): List[TypeDef] = tdef match {
case TypeDef(_, original) =>
original match {
case LambdaTypeTree(tparams, _) => tparams
case original: DerivedFromParamTree => typeParamTrees(original.watched)
case _ => Nil
}
case _ => Nil
}
val tparams = typeParamTrees(original)
completeParams(tparams)
tparams.map(symbolOfTree(_).asType)
}
Expand Down Expand Up @@ -832,9 +838,9 @@ class Namer { typer: Typer =>
* only if parent type contains uninstantiated type parameters.
*/
def parentType(parent: untpd.Tree)(implicit ctx: Context): Type =
if (parent.isType) {
if (parent.isType)
typedAheadType(parent, AnyTypeConstructorProto).tpe
} else {
else {
val (core, targs) = stripApply(parent) match {
case TypeApply(core, targs) => (core, targs)
case core => (core, Nil)
Expand Down
10 changes: 6 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import NameOps._
import collection.mutable
import reporting.diagnostic.Message
import reporting.diagnostic.messages._
import Checking.{preCheckKind, preCheckKinds, checkNoPrivateLeaks}

trait TypeAssigner {
import tpd._
Expand Down Expand Up @@ -134,8 +135,7 @@ trait TypeAssigner {
avoid(expr.tpe, localSyms(bindings).filter(_.isTerm))

def avoidPrivateLeaks(sym: Symbol, pos: Position)(implicit ctx: Context): Type =
if (!sym.is(SyntheticOrPrivate) && sym.owner.isClass)
Checking.checkNoPrivateLeaks(sym, pos)
if (!sym.is(SyntheticOrPrivate) && sym.owner.isClass) checkNoPrivateLeaks(sym, pos)
else sym.info

def seqToRepeated(tree: Tree)(implicit ctx: Context): Tree =
Expand Down Expand Up @@ -348,6 +348,8 @@ trait TypeAssigner {
case pt: TypeLambda =>
val paramNames = pt.paramNames
if (hasNamedArg(args)) {
val paramBoundsByName = paramNames.zip(pt.paramInfos).toMap

// Type arguments which are specified by name (immutable after this first loop)
val namedArgMap = new mutable.HashMap[Name, Type]
for (NamedArg(name, arg) <- args)
Expand All @@ -356,7 +358,7 @@ trait TypeAssigner {
else if (!paramNames.contains(name))
ctx.error(s"undefined parameter name, required: ${paramNames.mkString(" or ")}", arg.pos)
else
namedArgMap(name) = arg.tpe
namedArgMap(name) = preCheckKind(arg, paramBoundsByName(name.asTypeName)).tpe

// Holds indexes of non-named typed arguments in paramNames
val gapBuf = new mutable.ListBuffer[Int]
Expand Down Expand Up @@ -389,7 +391,7 @@ trait TypeAssigner {
}
}
else {
val argTypes = args.tpes
val argTypes = preCheckKinds(args, pt.paramInfos).tpes
if (sameLength(argTypes, paramNames) || ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes)
else wrongNumberOfTypeArgs(fn.tpe, pt.typeParams, args, tree.pos)
}
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1140,8 +1140,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
}
args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]]
}
val args2 = preCheckKinds(args1, tparams.map(_.paramInfo.bounds))
// check that arguments conform to bounds is done in phase PostTyper
assignType(cpy.AppliedTypeTree(tree)(tpt1, args1), tpt1, args1)
assignType(cpy.AppliedTypeTree(tree)(tpt1, args2), tpt1, args2)
}
}

Expand Down
4 changes: 2 additions & 2 deletions tests/neg/i1652.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
object Test {
val v: Array[Array[Array]] = Array() // error // error
val v: Array[Array[Array]] = Array() // error: Array takes type parameters
def f[T](w: Array[Array[T]]) = { for (r <- w) () }
f(v) // error
f(v)
}
24 changes: 24 additions & 0 deletions tests/neg/i2771.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
trait A { type L[X] }
trait B { type L }
trait C { type M <: A }
trait D { type M >: B }

object Test {
def test(x: C with D): Unit = {
def f(y: x.M)(z: y.L[y.L]) = z // error: y.L has wrong kind
f(new B { type L[F[_]] = F[F] })(1) // error: F has wrong kind
}

type LB[F[_]]

type LL[F[_]] <: LB[F] // ok

def foo[X[_] <: Any]() = ()
foo[Int]() // an error would be raised later, during PostTyper.

def bar[X, Y]() = ()
bar[List, Int]() // error: List has wrong kind

bar[Y = List, X = Int]() // error: List has wrong kind

}
11 changes: 11 additions & 0 deletions tests/neg/i2771b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
trait A { type L[X[_]] }
trait B { type L }
trait C { type M <: A }
trait D { type M >: B }

object Test {
def test(x: C with D): Unit = {
def f(y: x.M)(z: y.L[y.L]) = z // error: y.L has wrong kind
f(new B { type L[F[_[_]]] = F[F] })(1) // error: F has wrong kind
}
}
11 changes: 11 additions & 0 deletions tests/neg/i2771c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
trait A { type L[X[_]] }
trait B { type L }
trait C { type M <: A }
trait D { type M >: B }

object Test {
def test(x: D with C): Unit = {
def f(y: x.M)(z: y.L[y.L]) = z // error: y.L has wrong kind
new B { type L[F[_[_]]] = F[F] } // error: F has wrong kind
}
}