From 14d5901c3615a7d16d19885934071befcbe6e134 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Jul 2017 13:42:20 +0200 Subject: [PATCH 1/9] Add simple kind checking --- .../src/dotty/tools/dotc/typer/Checking.scala | 9 +++++++++ .../dotty/tools/dotc/typer/TypeAssigner.scala | 10 ++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 3 ++- tests/neg/i2771.scala | 20 +++++++++++++++++++ 4 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 tests/neg/i2771.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 7cb144adb025..8ea3fc147f12 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -100,6 +100,15 @@ object Checking { checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply)) } + def checkKind(arg: Tree, paramBounds: TypeBounds)(implicit ctx: Context): Tree = + if (arg.tpe.isHK && !paramBounds.isHK) + errorTree(arg, em"${arg.tpe} takes type parameters") + else + arg + + def checkKinds(args: List[Tree], paramBoundss: List[TypeBounds])(implicit ctx: Context): List[Tree] = + args.zipWithConserve(paramBoundss)(checkKind) + /** Check that `tp` refers to a nonAbstract class * and that the instance conforms to the self type of the created class. */ diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index b89ee3d4c595..6a57d7262d5d 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -14,6 +14,7 @@ import NameOps._ import collection.mutable import reporting.diagnostic.Message import reporting.diagnostic.messages._ +import Checking.{checkKind, checkKinds, checkNoPrivateLeaks} trait TypeAssigner { import tpd._ @@ -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 = @@ -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) @@ -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) = checkKind(arg, paramBoundsByName(name.asTypeName)).tpe // Holds indexes of non-named typed arguments in paramNames val gapBuf = new mutable.ListBuffer[Int] @@ -389,7 +391,7 @@ trait TypeAssigner { } } else { - val argTypes = args.tpes + val argTypes = checkKinds(args, pt.paramInfos).tpes if (sameLength(argTypes, paramNames) || ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes) else wrongNumberOfTypeArgs(fn.tpe, pt.typeParams, args, tree.pos) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b2a23e680bfd..605cc0bbc7f2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1140,8 +1140,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]] } + val args2 = checkKinds(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) } } diff --git a/tests/neg/i2771.scala b/tests/neg/i2771.scala new file mode 100644 index 000000000000..0c11acd1c46f --- /dev/null +++ b/tests/neg/i2771.scala @@ -0,0 +1,20 @@ +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 takes type parameters + f(new B { type L[F[_]] = F[F] })(1) // error: F takes type parameters + } + + def foo[X[_] <: Any]() = () + foo[Int]() // should be an error + + def bar[X, Y]() = () + bar[List, Int]() // error: List takes type parameters + + bar[Y = List, X = Int] // error: List takes type parameters + +} From d326f680170dc85d62d2858b59269d9623720b74 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Jul 2017 17:20:20 +0200 Subject: [PATCH 2/9] Avoid crashes due to errors when forming TypeLambdas After adding star-kind-checking, t3683-modified.scala caused a crash because an ErrorType ended up as the type of a type parameter but a TypeBounds was required. This was possible due to the unsafe cast in LambdaTypeCompanion.fromParams, which this commit eliminates. --- compiler/src/dotty/tools/dotc/core/Types.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8c6474ae0e55..df0b6d496c3d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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) } From 41a716beaa94c8fa68bbf6b4ca9ae26765e5a85e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Jul 2017 17:24:28 +0200 Subject: [PATCH 3/9] Improve robustness of computing type parameters in completers We need to take DerivedTypeTrees into account when computing the type parameters of a type completer. --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5038007b4ec0..0e2d18f71ae6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -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) } From ded42afb042b45b70d3ac7468d25c91d016c7392 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Jul 2017 17:28:29 +0200 Subject: [PATCH 4/9] Refine kind checking Fix problems in previous iteration. --- .../src/dotty/tools/dotc/typer/Checking.scala | 17 ++++++++++++++--- compiler/src/dotty/tools/dotc/typer/Namer.scala | 4 ++-- .../dotty/tools/dotc/typer/TypeAssigner.scala | 6 +++--- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/neg/i1652.scala | 4 ++-- tests/neg/i2771.scala | 4 ++-- 6 files changed, 24 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 8ea3fc147f12..cc54aadd435b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -100,14 +100,25 @@ object Checking { checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply)) } - def checkKind(arg: Tree, paramBounds: TypeBounds)(implicit ctx: Context): Tree = + /** Check that `arg` has a *-type, unless `paramBounds` is higher-kinded. + * More detailed kind checking is done as part of checkBounds in PostTyper. + * The purpose of checkStarKind 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 stackoverflows. + * A test case is neg/i2771.scala. + */ + def checkStarKind(arg: Tree, paramBounds: TypeBounds)(implicit ctx: Context): Tree = if (arg.tpe.isHK && !paramBounds.isHK) errorTree(arg, em"${arg.tpe} takes type parameters") else arg - def checkKinds(args: List[Tree], paramBoundss: List[TypeBounds])(implicit ctx: Context): List[Tree] = - args.zipWithConserve(paramBoundss)(checkKind) + def checkStarKinds(args: List[Tree], paramBoundss: List[TypeBounds])(implicit ctx: Context): List[Tree] = { + val args1 = args.zipWithConserve(paramBoundss)(checkStarKind) + 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. diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 0e2d18f71ae6..f9addf9cc8a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -838,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) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 6a57d7262d5d..584066ebdfe4 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -14,7 +14,7 @@ import NameOps._ import collection.mutable import reporting.diagnostic.Message import reporting.diagnostic.messages._ -import Checking.{checkKind, checkKinds, checkNoPrivateLeaks} +import Checking.{checkStarKind, checkStarKinds, checkNoPrivateLeaks} trait TypeAssigner { import tpd._ @@ -358,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) = checkKind(arg, paramBoundsByName(name.asTypeName)).tpe + namedArgMap(name) = checkStarKind(arg, paramBoundsByName(name.asTypeName)).tpe // Holds indexes of non-named typed arguments in paramNames val gapBuf = new mutable.ListBuffer[Int] @@ -391,7 +391,7 @@ trait TypeAssigner { } } else { - val argTypes = checkKinds(args, pt.paramInfos).tpes + val argTypes = checkStarKinds(args, pt.paramInfos).tpes if (sameLength(argTypes, paramNames) || ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes) else wrongNumberOfTypeArgs(fn.tpe, pt.typeParams, args, tree.pos) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 605cc0bbc7f2..4bfe6b4925a1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1140,7 +1140,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]] } - val args2 = checkKinds(args1, tparams.map(_.paramInfo.bounds)) + val args2 = checkStarKinds(args1, tparams.map(_.paramInfo.bounds)) // check that arguments conform to bounds is done in phase PostTyper assignType(cpy.AppliedTypeTree(tree)(tpt1, args2), tpt1, args2) } diff --git a/tests/neg/i1652.scala b/tests/neg/i1652.scala index b8f1297b33c7..5e8d27a96392 100644 --- a/tests/neg/i1652.scala +++ b/tests/neg/i1652.scala @@ -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) } diff --git a/tests/neg/i2771.scala b/tests/neg/i2771.scala index 0c11acd1c46f..2b44aefb0afc 100644 --- a/tests/neg/i2771.scala +++ b/tests/neg/i2771.scala @@ -6,11 +6,11 @@ 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 takes type parameters - f(new B { type L[F[_]] = F[F] })(1) // error: F takes type parameters + f(new B { type L[F[_]] = F[F] })(1) } def foo[X[_] <: Any]() = () - foo[Int]() // should be an error + foo[Int]() // an error would be raised later, during PostTyper. def bar[X, Y]() = () bar[List, Int]() // error: List takes type parameters From a39644dc062d5d5d610c34291a6f764897209318 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Jul 2017 17:45:24 +0200 Subject: [PATCH 5/9] Fix test --- tests/neg/i2771.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/neg/i2771.scala b/tests/neg/i2771.scala index 2b44aefb0afc..1ba796b0443b 100644 --- a/tests/neg/i2771.scala +++ b/tests/neg/i2771.scala @@ -6,15 +6,19 @@ 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 takes type parameters - f(new B { type L[F[_]] = F[F] })(1) + f(new B { type L[F[_]] = F[F] })(1) // error: F takes type parameters (follow-on-error) } + 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 takes type parameters - bar[Y = List, X = Int] // error: List takes type parameters + bar[Y = List, X = Int]() // error: List takes type parameters } From 1d313e9ff24bbc04d983906c31c422d89cf2b9b2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 18 Jul 2017 10:01:57 +0200 Subject: [PATCH 6/9] Generalize kind checking We now check that the rank of the kind of a type argument does not exceed the rank of its bounds. i2771b is an example that shows that checking at rank * only is not enough. --- .../src/dotty/tools/dotc/typer/Checking.scala | 24 +++++++++++-------- .../dotty/tools/dotc/typer/TypeAssigner.scala | 6 ++--- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/neg/i2771b.scala | 11 +++++++++ 4 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 tests/neg/i2771b.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index cc54aadd435b..bb6a08c9c10d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -100,21 +100,25 @@ object Checking { checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply)) } - /** Check that `arg` has a *-type, unless `paramBounds` is higher-kinded. + /** Check that the rank of the kind of `arg` does not exceed the rank of the + * kind of `paramBounds`. E.g. if `paramBounds` has *-kind, `arg` must have + * *-kind as well, and analogously for higher kinds. * More detailed kind checking is done as part of checkBounds in PostTyper. - * The purpose of checkStarKind is to do a rough test earlier in Typer, + * The purpose of checkKindRank 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 stackoverflows. * A test case is neg/i2771.scala. */ - def checkStarKind(arg: Tree, paramBounds: TypeBounds)(implicit ctx: Context): Tree = - if (arg.tpe.isHK && !paramBounds.isHK) - errorTree(arg, em"${arg.tpe} takes type parameters") - else - arg - - def checkStarKinds(args: List[Tree], paramBoundss: List[TypeBounds])(implicit ctx: Context): List[Tree] = { - val args1 = args.zipWithConserve(paramBoundss)(checkStarKind) + def checkKindRank(arg: Tree, paramBounds: TypeBounds)(implicit ctx: Context): Tree = { + def kindOK(argType: Type, boundType: Type): Boolean = + !argType.isHK || + boundType.isHK && kindOK(argType.resultType, boundType.resultType) + if (kindOK(arg.tpe, paramBounds.hi)) arg + else errorTree(arg, em"${arg.tpe} takes type parameters") + } + + def checkKindRanks(args: List[Tree], paramBoundss: List[TypeBounds])(implicit ctx: Context): List[Tree] = { + val args1 = args.zipWithConserve(paramBoundss)(checkKindRank) 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. diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 584066ebdfe4..463b184d7d3f 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -14,7 +14,7 @@ import NameOps._ import collection.mutable import reporting.diagnostic.Message import reporting.diagnostic.messages._ -import Checking.{checkStarKind, checkStarKinds, checkNoPrivateLeaks} +import Checking.{checkKindRank, checkKindRanks, checkNoPrivateLeaks} trait TypeAssigner { import tpd._ @@ -358,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) = checkStarKind(arg, paramBoundsByName(name.asTypeName)).tpe + namedArgMap(name) = checkKindRank(arg, paramBoundsByName(name.asTypeName)).tpe // Holds indexes of non-named typed arguments in paramNames val gapBuf = new mutable.ListBuffer[Int] @@ -391,7 +391,7 @@ trait TypeAssigner { } } else { - val argTypes = checkStarKinds(args, pt.paramInfos).tpes + val argTypes = checkKindRanks(args, pt.paramInfos).tpes if (sameLength(argTypes, paramNames) || ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes) else wrongNumberOfTypeArgs(fn.tpe, pt.typeParams, args, tree.pos) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4bfe6b4925a1..0f4aa90e7342 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1140,7 +1140,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]] } - val args2 = checkStarKinds(args1, tparams.map(_.paramInfo.bounds)) + val args2 = checkKindRanks(args1, tparams.map(_.paramInfo.bounds)) // check that arguments conform to bounds is done in phase PostTyper assignType(cpy.AppliedTypeTree(tree)(tpt1, args2), tpt1, args2) } diff --git a/tests/neg/i2771b.scala b/tests/neg/i2771b.scala new file mode 100644 index 000000000000..04c00d052e9e --- /dev/null +++ b/tests/neg/i2771b.scala @@ -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 takes type parameters + f(new B { type L[F[_[_]]] = F[F] })(1) // error: F takes type parameters + } +} From 757cf3fdf98e59d48cb34d514518ee96eb0c7ce3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 18 Jul 2017 10:46:18 +0200 Subject: [PATCH 7/9] Refine checkKindRank Previous way of taking the result type was too naive. --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index bb6a08c9c10d..4be42578d18c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -110,9 +110,14 @@ object Checking { * A test case is neg/i2771.scala. */ def checkKindRank(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 = !argType.isHK || - boundType.isHK && kindOK(argType.resultType, boundType.resultType) + boundType.isHK && kindOK(result(argType), result(boundType)) if (kindOK(arg.tpe, paramBounds.hi)) arg else errorTree(arg, em"${arg.tpe} takes type parameters") } From 5c5d155e94bc6be71d85602f460b44106f89fd13 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 18 Jul 2017 13:06:07 +0200 Subject: [PATCH 8/9] Refine kind checking Need to also check parameter kinds of higher-kinded types, not just result kinds. --- .../tools/dotc/core/TypeApplications.scala | 21 ++++++----- .../src/dotty/tools/dotc/typer/Checking.scala | 36 +++++++++++-------- .../dotty/tools/dotc/typer/TypeAssigner.scala | 6 ++-- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index b37ed2b0af8b..dfbeef4e2ddc 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -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 */ diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 4be42578d18c..39faed0412ee 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -100,30 +100,38 @@ object Checking { checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply)) } - /** Check that the rank of the kind of `arg` does not exceed the rank of the - * kind of `paramBounds`. E.g. if `paramBounds` has *-kind, `arg` must have - * *-kind as well, and analogously for higher kinds. - * More detailed kind checking is done as part of checkBounds in PostTyper. - * The purpose of checkKindRank is to do a rough test earlier in Typer, + /** 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 stackoverflows. - * A test case is neg/i2771.scala. + * 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 checkKindRank(arg: Tree, paramBounds: TypeBounds)(implicit ctx: Context): Tree = { + 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 = - !argType.isHK || - boundType.isHK && kindOK(result(argType), result(boundType)) + 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} takes type parameters") + else errorTree(arg, em"${arg.tpe} has wrong kind") } - def checkKindRanks(args: List[Tree], paramBoundss: List[TypeBounds])(implicit ctx: Context): List[Tree] = { - val args1 = args.zipWithConserve(paramBoundss)(checkKindRank) + 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. diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 463b184d7d3f..3a330b3f041d 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -14,7 +14,7 @@ import NameOps._ import collection.mutable import reporting.diagnostic.Message import reporting.diagnostic.messages._ -import Checking.{checkKindRank, checkKindRanks, checkNoPrivateLeaks} +import Checking.{preCheckKind, preCheckKinds, checkNoPrivateLeaks} trait TypeAssigner { import tpd._ @@ -358,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) = checkKindRank(arg, paramBoundsByName(name.asTypeName)).tpe + namedArgMap(name) = preCheckKind(arg, paramBoundsByName(name.asTypeName)).tpe // Holds indexes of non-named typed arguments in paramNames val gapBuf = new mutable.ListBuffer[Int] @@ -391,7 +391,7 @@ trait TypeAssigner { } } else { - val argTypes = checkKindRanks(args, pt.paramInfos).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) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0f4aa90e7342..cb73d37d34e5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1140,7 +1140,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]] } - val args2 = checkKindRanks(args1, tparams.map(_.paramInfo.bounds)) + val args2 = preCheckKinds(args1, tparams.map(_.paramInfo.bounds)) // check that arguments conform to bounds is done in phase PostTyper assignType(cpy.AppliedTypeTree(tree)(tpt1, args2), tpt1, args2) } From 567b91ff4ef57f9e3669f2a7db38010aa9afd71a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 18 Jul 2017 13:12:58 +0200 Subject: [PATCH 9/9] Refine tests --- tests/neg/i2771.scala | 8 ++++---- tests/neg/i2771b.scala | 4 ++-- tests/neg/i2771c.scala | 11 +++++++++++ 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 tests/neg/i2771c.scala diff --git a/tests/neg/i2771.scala b/tests/neg/i2771.scala index 1ba796b0443b..500f77919d23 100644 --- a/tests/neg/i2771.scala +++ b/tests/neg/i2771.scala @@ -5,8 +5,8 @@ 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 takes type parameters - f(new B { type L[F[_]] = F[F] })(1) // error: F takes type parameters (follow-on-error) + 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[_]] @@ -17,8 +17,8 @@ object Test { foo[Int]() // an error would be raised later, during PostTyper. def bar[X, Y]() = () - bar[List, Int]() // error: List takes type parameters + bar[List, Int]() // error: List has wrong kind - bar[Y = List, X = Int]() // error: List takes type parameters + bar[Y = List, X = Int]() // error: List has wrong kind } diff --git a/tests/neg/i2771b.scala b/tests/neg/i2771b.scala index 04c00d052e9e..802da51c0e8e 100644 --- a/tests/neg/i2771b.scala +++ b/tests/neg/i2771b.scala @@ -5,7 +5,7 @@ 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 takes type parameters - f(new B { type L[F[_[_]]] = F[F] })(1) // error: F takes type parameters + 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 } } diff --git a/tests/neg/i2771c.scala b/tests/neg/i2771c.scala new file mode 100644 index 000000000000..e83d393d807c --- /dev/null +++ b/tests/neg/i2771c.scala @@ -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 + } +}