From fd6054e09d54fc2b00f3447fdc23b418b247f64f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Apr 2018 13:18:55 +0200 Subject: [PATCH 1/4] Clean up kindedness tests We currently mostly use isLambdaSub which detects unapplied abstract and alias types but not unapplied generic classes. We should use `hasHigherKind` more often, which includes unapplied classes. The first commit fixes a problem with `hasHigherKind` where abstract types upper bounded by `AnyKind` where not classified as higher-kinded. It also changes checkSimpleKinded to use hasHigherKind instead if isLambdaSub. --- .../dotty/tools/dotc/core/TypeApplications.scala | 5 ++++- compiler/src/dotty/tools/dotc/core/Types.scala | 14 ++++++++++++++ compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 53b5bede376b..de5f663f9221 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -212,7 +212,10 @@ class TypeApplications(val self: Type) extends AnyVal { /** Is self type of kind != "*"? */ def hasHigherKind(implicit ctx: Context): Boolean = - typeParams.nonEmpty || self.isRef(defn.AnyKindClass) + typeParams.nonEmpty || self.hasAnyKind + + /** Is self type of kind "*"? */ + def hasFirstKind(implicit ctx: Context): Boolean = !hasHigherKind /** If self type is higher-kinded, its result type, otherwise NoType. * Note: The hkResult of an any-kinded type is again AnyKind. diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3de52cb16f87..8d81d0f72b7b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -248,6 +248,20 @@ object Types { /** Is this type a (possibly aliased) singleton type? */ def isSingleton(implicit ctx: Context) = dealias.isInstanceOf[SingletonType] + /** Is this type of kind `AnyKind`? */ + def hasAnyKind(implicit ctx: Context): Boolean = { + @tailrec def loop(tp: Type): Boolean = tp match { + case tp: TypeRef => + val sym = tp.symbol + if (sym.isClass) sym == defn.AnyKindClass else loop(tp.superType) + case tp: TypeProxy => + loop(tp.underlying) + case _ => + false + } + loop(this) + } + /** Is this type guaranteed not to have `null` as a value? */ final def isNotNull(implicit ctx: Context): Boolean = this match { case tp: ConstantType => tp.value.value != null diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 291d3102a235..1d702c7ed4c7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -704,7 +704,7 @@ trait Checking { /** Check that `tpt` does not define a higher-kinded type */ def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = - if (tpt.tpe.isLambdaSub && !ctx.compilationUnit.isJava) { + if (tpt.tpe.hasHigherKind && !ctx.compilationUnit.isJava) { // be more lenient with missing type params in Java, // needed to make pos/java-interop/t1196 work. errorTree(tpt, MissingTypeParameterFor(tpt.tpe)) From 7ce3ea0f49be97e8f17e83017ea1cc5027d6ddb1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Apr 2018 14:59:46 +0200 Subject: [PATCH 2/4] Tighten checkBounds Also disallow unapplied classes if bounds are first-kinded. This broke the Child annotation which should really be kind polymorphic. Fixed for now by making a special exception for Child. --- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 5 ++++- compiler/src/dotty/tools/dotc/typer/Checking.scala | 7 ++++++- library/src/scala/annotation/internal/Child.scala | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 103afdad960a..534ab3434cfd 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -213,7 +213,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } case tree: TypeApply => val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) - Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType]) + if (fn.symbol != defn.ChildAnnot.primaryConstructor) { + // Make an exception for ChildAnnot, which should really have AnyKind bounds + Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType]) + } fn match { case sel: Select => val args1 = transform(args) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 1d702c7ed4c7..8dcf059e88e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -46,9 +46,14 @@ object Checking { */ def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): Unit = { (args, boundss).zipped.foreach { (arg, bound) => - if (!bound.isLambdaSub && arg.tpe.isLambdaSub) + if (!bound.isLambdaSub && arg.tpe.hasHigherKind) { // see MissingTypeParameterFor + if (!arg.tpe.isLambdaSub) { // FIXME: Provisional, remove + println(i"different for checkBounds $arg vs $bound at ${ctx.phase} in ${ctx.owner.ownersIterator.toList}") + throw new AssertionError("") + } ctx.error(ex"missing type parameter(s) for $arg", arg.pos) + } } for ((arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate)) ctx.error( diff --git a/library/src/scala/annotation/internal/Child.scala b/library/src/scala/annotation/internal/Child.scala index c90871945dc4..208f471135d5 100644 --- a/library/src/scala/annotation/internal/Child.scala +++ b/library/src/scala/annotation/internal/Child.scala @@ -12,5 +12,6 @@ import scala.annotation.Annotation * Then the class symbol `A` would carry the annotations * `@Child[Bref] @Child[Cref]` where `Bref`, `Cref` are TypeRefs * referring to the class symbols of `B` and `C` + * TODO: This should be `Child[T <: AnyKind]` */ class Child[T] extends Annotation From 64f2333c6a6dcee5885dde2f8d037ba8f40290a8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Apr 2018 18:19:59 +0200 Subject: [PATCH 3/4] Use hasHigherKind in accessibleSelectionType test --- compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 2a9027aaa26d..5edf8b465d38 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -255,7 +255,9 @@ trait TypeAssigner { */ def accessibleSelectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = { var qualType = qual1.tpe.widenIfUnstable - if (qualType.isLambdaSub) qualType = errorType(em"$qualType takes type parameters", qual1.pos) + if (qualType.hasHigherKind && tree.name != nme.CONSTRUCTOR) + // constructors are selected on typeconstructor, type arguments are passed afterwards + qualType = errorType(em"$qualType takes type parameters", qual1.pos) else if (!qualType.isInstanceOf[TermType]) qualType = errorType(em"$qualType is illegal as a selection prefix", qual1.pos) val ownType = selectionType(qualType, tree.name, tree.pos) ensureAccessible(ownType, qual1.isInstanceOf[Super], tree.pos) From 24704553f661b316cb78c79b599a59716d2b669a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Apr 2018 18:36:47 +0200 Subject: [PATCH 4/4] Replace hasHigherKind by hasSimpleKind hasHigherKind is misleading since it will cover AnyKind and Effect as well and it is not clear how these are "higher" kinds. --- compiler/src/dotty/tools/dotc/core/TypeApplications.scala | 7 ++----- compiler/src/dotty/tools/dotc/typer/Checking.scala | 8 ++------ compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index de5f663f9221..83188406d6a1 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -210,12 +210,9 @@ class TypeApplications(val self: Type) extends AnyVal { /** Is self type bounded by a type lambda or AnyKind? */ def isLambdaSub(implicit ctx: Context): Boolean = hkResult.exists - /** Is self type of kind != "*"? */ - def hasHigherKind(implicit ctx: Context): Boolean = - typeParams.nonEmpty || self.hasAnyKind - /** Is self type of kind "*"? */ - def hasFirstKind(implicit ctx: Context): Boolean = !hasHigherKind + def hasSimpleKind(implicit ctx: Context): Boolean = + typeParams.isEmpty && !self.hasAnyKind /** If self type is higher-kinded, its result type, otherwise NoType. * Note: The hkResult of an any-kinded type is again AnyKind. diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 8dcf059e88e7..1b39859089e3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -46,12 +46,8 @@ object Checking { */ def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): Unit = { (args, boundss).zipped.foreach { (arg, bound) => - if (!bound.isLambdaSub && arg.tpe.hasHigherKind) { + if (!bound.isLambdaSub && !arg.tpe.hasSimpleKind) { // see MissingTypeParameterFor - if (!arg.tpe.isLambdaSub) { // FIXME: Provisional, remove - println(i"different for checkBounds $arg vs $bound at ${ctx.phase} in ${ctx.owner.ownersIterator.toList}") - throw new AssertionError("") - } ctx.error(ex"missing type parameter(s) for $arg", arg.pos) } } @@ -709,7 +705,7 @@ trait Checking { /** Check that `tpt` does not define a higher-kinded type */ def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = - if (tpt.tpe.hasHigherKind && !ctx.compilationUnit.isJava) { + if (!tpt.tpe.hasSimpleKind && !ctx.compilationUnit.isJava) { // be more lenient with missing type params in Java, // needed to make pos/java-interop/t1196 work. errorTree(tpt, MissingTypeParameterFor(tpt.tpe)) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 5edf8b465d38..9bb608be7736 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -255,7 +255,7 @@ trait TypeAssigner { */ def accessibleSelectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = { var qualType = qual1.tpe.widenIfUnstable - if (qualType.hasHigherKind && tree.name != nme.CONSTRUCTOR) + if (!qualType.hasSimpleKind && tree.name != nme.CONSTRUCTOR) // constructors are selected on typeconstructor, type arguments are passed afterwards qualType = errorType(em"$qualType takes type parameters", qual1.pos) else if (!qualType.isInstanceOf[TermType]) qualType = errorType(em"$qualType is illegal as a selection prefix", qual1.pos)