From 74ca079e4cff0566cc2c8797bf52a70a82fcedc9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Mar 2018 15:07:29 +0100 Subject: [PATCH 01/16] Add Any-kinded types Allow a form of type polymorphism by adding an AnyKind upper bound. Types of any kind are subtypes of `AnyKind`. The "any-kinded types" are `AnyKind` itself, and all type parameters and abstract types having it as an upper bound. Any-kinded types cannot be used as types of values, nor can they be applied to type parameters. About the only thing one can do with them is use them as parameters for implicit searches. --- .../dotty/tools/dotc/core/Definitions.scala | 4 + .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/core/TypeApplications.scala | 34 +++++--- .../dotty/tools/dotc/core/TypeComparer.scala | 24 ++++-- .../dotc/reporting/diagnostic/messages.scala | 4 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/neg/anykind.scala | 23 +++++ tests/neg/anykind2.scala | 20 +++++ tests/pos/kindPolySemiGroup.scala | 85 +++++++++++++++++++ 9 files changed, 177 insertions(+), 20 deletions(-) create mode 100644 tests/neg/anykind.scala create mode 100644 tests/neg/anykind2.scala create mode 100644 tests/pos/kindPolySemiGroup.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 75a7b560945c..f5622f27d0ca 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -304,6 +304,9 @@ class Definitions { def ObjectMethods = List(Object_eq, Object_ne, Object_synchronized, Object_clone, Object_finalize, Object_notify, Object_notifyAll, Object_wait, Object_waitL, Object_waitLI) + lazy val AnyKindClass = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal, Nil)) + def AnyKindType = AnyKindClass.typeRef + /** Marker method to indicate an argument to a call-by-name parameter. * Created by byNameClosures and elimByName, eliminated by Erasure, */ @@ -1158,6 +1161,7 @@ class Definitions { lazy val syntheticScalaClasses = List( AnyClass, AnyRefAlias, + AnyKindClass, RepeatedParamClass, ByNameParamClass2x, AnyValClass, diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 1e64ff94d872..3ba81450301e 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -196,6 +196,7 @@ object StdNames { final val REIFY_TYPECREATOR_PREFIX: N = "$typecreator" final val Any: N = "Any" + final val AnyKind: N = "AnyKind" final val AnyVal: N = "AnyVal" final val ExprApi: N = "ExprApi" final val Mirror: N = "Mirror" diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 0eefd44b109e..07eaa22e91c0 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -207,12 +207,17 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => Nil } - /** Is self type higher-kinded (i.e. of kind != "*")? */ + /** Is self type higher-kinded (i.e. of kind != "*") + * or any-kinded (i.e. has AnyKind as upper bound)? + */ def isHK(implicit ctx: Context): Boolean = hkResult.exists - /** If self type is higher-kinded, its result type, otherwise NoType */ + /** If self type is higher-kinded, its result type, otherwise NoType. + * Note: The hkResult of an any-kinded type is again AnyKind. + */ def hkResult(implicit ctx: Context): Type = self.dealias match { - case self: TypeRef => self.info.hkResult + case self: TypeRef => + if (self.symbol == defn.AnyKindClass) self else self.info.hkResult case self: AppliedType => if (self.tycon.typeSymbol.isClass) NoType else self.superType.hkResult case self: HKTypeLambda => self.resultType @@ -226,17 +231,24 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => NoType } - /** Do self and other have the same kinds (not counting bounds and variances) */ + /** Do self and other have the same kinds (not counting bounds and variances)? + * Note: An any-kinded type "has the same kind" as any other type. + */ def hasSameKindAs(other: Type)(implicit ctx: Context): Boolean = { - // println(i"check kind $self $other") // DEBUG + def isAnyKind(tp: Type) = tp match { + case tp: TypeRef => tp.symbol == defn.AnyKindClass + case _ => false + } val selfResult = self.hkResult val otherResult = other.hkResult - if (selfResult.exists) - otherResult.exists && - selfResult.hasSameKindAs(otherResult) && - self.typeParams.corresponds(other.typeParams)((sparam, oparam) => - sparam.paramInfo.hasSameKindAs(oparam.paramInfo)) - else !otherResult.exists + isAnyKind(selfResult) || isAnyKind(otherResult) || + { if (selfResult.exists) + otherResult.exists && + selfResult.hasSameKindAs(otherResult) && + self.typeParams.corresponds(other.typeParams)((sparam, oparam) => + sparam.paramInfo.hasSameKindAs(oparam.paramInfo)) + else !otherResult.exists + } } /** Dealias type if it can be done without forcing the TypeRef's info */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 2611d96df182..36d8c42494bb 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -48,16 +48,22 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { private[this] var totalCount = 0 private[this] var myAnyClass: ClassSymbol = null + private[this] var myAnyKindClass: ClassSymbol = null private[this] var myNothingClass: ClassSymbol = null private[this] var myNullClass: ClassSymbol = null private[this] var myObjectClass: ClassSymbol = null private[this] var myAnyType: TypeRef = null + private[this] var myAnyKindType: TypeRef = null private[this] var myNothingType: TypeRef = null def AnyClass = { if (myAnyClass == null) myAnyClass = defn.AnyClass myAnyClass } + def AnyKindClass = { + if (myAnyKindClass == null) myAnyKindClass = defn.AnyKindClass + myAnyKindClass + } def NothingClass = { if (myNothingClass == null) myNothingClass = defn.NothingClass myNothingClass @@ -74,6 +80,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (myAnyType == null) myAnyType = AnyClass.typeRef myAnyType } + def AnyKindType = { + if (myAnyKindType == null) myAnyKindType = AnyKindClass.typeRef + myAnyKindType + } def NothingType = { if (myNothingType == null) myNothingType = NothingClass.typeRef myNothingType @@ -367,8 +377,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case _ => val cls2 = tp2.symbol if (cls2.isClass) { - if (cls2.typeParams.nonEmpty && tp1.isHK) - recur(tp1, EtaExpansion(cls2.typeRef)) + if (cls2 eq AnyKindClass) + return true else { val base = tp1.baseType(cls2) if (base.exists) { @@ -540,7 +550,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { def compareTypeBounds = tp1 match { case tp1 @ TypeBounds(lo1, hi1) => ((lo2 eq NothingType) || isSubType(lo2, lo1)) && - ((hi2 eq AnyType) || isSubType(hi1, hi2)) + ((hi2 eq AnyType) || (hi2 eq AnyKindType) || isSubType(hi1, hi2)) case tp1: ClassInfo => tp2 contains tp1 case _ => @@ -1216,8 +1226,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (tp1 eq tp2) tp1 else if (!tp1.exists) tp2 else if (!tp2.exists) tp1 - else if ((tp1 isRef AnyClass) || (tp2 isRef NothingClass)) tp2 - else if ((tp2 isRef AnyClass) || (tp1 isRef NothingClass)) tp1 + else if ((tp1 isRef AnyClass) || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) tp2 + else if ((tp2 isRef AnyClass) || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) tp1 else tp2 match { // normalize to disjunctive normal form if possible. case OrType(tp21, tp22) => tp1 & tp21 | tp1 & tp22 @@ -1265,8 +1275,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (tp1 eq tp2) tp1 else if (!tp1.exists) tp1 else if (!tp2.exists) tp2 - else if ((tp1 isRef AnyClass) || (tp2 isRef NothingClass)) tp1 - else if ((tp2 isRef AnyClass) || (tp1 isRef NothingClass)) tp2 + else if ((tp1 isRef AnyClass) || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) tp1 + else if ((tp2 isRef AnyClass) || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) tp2 else { val t1 = mergeIfSuper(tp1, tp2, canConstrain) if (t1.exists) t1 diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 088b121da737..42ee9cb34db2 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1462,7 +1462,9 @@ object messages { case class MissingTypeParameterFor(tpe: Type)(implicit ctx: Context) extends Message(MissingTypeParameterForID) { - val msg = hl"missing type parameter for ${tpe}" + val msg = + if (tpe.derivesFrom(defn.AnyKindClass)) hl"${tpe} cannot be used as a value type" + else hl"missing type parameter for ${tpe}" val kind = "Syntax" val explanation = "" } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 754ccddefed2..6a397f4a5c5b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2147,7 +2147,7 @@ class Typer extends Namer def adaptNoArgsImplicitMethod(wtp: MethodType): Tree = { assert(wtp.isImplicitMethod) - val tvarsToInstantiate = tvarsInParams(tree, locked) + val tvarsToInstantiate = tvarsInParams(tree, locked).distinct wtp.paramInfos.foreach(instantiateSelected(_, tvarsToInstantiate)) val constr = ctx.typerState.constraint diff --git a/tests/neg/anykind.scala b/tests/neg/anykind.scala new file mode 100644 index 000000000000..ea7c33baa6a4 --- /dev/null +++ b/tests/neg/anykind.scala @@ -0,0 +1,23 @@ +object AnyKinds { + + val x: AnyKind = 2 // error + + def f[X <: AnyKind]: Any = { + type T0 = AnyKind[String] // error + type T1 = X[Int] // error + type T2 = X { type F = Int } // error + val x: X = ??? // error + } + + f[Int] // OK + f[[X] => X] // OK + f[Nothing] // OK + + def g[X <: Any]: Any = { + f[X] // OK + } + g[Int] // OK + g[Nothing] // OK + + +} \ No newline at end of file diff --git a/tests/neg/anykind2.scala b/tests/neg/anykind2.scala new file mode 100644 index 000000000000..c9bc4faa4b00 --- /dev/null +++ b/tests/neg/anykind2.scala @@ -0,0 +1,20 @@ +object AnyKinds { + + def f[X <: AnyKind]: Any = { + g[X] // error + g[AnyKind] // error + } + + f[Int] // OK + f[[X] => X] // OK + f[Nothing] // OK + + def g[X <: Any]: Any = { + f[X] // OK + } + g[Int] // OK + g[List] // error + g[Nothing] // OK + + +} \ No newline at end of file diff --git a/tests/pos/kindPolySemiGroup.scala b/tests/pos/kindPolySemiGroup.scala new file mode 100644 index 000000000000..8ee2db278874 --- /dev/null +++ b/tests/pos/kindPolySemiGroup.scala @@ -0,0 +1,85 @@ +// Adapted from github:mandubian/kind-polymorphic-semigroup.scala +sealed trait HList +case class HCons[+HD, +TL](hd: HD, tl: TL) extends HList +case object HNil extends HList + +object Test { + + type HNil = HNil.type + + // Kind Extractor + trait Kinder[MA] { type M <: AnyKind } + object Kinder extends KinderLowerImplicits { + type Aux[MA, M0 <: AnyKind] = Kinder[MA] { type M = M0 } + + implicit def kinder1[M0[_], A0]: Kinder.Aux[M0[A0], M0] = + new Kinder[M0[A0]] { type M[t] = M0[t] } + implicit def kinder2[M0[_, _], A0, B0]: Kinder.Aux[M0[A0, B0], M0] = + new Kinder[M0[A0, B0]] { type M[t, u] = M0[t, u]; type Args = HCons[A0, HCons[B0, HNil]] } + } + trait KinderLowerImplicits { + implicit def kinder0[A]: Kinder.Aux[A, A] = new Kinder[A] { type M = A; type Args = HNil } + } + + // Kind Polymorphic Semigroup using shapeless "Polymorphic function"-style + trait SemiGroup[M <: AnyKind] { + // Just a mirror type of itself to ensure the owning of AppendFunction... + type Self + // the function accepting only monomorphic type MA allowed by this scoped Semigroup AppendFunction + def append[MA](m1: MA, m2: MA)(implicit appender: SemiGroup.AppendFunction[Self, MA, M]) = appender(m1, m2) + } + + object SemiGroup { + type Aux[M <: AnyKind, Self0] = SemiGroup[M] { type Self = Self0 } + + // the monomorphic append function (yes we need to reify monomorphic types sometimes) + trait AppendFunction[P, FA, F <: AnyKind] { + def apply(m1: FA, m2: FA): FA + } + } + + // Int SemiGroup instance + implicit object SemiGroupInt extends SemiGroup[Int] { + type Self = this.type + implicit val appender: SemiGroup.AppendFunction[Self, Int, Int]= new SemiGroup.AppendFunction[Self, Int, Int] { + def apply(m1: Int, m2: Int) = m1 + m2 + } + } + + // List SemiGroup instance + implicit object SemiGroupList extends SemiGroup[List] { + type Self = this.type + implicit def appender[A]: SemiGroup.AppendFunction[Self, List[A], List] = new { + def apply(m1: List[A], m2: List[A]) = m1 ++ m2 + } + } + + // Map SemiGroup instance + implicit object SemiGroupMap extends SemiGroup[Map] { + type Self = this.type + implicit def appender[A, B]: SemiGroup.AppendFunction[Self, Map[A, B], Map] = new { + def apply(m1: Map[A, B], m2: Map[A, B]) = m1 ++ m2 + } + } + + // Searching a semigroup and using it + def semiGroup[M <: AnyKind](implicit sg: SemiGroup[M]): SemiGroup.Aux[M, sg.Self] = sg + + semiGroup[Int].append(5, 8) + semiGroup[List].append(List(1), List(3)) + semiGroup[Map].append(Map("toto" -> 1L), Map("tata" -> 3L)) + + // higher level append function + def append[MA, M <: AnyKind, Self](m1: MA, m2: MA)( + implicit kinder: Kinder.Aux[MA, M], semiGroup: SemiGroup.Aux[M, Self], appender: SemiGroup.AppendFunction[Self, MA, M] + ): MA = semiGroup.append(m1, m2) + + import SemiGroupList.appender + import SemiGroupMap.appender + + val r1: Int = append(5, 8) + + // TODO: Figure igure out why `M` below cannot be inferred + val r2: List[Int] = append[M = List](List(1), List(3)) + val r3: Map[String, Long] = append[M = Map](Map("toto" -> 1L), Map("tata" -> 3L)) +} \ No newline at end of file From c825eb5f3d32d7e848ed39fcaa55dfe07598b039 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Mar 2018 19:16:34 +0100 Subject: [PATCH 02/16] Add docs --- docs/docs/reference/kind-polymorphism.md | 43 ++++++++++++++++++++++++ docs/sidebar.yml | 2 ++ 2 files changed, 45 insertions(+) create mode 100644 docs/docs/reference/kind-polymorphism.md diff --git a/docs/docs/reference/kind-polymorphism.md b/docs/docs/reference/kind-polymorphism.md new file mode 100644 index 000000000000..9c375c8e3a07 --- /dev/null +++ b/docs/docs/reference/kind-polymorphism.md @@ -0,0 +1,43 @@ +--- +layout: doc-page +title: "Kind Polymorphism" +--- + +Normally type parameters in Scala are partitioned into _kinds_. First-level types are types of values. Higher-kinded types are type constructurs +such as `List` or `Map`. The kind of a type is indicated by the top type of which it is a subtype. Normal types are subtypes of `Any`, +covariant single argument type constructors such as `List` are subtypes of `[+X] => Any`, and the `Map` type constructor is +a subtype of `[X, +Y] => Any`. + +A type can be used only as prescribed by its kind. Subtypes of `Any` cannot be applied to type arguments whereas subtypes of `[X] => Any` +_must_ be applied to a type argument, unless they are passed to type parameters of the same kind. + +Sometimes we would like to have type parameters that can have more than one kind, for instance to define an implicit +value that works for parameters of any kind. This is now possible through a form of (_subtype_) kind polymorphism. +Kind polymorphism relies on the special type `scala.AnyKind` that can be used as an upper bound of a type. + +```scala +def f[T <: AnyKind] = ... +``` + +The actual type arguments of `f` can then be types of arbitrary kinds. So the following would all be legal: + +```scala +f[Int] +f[List] +f[Map] +f[[X] => String] +``` + +We call type parameters and abstract types with an `AnyKind` upper bound _any-kinded types_`. +Since the actual kind of an any-kinded type is unknown, its usage must be heavily restricted: An any-kinded type +can be neither the type of a value, nor can it be instantiated with type parameters. So about the only +thing one can do with an any-kinded type is to pass it to another any-kinded type argument. +Nevertheless, this is enough to achieve some interesting generalizations that work across kinds, typicially +through advanced uses of implicits. + +(todo: insert good concise example) + +Some technical details: `AnyKind` is a synthesized class just like `Any`, but without any members. It extends no other class. +It is declared `abstract` and `final`, so it can be neither instantiated nor extended. + +`AnyKind` plays a special role in Scala's subtype system: It is a supertype of all other types no matter what their kind is. It is also assumed to be kind-compatible with all other types. Furthermore, `AnyKind` is treated as a higher-kinded type (so it cannot be used as a type of values), but at the same time it has no type parameters (so it cannot be instantiated). \ No newline at end of file diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 2aed6909d5ef..ca456b11af65 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -55,6 +55,8 @@ sidebar: url: docs/reference/named-typeargs.html - title: Erased Terms url: docs/reference/erased-terms.html + - title: Kind Polymorphism + url: docs/reference/kind-polymorphism.html - title: Local Optimisations url: docs/reference/optimisations.html - title: Changed Features From 321b616671eeadaf6f49c17b41618f2c3c7776ff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Mar 2018 19:43:48 +0100 Subject: [PATCH 03/16] More negative tests These show that the technique used in mandubian/any-order-ubiklist.scala does not work here, since intersections are only allowed over *-types and AnyKind is not a *-type. --- tests/neg/anykind.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/neg/anykind.scala b/tests/neg/anykind.scala index ea7c33baa6a4..c79d09c30a48 100644 --- a/tests/neg/anykind.scala +++ b/tests/neg/anykind.scala @@ -6,6 +6,8 @@ object AnyKinds { type T0 = AnyKind[String] // error type T1 = X[Int] // error type T2 = X { type F = Int } // error + type T3 <: List & AnyKind // error // error + type T4 <: Int & AnyKind // error val x: X = ??? // error } From 1bb183a8e0e562d9b2cae318f7e817a6ebccdfbe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Mar 2018 20:36:48 +0100 Subject: [PATCH 04/16] Fix regression in TypeComparer So we could not delete the two lines that never returned a result. It turns out they were necessary for their effect on the constraint. If they are removed, i3422.scala fails. --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 36d8c42494bb..65b6d4420810 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -379,6 +379,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (cls2.isClass) { if (cls2 eq AnyKindClass) return true + else if (cls2.typeParams.nonEmpty && tp1.isHK) + return recur(tp1, EtaExpansion(cls2.typeRef)) else { val base = tp1.baseType(cls2) if (base.exists) { From a020409ec3dcb24708e94b4c7d8cc9e58f8626ab Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Mar 2018 16:49:35 +0100 Subject: [PATCH 05/16] Harden tryReduce When compiling kind-incorrect code in neg/anykind3.scala, substParams fails with an index-out-bounds exception. It is called from tryReduce via instantiate. We know there are several scenarios where this can happen - Sandro has detected a couple of others. This commit avoids the problem by aborting the reduce if there is an index out of bounds. --- compiler/src/dotty/tools/dotc/core/TypeApplications.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 07eaa22e91c0..ef7e77515456 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -367,7 +367,9 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => false } } - if ((dealiased eq stripped) || followAlias) dealiased.instantiate(args) + if ((dealiased eq stripped) || followAlias) + try dealiased.instantiate(args) + catch { case ex: IndexOutOfBoundsException => AppliedType(self, args) } else AppliedType(self, args) } else dealiased.resType match { From 826c1de4ad2608fc2d88af234c29f9417dfff7b8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Mar 2018 16:53:01 +0100 Subject: [PATCH 06/16] Avoid generating kind-incorrect types for wildcard arguments. When doing the changes to higher-kinded types, an error popped up in pos/i3976.scala that a wildcard argument `_` was illegal because the corresponding type parameter is higher-kinded. This seemed to have been masked by an incorrect subtype check before. We now use the parameter bounds as the argument in this case. --- .../src/dotty/tools/dotc/typer/Typer.scala | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6a397f4a5c5b..a367a81a2e00 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1233,7 +1233,24 @@ class Typer extends Namer tparam.ensureCompleted() // This is needed to get the test `compileParSetSubset` to work case _ => } - if (desugaredArg.isType) typed(desugaredArg, argPt) + if (desugaredArg.isType) { + var res = typed(desugaredArg, argPt) + arg match { + case TypeBoundsTree(EmptyTree, EmptyTree) + if tparam.paramInfo.isHK && + tpt1.tpe.typeParamSymbols.nonEmpty && + !ctx.mode.is(Mode.Pattern) => + // An unbounded `_` automatically adapts to type parameter bounds. This means: + // If we have wildcard application C[_], where `C` is a class replace + // with C[_ >: L <: H] where `L` and `H` are the bounds of the corresponding + // type parameter in `C`, avoiding any referemces to parameters of `C`. + // The transform does not apply for patters, where empty bounds translate to + // wildcard identifiers `_` instead. + res = res.withType(avoid(tparam.paramInfo, tpt1.tpe.typeParamSymbols)) + case _ => + } + res + } else desugaredArg.withType(UnspecifiedErrorType) } args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]] From 060e1de6d6fa61b2b53d55f150a29997e9b034ec Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Mar 2018 17:18:58 +0100 Subject: [PATCH 07/16] Update comparisons of higher-kinded types Several fixes to make sure that Any is a supertype only of * types. Before there were some types that slipped through the net. Also, new tests. --- .../tools/dotc/core/SymDenotations.scala | 2 +- .../dotty/tools/dotc/core/TypeComparer.scala | 39 ++-- tests/neg/anykind.scala | 1 + tests/neg/anykind1.scala | 11 + tests/neg/anykind2.scala | 1 + tests/neg/anykind3.scala | 63 +++++ tests/neg/anykind4.scala | 14 ++ tests/neg/i2492.scala | 4 - tests/pos/anykind.scala | 217 ++++++++++++++++++ 9 files changed, 327 insertions(+), 25 deletions(-) create mode 100644 tests/neg/anykind1.scala create mode 100644 tests/neg/anykind3.scala create mode 100644 tests/neg/anykind4.scala delete mode 100644 tests/neg/i2492.scala create mode 100644 tests/pos/anykind.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index d3db4845e8b3..c190dfb7262c 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -667,7 +667,7 @@ object SymDenotations { /** Is this symbol a class references to which that are supertypes of null? */ final def isNullableClass(implicit ctx: Context): Boolean = - isClass && !isValueClass && !(this is ModuleClass) && symbol != defn.NothingClass + isClass && !isValueClass && !is(ModuleClass) && symbol != defn.NothingClass /** Is this definition accessible as a member of tree with type `pre`? * @param pre The type of the tree from which the selection is made diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 65b6d4420810..7cc254f9ca36 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -377,21 +377,22 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case _ => val cls2 = tp2.symbol if (cls2.isClass) { - if (cls2 eq AnyKindClass) - return true - else if (cls2.typeParams.nonEmpty && tp1.isHK) - return recur(tp1, EtaExpansion(cls2.typeRef)) - else { + if (cls2.typeParams.isEmpty) { + if (cls2 eq AnyKindClass) return true + if (tp1.isRef(defn.NothingClass)) return true + if (tp1.isHK) return false val base = tp1.baseType(cls2) - if (base.exists) { - if (cls2.is(JavaDefined)) - // If `cls2` is parameterized, we are seeing a raw type, so we need to compare only the symbol - return base.typeSymbol == cls2 - if (base ne tp1) - return isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx.addLow) - } + if (base.exists && base.ne(tp1)) + return isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx.addLow) if (cls2 == defn.SingletonClass && tp1.isStable) return true } + else if (cls2.is(JavaDefined)) { + // If `cls2` is parameterized, we are seeing a raw type, so we need to compare only the symbol + val base = tp1.baseType(cls2) + if (base.typeSymbol == cls2) return true + } + else if (tp1.isHK && !tp1.isRef(defn.AnyKindClass)) + return recur(tp1, EtaExpansion(cls2.typeRef)) } fourthTry } @@ -487,13 +488,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { isSubType(tp1.resType, tp2.resType.subst(tp2, tp1)) finally comparedTypeLambdas = saved case _ => - if (tp1.isHK) { - val tparams1 = tp1.typeParams + val tparams1 = tp1.typeParams + if (tparams1.nonEmpty) return recur( HKTypeLambda.fromParams(tparams1, tp1.appliedTo(tparams1.map(_.paramRef))), - tp2 - ) - } + tp2) else tp2 match { case EtaExpansion(tycon2) if tycon2.symbol.isClass => return recur(tp1, tycon2) @@ -552,7 +551,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { def compareTypeBounds = tp1 match { case tp1 @ TypeBounds(lo1, hi1) => ((lo2 eq NothingType) || isSubType(lo2, lo1)) && - ((hi2 eq AnyType) || (hi2 eq AnyKindType) || isSubType(hi1, hi2)) + ((hi2 eq AnyType) && !hi1.isHK || (hi2 eq AnyKindType) || isSubType(hi1, hi2)) case tp1: ClassInfo => tp2 contains tp1 case _ => @@ -1228,8 +1227,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (tp1 eq tp2) tp1 else if (!tp1.exists) tp2 else if (!tp2.exists) tp1 - else if ((tp1 isRef AnyClass) || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) tp2 - else if ((tp2 isRef AnyClass) || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) tp1 + else if ((tp1 isRef AnyClass) && !tp2.isHK || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) tp2 + else if ((tp2 isRef AnyClass) && !tp1.isHK || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) tp1 else tp2 match { // normalize to disjunctive normal form if possible. case OrType(tp21, tp22) => tp1 & tp21 | tp1 & tp22 diff --git a/tests/neg/anykind.scala b/tests/neg/anykind.scala index c79d09c30a48..95f851fbb571 100644 --- a/tests/neg/anykind.scala +++ b/tests/neg/anykind.scala @@ -21,5 +21,6 @@ object AnyKinds { g[Int] // OK g[Nothing] // OK + trait X[F[_] <: AnyKind] { type L = F[Int]; def a: L = ??? } // error: cannot be used as a value type } \ No newline at end of file diff --git a/tests/neg/anykind1.scala b/tests/neg/anykind1.scala new file mode 100644 index 000000000000..2a75d1f42fae --- /dev/null +++ b/tests/neg/anykind1.scala @@ -0,0 +1,11 @@ +object Test { + + // Checking edge cases that should not compile with kind-polymorphism + trait X[F[_] <: AnyKind] { def a: Int } + new X[Int] { } // error + new X[Int] { }.a // error + new X[Either] { } // error + new X[Either] { }.a // error + new X[({ type l[X, Y] = X })#l] { } // error + +} diff --git a/tests/neg/anykind2.scala b/tests/neg/anykind2.scala index c9bc4faa4b00..19972a686226 100644 --- a/tests/neg/anykind2.scala +++ b/tests/neg/anykind2.scala @@ -16,5 +16,6 @@ object AnyKinds { g[List] // error g[Nothing] // OK + 1.asInstanceOf[AnyKind] // error } \ No newline at end of file diff --git a/tests/neg/anykind3.scala b/tests/neg/anykind3.scala new file mode 100644 index 000000000000..d374d2f841df --- /dev/null +++ b/tests/neg/anykind3.scala @@ -0,0 +1,63 @@ + +object Test { + + def f[X] = () + f[List] + + // Checking edge cases that should not compile with kind-polymorphism + + trait X[A <: AnyKind, F[_ <: AnyKind]] { type B = F[A] } + + val i0: X[Option, Double]#B = Some(5) // error + + val i1: X[Option, List]#B = Some(5) // error + + val i2: X[Option[Double], List]#B = Some(5) // error + + val i3: X[Option[Double], ({ type l[X[_]] = X[Int] })#l]#B = Some(5) // error + + val i4: X[Double, ({ type l[X[_]] = X[Int] })#l]#B = 5.0 // error + + val i6: X[Either, ({ type l[X[_]] = X[Int] })#l]#B = Some(5) // error + + val i7: X[Either, List]#B = Some(5) // error + + trait Foo[A[_[_]]] + val i8: X[Foo, ({ type l[X[_]] = X[Int] })#l]#B = Some(5) // error + + trait X2[A <: AnyKind, B <: AnyKind] { def run[F[_ <: AnyKind]]: F[A] => F[B] } + + val x21 = { + new X2[Int, Int] { def run[F[_]]: F[Int] => F[Int] = identity[F[Int]] } + .asInstanceOf[X2[List, Option[Double]]].run[({ type l[X[_]] = X[Int] })#l] + } + + val x22 = { + new X2[Int, Int] { def run[F[_]]: F[Int] => F[Int] = identity[F[Int]] } + .asInstanceOf[X2[List, Option[Double]]].run[List] + } + + trait X3[A <: AnyKind, B <: AnyKind, C <: AnyKind] { def run[F[_ <: AnyKind, _ <: AnyKind]]: F[A, C] => F[B, C] } + + val x31 = { + new X3[Int, Int, String] { def run[F[_, _]]: F[Int, String] => F[Int, String] = identity[F[Int, String]] } + .asInstanceOf[X3[Option[Double], List, String]].run[Map] + } + val x32 = { + new X3[Int, Int, String] { def run[F[_, _]]: F[Int, String] => F[Int, String] = identity[F[Int, String]] } + .asInstanceOf[X3[List, Option[Double], String]].run[Map] + } + + trait X4[A <: AnyKind, B <: AnyKind, C] { def run[F[_ <: AnyKind, _]]: F[A, C] => F[B, C] } + + trait Foo2[A] + trait Bar[A] + trait Bar2[A, B] + + trait Toto[F[_], A] + + val x41 = { + new X3[Foo2, Foo2, Int] { def run[F[_[_], A]]: F[Foo2, Int] => F[Foo2, Int] = identity[F[Foo2, Int]] } + .asInstanceOf[X3[Bar, Bar, Int]].run[Bar2] + } +} diff --git a/tests/neg/anykind4.scala b/tests/neg/anykind4.scala new file mode 100644 index 000000000000..274c79f775c1 --- /dev/null +++ b/tests/neg/anykind4.scala @@ -0,0 +1,14 @@ + +object Test { + + trait Foo[T <: AnyKind] + + def foo[T <: AnyKind](implicit f: Foo[T]): f.type = f + + implicit def c1[T]: Foo[T] = ??? + implicit def c2[T[_]]: Foo[T] = ??? + + foo[List](c1) // error + + foo[List](c2) +} \ No newline at end of file diff --git a/tests/neg/i2492.scala b/tests/neg/i2492.scala deleted file mode 100644 index d69a837f3f9d..000000000000 --- a/tests/neg/i2492.scala +++ /dev/null @@ -1,4 +0,0 @@ -class Map[K, V] -object Foo { - val s: Map {type Map$K = String; type Map$V = Int} = null // error -} diff --git a/tests/pos/anykind.scala b/tests/pos/anykind.scala new file mode 100644 index 000000000000..a8f8e2839218 --- /dev/null +++ b/tests/pos/anykind.scala @@ -0,0 +1,217 @@ +object Test { + + case class Bar[A](a: A) + trait Toto[A, B] + + //////////////////////////////////////////////// + // PoC of controlled KindPolymorphism in Scala + // + // The idea is NOT to provide universal kind-polymorphism that would be a bad idea anyway + // but to bring a "controlled" kind-polymorphism relying on accepted kinds defined by typeclass implicits + // Thus, kind-polymorphism is strictly scoped to your domain and is what you expect to be, nothing else. + // + // `Ykind-polymorphism` flag aims at deferring just a bit Scalac type inference when encountering AnyKind higher bounds + // without losing any strictness in the final typing. + // `<: AnyKind` type-bound is purely technicaland totally eliminated after erasure. There is not type associated to it. + // + // Here are code-samples that work now: + // - basic kind polymorphism controlled by implicits + // - Kindness proofs based on typeclasses (specially SameKind) + // - Kind-Polymorphic list (on type & value) (2 different implementations) + // - Some weird cases we don't want the compiler to authorize + + //////////////////////////////////////////////// + // Basic Kind polymorphism sample + trait Foo[T <: AnyKind] { type Out ; def id(t: Out): Out = t } + + object Foo { + implicit def foo0[T]: Foo[T] { type Out = T } = new Foo[T] { type Out = T } + implicit def foo1[T[_]]: Foo[T] { type Out = T[Any] } = new Foo[T] { type Out = T[Any] } + implicit def foo2[T[_, _]]: Foo[T] { type Out = T[Any, Any] } = new Foo[T] { type Out = T[Any, Any] } + } + + def foo[T <: AnyKind](implicit f: Foo[T]): f.type = f + foo[Int].id(23) + foo[List].id(List[Any](1, 2, 3)) + foo[Map].id(Map[Any, Any](1 -> "toto", 2 -> "tata", 3 -> "tutu")) + + //////////////////////////////////////////////// + // Is a type M Kinded as you want ? + trait Kinded[M <: AnyKind] { type Out <: AnyKind } + object Kinded { + type Aux[M <: AnyKind, Out0 <: AnyKind] = Kinded[M] { type Out = Out0 } + + implicit def kinded0[M]: Aux[M, M] = new Kinded[M] { type Out = M } + implicit def kinded1[M[_]]: Aux[M, M] = new Kinded[M] { type Out[t] = M[t] } + implicit def kinded2[M[_, _]]: Aux[M, M] = new Kinded[M] { type Out[t, u] = M[t, u] } + } + + implicitly[Kinded.Aux[Int, Int]] + implicitly[Kinded.Aux[List, List]] + implicitly[Kinded.Aux[Map, Map]] + + //////////////////////////////////////////////// + // Extract Kind from a type + trait Kinder[MA] { type M <: AnyKind; type Args <: HList } + object Kinder extends KinderLowerImplicits { + type Aux[MA, M0 <: AnyKind, Args0 <: HList] = Kinder[MA] { type M = M0; type Args = Args0 } + + implicit def kinder2[M0[_, _], A0, B0]: Kinder.Aux[M0[A0, B0], M0, A0 :: B0 :: HNil] = new Kinder[M0[A0, B0]] { type M[t, u] = M0[t, u]; type Args = A0 :: B0 :: HNil } + implicit def kinder1[M0[_], A0]: Kinder.Aux[M0[A0], M0, A0 :: HNil] = new Kinder[M0[A0]] { type M[t] = M0[t]; type Args = A0 :: HNil } + } + + trait KinderLowerImplicits { + implicit def kinder0[A]: Kinder.Aux[A, A, HNil] = new Kinder[A] { type M = A; type Args = HNil } + } + + //////////////////////////////////////////////// + //IsoKindness Test + trait SameKind[M <: AnyKind, M2 <: AnyKind] + object SameKind { + + implicit def sameKind0[A, B]: SameKind[A, B] = new {} + implicit def sameKind01[M1[_], M2[_]]: SameKind[M1, M2] = new {} + implicit def sameKind02[M1[_, _], M2[_, _]]: SameKind[M1, M2] = new {} + } + + def sameKind[M1 <: AnyKind, M2 <: AnyKind](implicit sameKind: SameKind[M1, M2]) = sameKind + + sameKind[Int, String] // OK + sameKind[List, Bar] // OK + sameKind[Map, Toto] // OK + + // sameKind[List, String] // KO + // sameKind[Map, List] // KO + // sameKind[Map, Boolean] // KO + + + + //////////////////////////////////////////////// + // Kind-Polymorphic List style + + // Classic Heterogenous List used in KindPolymorphic List + sealed trait HList + final case class ::[+H, +T <: HList](head : H, tail : T) extends HList + sealed trait HNil extends HList + final case object HNil extends HNil + + object New { + // The Kind Polymorphic List + sealed trait KPList + + sealed trait KPNil extends KPList + case object KPNil extends KPNil { + def :::[H, M <: AnyKind, HL <: HList](h:H)(implicit kinder: Kinder.Aux[H, M, HL]) = + New.:::(h, KPNil) + } + + sealed case class :::[H, T <: KPList, M <: AnyKind, HL0 <: HList]( + head: H + , tail: T + )(implicit val kinder: Kinder.Aux[H, M, HL0]) extends KPList + + final case class KPListOps[L <: KPList](l : L) { + def :::[H, M <: AnyKind, HL <: HList](h:H)(implicit kinder: Kinder.Aux[H, M, HL]) = + New.:::(h, l) + } + + implicit def kplistOps[L <: KPList](l: L): KPListOps[L] = new KPListOps(l) + + val kl = Bar(5) ::: "toto" ::: List(1, 2, 3) ::: Map("toto" -> 1L, "tata" -> 2L) ::: KPNil + + val h: Bar[Int] = kl.head + val h2: String = kl.tail.head + val h3: List[Int] = kl.tail.tail.head + val h4: Map[String, Long] = kl.tail.tail.tail.head + + } + + + //////////////////////////////////////////////// + // SPECIAL CASES + // def foo0[F <: AnyKind]: F = null.asInstanceOf[F] // error: F cannot be used as a value type + // val i = foo0[Int] + // val li = foo0[List[Int]] + // foo0[List] // KO -> neg + // val l = foo0[List] // KO -> neg + + // def foo1[F <: AnyKind, A <: AnyKind]: F[A] = ??? // KO + + // def foo2: AnyKind = ??? // KO + + + // Older implementation Kind-Polymorphic List but I prefer the one above + object Old { + + // The Kind Polymorphic List + sealed trait KPList + + sealed trait KPNil extends KPList + case object KPNil extends KPNil + + sealed trait :::[H <: AnyKind, T <: KPList] extends KPList + trait KPCons[M <: AnyKind, T <: KPList] extends :::[M, T] { + type HL <: HList + type H + def head: H + def tail: T + } + + object KPCons { + type Aux[M <: AnyKind, T <: KPList, H0, HL0 <: HList] = KPCons[M, T] { type H = H0; type HL = HL0 } + // Polymorphic + trait Apply[M <: AnyKind, A <: HList] { type Out } + object Apply { + type Aux[M <: AnyKind, A <: HList, Out0] = Apply[M, A] { type Out = Out0 } + implicit def apply0[M]: Aux[M, HNil, M] = new Apply[M, HNil] { type Out = M } + implicit def apply1[M[_], A]: Aux[M, A :: HNil, M[A]] = new Apply[M, A :: HNil] { type Out = M[A] } + implicit def apply2[M[_, _], A, B]: Aux[M, A :: B :: HNil, M[A, B]] = new Apply[M, A :: B :: HNil] { type Out = M[A, B] } + } + + trait Unapply[M <: AnyKind, O] { type Out <: HList } + object Unapply { + type Aux[M <: AnyKind, O, Out0 <: HList] = Unapply[M, O] { type Out = Out0 } + + implicit def unapply0[M]: Aux[M, M, HNil] = new Unapply[M, M] { type Out = HNil } + implicit def unapply1[M[_], A0]: Unapply.Aux[M, M[A0], A0 :: HNil] = new Unapply[M, M[A0]] { type Out = A0 :: HNil } + implicit def unapply2[M[_, _], A0, B0]: Aux[M, M[A0, B0], A0 :: B0 :: HNil] = new Unapply[M, M[A0, B0]] { type Out = A0 :: B0 :: HNil } + } + + // the list builder + trait KPConsBuilder[M <: AnyKind] { + def apply[H0, HL0 <: HList, T <: KPList](head0: H0, tail0: T)(implicit unap: Unapply.Aux[M, H0, HL0]): KPCons.Aux[M, T, H0, HL0] = new KPCons[M, T] { + type HL = HL0 + type H = H0 + val head: H = head0 + val tail: T = tail0 + } + } + + def apply[M <: AnyKind] = new KPConsBuilder[M] {} + } + + + // Let's create some kind-polymorphic list + val kl = + KPCons[Bar]( + Bar(5) + , KPCons[String]( + "toto" + , KPCons[List]( + List(1, 2, 3) + , KPCons[Map]( + Map("toto" -> 1L, "tata" -> 2L) + , KPNil + ) + ) + ) + ) + + val h: Bar[Int] = kl.head + val h2: String = kl.tail.head + val h3: List[Int] = kl.tail.tail.head + val h4: Map[String, Long] = kl.tail.tail.tail.head + + } + +} \ No newline at end of file From 462a401f305484e5ddc2c790e72cc90bb09c9e7f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Mar 2018 17:55:52 +0100 Subject: [PATCH 08/16] Remove failing repl test We keep the test as a neg test anyway. The old and new error messages are both uninteresting. --- compiler/test-resources/repl/i2492 | 6 ------ tests/neg/i2492.scala | 4 ++++ 2 files changed, 4 insertions(+), 6 deletions(-) delete mode 100644 compiler/test-resources/repl/i2492 create mode 100644 tests/neg/i2492.scala diff --git a/compiler/test-resources/repl/i2492 b/compiler/test-resources/repl/i2492 deleted file mode 100644 index b459d4feef43..000000000000 --- a/compiler/test-resources/repl/i2492 +++ /dev/null @@ -1,6 +0,0 @@ -scala> class Map[K, V] -// defined class Map -scala> val s: Map {type Map$K = String; type Map$V = Int} = null -1 | val s: Map {type Map$K = String; type Map$V = Int} = null - | ^^^ - | missing type parameter for Map diff --git a/tests/neg/i2492.scala b/tests/neg/i2492.scala new file mode 100644 index 000000000000..d69a837f3f9d --- /dev/null +++ b/tests/neg/i2492.scala @@ -0,0 +1,4 @@ +class Map[K, V] +object Foo { + val s: Map {type Map$K = String; type Map$V = Int} = null // error +} From 5bc992720397153db02b710aa212989209b50fd6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 10:42:22 +0100 Subject: [PATCH 09/16] Rename isHK -> isLambdaSub --- .../src/dotty/tools/dotc/config/Config.scala | 2 +- .../tools/dotc/core/TypeApplications.scala | 18 ++++++++++-------- .../dotty/tools/dotc/core/TypeComparer.scala | 19 +++++++++++-------- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 4 ++-- .../src/dotty/tools/dotc/typer/Checking.scala | 4 ++-- .../dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 7 files changed, 28 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 8abbb81bae85..4d8fcac61f50 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -160,7 +160,7 @@ object Config { final val showCompletions = false /** If set, enables tracing */ - final val tracingEnabled = false + final val tracingEnabled = true /** Initial capacity of uniques HashMap. * Note: This MUST BE a power of two to work with util.HashSet diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index ef7e77515456..53b5bede376b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -197,7 +197,7 @@ class TypeApplications(val self: Type) extends AnyVal { /** If `self` is a higher-kinded type, its type parameters, otherwise Nil */ final def hkTypeParams(implicit ctx: Context): List[TypeParamInfo] = - if (isHK) typeParams else Nil + if (isLambdaSub) typeParams else Nil /** If `self` is a generic class, its type parameter symbols, otherwise Nil */ final def typeParamSymbols(implicit ctx: Context): List[TypeSymbol] = typeParams match { @@ -207,10 +207,12 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => Nil } - /** Is self type higher-kinded (i.e. of kind != "*") - * or any-kinded (i.e. has AnyKind as upper bound)? - */ - def isHK(implicit ctx: Context): Boolean = hkResult.exists + /** 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.isRef(defn.AnyKindClass) /** If self type is higher-kinded, its result type, otherwise NoType. * Note: The hkResult of an any-kinded type is again AnyKind. @@ -268,9 +270,9 @@ class TypeApplications(val self: Type) extends AnyVal { //.ensuring(res => res.EtaReduce =:= self, s"res = $res, core = ${res.EtaReduce}, self = $self, hc = ${res.hashCode}") } - /** If self is not higher-kinded, eta expand it. */ - def ensureHK(implicit ctx: Context): Type = - if (isHK) self else EtaExpansion(self) + /** If self is not lambda-bound, eta expand it. */ + def ensureLambdaSub(implicit ctx: Context): Type = + if (isLambdaSub) self else EtaExpansion(self) /** Eta expand if `self` is a (non-lambda) class reference and `bound` is a higher-kinded type */ def EtaExpandIfHK(bound: Type)(implicit ctx: Context): Type = { diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 7cc254f9ca36..e241a32e6a50 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -380,7 +380,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (cls2.typeParams.isEmpty) { if (cls2 eq AnyKindClass) return true if (tp1.isRef(defn.NothingClass)) return true - if (tp1.isHK) return false + if (tp1.isLambdaSub) return false + // Note: We would like to replace this by `if (tp1.hasHigherKind)` + // but right now we cannot since some parts of the standard library rely on the + // idiom that e.g. `List <: Any`. We have to bootstrap without scalac first. val base = tp1.baseType(cls2) if (base.exists && base.ne(tp1)) return isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx.addLow) @@ -391,7 +394,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { val base = tp1.baseType(cls2) if (base.typeSymbol == cls2) return true } - else if (tp1.isHK && !tp1.isRef(defn.AnyKindClass)) + else if (tp1.isLambdaSub && !tp1.isRef(defn.AnyKindClass)) return recur(tp1, EtaExpansion(cls2.typeRef)) } fourthTry @@ -551,7 +554,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { def compareTypeBounds = tp1 match { case tp1 @ TypeBounds(lo1, hi1) => ((lo2 eq NothingType) || isSubType(lo2, lo1)) && - ((hi2 eq AnyType) && !hi1.isHK || (hi2 eq AnyKindType) || isSubType(hi1, hi2)) + ((hi2 eq AnyType) && !hi1.isLambdaSub || (hi2 eq AnyKindType) || isSubType(hi1, hi2)) case tp1: ClassInfo => tp2 contains tp1 case _ => @@ -621,7 +624,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case EtaExpansion(tycon1) => recur(tycon1, tp2) case _ => tp2 match { case tp2: HKTypeLambda => false // this case was covered in thirdTry - case _ => tp2.isHK && isSubType(tp1.resultType, tp2.appliedTo(tp1.paramRefs)) + case _ => tp2.isLambdaSub && isSubType(tp1.resultType, tp2.appliedTo(tp1.paramRefs)) } } compareHKLambda @@ -729,7 +732,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { tl => tp1base.tycon.appliedTo(args1.take(lengthDiff) ++ tparams1.indices.toList.map(tl.paramRefs(_)))) (ctx.mode.is(Mode.TypevarsMissContext) || - tryInstantiate(tycon2, tycon1.ensureHK)) && + tryInstantiate(tycon2, tycon1.ensureLambdaSub)) && recur(tp1, tycon1.appliedTo(args2)) } } @@ -812,7 +815,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case param1: TypeParamRef => def canInstantiate = tp2 match { case AppliedType(tycon2, args2) => - tryInstantiate(param1, tycon2.ensureHK) && isSubArgs(args1, args2, tp1, tycon2.typeParams) + tryInstantiate(param1, tycon2.ensureLambdaSub) && isSubArgs(args1, args2, tp1, tycon2.typeParams) case _ => false } @@ -1227,8 +1230,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (tp1 eq tp2) tp1 else if (!tp1.exists) tp2 else if (!tp2.exists) tp1 - else if ((tp1 isRef AnyClass) && !tp2.isHK || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) tp2 - else if ((tp2 isRef AnyClass) && !tp1.isHK || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) tp1 + else if ((tp1 isRef AnyClass) && !tp2.isLambdaSub || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) tp2 + else if ((tp2 isRef AnyClass) && !tp1.isLambdaSub || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) tp1 else tp2 match { // normalize to disjunctive normal form if possible. case OrType(tp21, tp22) => tp1 & tp21 | tp1 & tp22 diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 312e2d4093db..78cab75d55c8 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -356,8 +356,8 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder private def computeType(tp: Type): api.Type = { // TODO: Never dealias. We currently have to dealias because // sbt main class discovery relies on the signature of the main - // method being fully dealiased. See https://github.com/sbt/zinc/issues/102 - val tp2 = if (!tp.isHK) tp.dealiasKeepAnnots else tp + // method being fully dealiased. See https://github.com/sbt/zinc/isisLambdaSub/102 + val tp2 = if (!tp.isLambdaSub) tp.dealiasKeepAnnots else tp tp2 match { case NoPrefix | NoType => Constants.emptyType diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 0659b68666dd..698fdaff0c15 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -45,7 +45,7 @@ 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.isHK && arg.tpe.isHK) + if (!bound.isLambdaSub && arg.tpe.isLambdaSub) // see MissingTypeParameterFor ctx.error(ex"missing type parameter(s) for $arg", arg.pos) } @@ -657,7 +657,7 @@ trait Checking { /** Check that `tpt` does not define a higher-kinded type */ def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = - if (tpt.tpe.isHK && !ctx.compilationUnit.isJava) { + if (tpt.tpe.isLambdaSub && !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 0e283cd49e37..61502cddf482 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.isHK) qualType = errorType(em"$qualType takes type parameters", qual1.pos) + if (qualType.isLambdaSub) qualType = errorType(em"$qualType takes type parameters", qual1.pos) val ownType = selectionType(qualType, tree.name, tree.pos) ensureAccessible(ownType, qual1.isInstanceOf[Super], tree.pos) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a367a81a2e00..9a9c78130f65 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1237,7 +1237,7 @@ class Typer extends Namer var res = typed(desugaredArg, argPt) arg match { case TypeBoundsTree(EmptyTree, EmptyTree) - if tparam.paramInfo.isHK && + if tparam.paramInfo.isLambdaSub && tpt1.tpe.typeParamSymbols.nonEmpty && !ctx.mode.is(Mode.Pattern) => // An unbounded `_` automatically adapts to type parameter bounds. This means: From 0031ca92af60e560421442a9f06037b43e5f67c9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 20 Mar 2018 15:52:56 +0100 Subject: [PATCH 10/16] Add hook to make AnyKind support conditional --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index f5622f27d0ca..c2d92bcd2027 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -304,7 +304,11 @@ class Definitions { def ObjectMethods = List(Object_eq, Object_ne, Object_synchronized, Object_clone, Object_finalize, Object_notify, Object_notifyAll, Object_wait, Object_waitL, Object_waitLI) - lazy val AnyKindClass = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal, Nil)) + lazy val AnyKindClass = { + val cls = ctx.newCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal | Permanent, Nil) + if (true) cls.entered // TODO: replace with if (ctx.settings.Yanykind.value) ... + cls + } def AnyKindType = AnyKindClass.typeRef /** Marker method to indicate an argument to a call-by-name parameter. From 99e0dc6c71385c6051bcf1ddee3f5cd6157e2977 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 20 Mar 2018 17:29:18 +0100 Subject: [PATCH 11/16] Fix accidental replacement --- compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 78cab75d55c8..0921bfcf58e8 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -356,7 +356,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder private def computeType(tp: Type): api.Type = { // TODO: Never dealias. We currently have to dealias because // sbt main class discovery relies on the signature of the main - // method being fully dealiased. See https://github.com/sbt/zinc/isisLambdaSub/102 + // method being fully dealiased. See https://github.com/sbt/zinc/issues/102 val tp2 = if (!tp.isLambdaSub) tp.dealiasKeepAnnots else tp tp2 match { case NoPrefix | NoType => From 92a941aa6488f88cf457eb0f4399ff249144fc18 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 20 Mar 2018 17:53:58 +0100 Subject: [PATCH 12/16] Hide kind-polymorphism behind -Ykind-polymorphism --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 3 +++ compiler/src/dotty/tools/dotc/core/Definitions.scala | 5 ++++- compiler/test/dotty/tools/dotc/CompilationTests.scala | 2 ++ tests/{neg => neg-kind-polymorphism}/anykind.scala | 0 tests/{neg => neg-kind-polymorphism}/anykind1.scala | 0 tests/{neg => neg-kind-polymorphism}/anykind2.scala | 0 tests/{neg => neg-kind-polymorphism}/anykind3.scala | 0 tests/{neg => neg-kind-polymorphism}/anykind4.scala | 0 tests/{pos => pos-kind-polymorphism}/anykind.scala | 0 tests/{pos => pos-kind-polymorphism}/kindPolySemiGroup.scala | 0 10 files changed, 9 insertions(+), 1 deletion(-) rename tests/{neg => neg-kind-polymorphism}/anykind.scala (100%) rename tests/{neg => neg-kind-polymorphism}/anykind1.scala (100%) rename tests/{neg => neg-kind-polymorphism}/anykind2.scala (100%) rename tests/{neg => neg-kind-polymorphism}/anykind3.scala (100%) rename tests/{neg => neg-kind-polymorphism}/anykind4.scala (100%) rename tests/{pos => pos-kind-polymorphism}/anykind.scala (100%) rename tests/{pos => pos-kind-polymorphism}/kindPolySemiGroup.scala (100%) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 17deaa5d88e2..ec2585f8f199 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -123,6 +123,9 @@ class ScalaSettings extends Settings.SettingGroup { val YprofileRunGcBetweenPhases = PhasesSetting("-Yprofile-run-gc", "Run a GC between phases - this allows heap size to be accurate at the expense of more time. Specify a list of phases, or *", "_") //.withPostSetHook( _ => YprofileEnabled.value = true ) + // Extremely experimetnal language feature + val YkindPolymorphism = BooleanSetting("-Ykind-polymorphism", "Potentially unsound.") + /** Area-specific debug output */ val YexplainLowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") val YnoDoubleBindings = BooleanSetting("-Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).") diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index c2d92bcd2027..95c5aa94dd37 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -306,7 +306,10 @@ class Definitions { lazy val AnyKindClass = { val cls = ctx.newCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal | Permanent, Nil) - if (true) cls.entered // TODO: replace with if (ctx.settings.Yanykind.value) ... + if (ctx.settings.YkindPolymorphism.value) { + // Enable kind-polymorphism by exposing scala.AnyKind + cls.entered + } cls } def AnyKindType = AnyKindClass.typeRef diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 1c5efcd57de7..5921665f2d7b 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -102,6 +102,7 @@ class CompilationTests extends ParallelTesting { compileFilesInDir("tests/pos", defaultOptions) + compileFilesInDir("tests/pos-no-optimise", defaultOptions) + compileFilesInDir("tests/pos-deep-subtype", allowDeepSubtypes) + + compileFilesInDir("tests/pos-kind-polymorphism", defaultOptions and "-Ykind-polymorphism") + compileDir("tests/pos/i1137-1", defaultOptions and "-Yemit-tasty") + compileFile( // succeeds despite -Xfatal-warnings because of -nowarn @@ -175,6 +176,7 @@ class CompilationTests extends ParallelTesting { compileFilesInDir("tests/neg", defaultOptions) + compileFilesInDir("tests/neg-tailcall", defaultOptions) + compileFilesInDir("tests/neg-no-optimise", defaultOptions) + + compileFilesInDir("tests/neg-kind-polymorphism", defaultOptions and "-Ykind-polymorphism") + compileFilesInDir("tests/neg-custom-args/fatal-warnings", defaultOptions.and("-Xfatal-warnings")) + compileFilesInDir("tests/neg-custom-args/allow-double-bindings", allowDoubleBindings) + compileFile("tests/neg-custom-args/i3246.scala", scala2Mode) + diff --git a/tests/neg/anykind.scala b/tests/neg-kind-polymorphism/anykind.scala similarity index 100% rename from tests/neg/anykind.scala rename to tests/neg-kind-polymorphism/anykind.scala diff --git a/tests/neg/anykind1.scala b/tests/neg-kind-polymorphism/anykind1.scala similarity index 100% rename from tests/neg/anykind1.scala rename to tests/neg-kind-polymorphism/anykind1.scala diff --git a/tests/neg/anykind2.scala b/tests/neg-kind-polymorphism/anykind2.scala similarity index 100% rename from tests/neg/anykind2.scala rename to tests/neg-kind-polymorphism/anykind2.scala diff --git a/tests/neg/anykind3.scala b/tests/neg-kind-polymorphism/anykind3.scala similarity index 100% rename from tests/neg/anykind3.scala rename to tests/neg-kind-polymorphism/anykind3.scala diff --git a/tests/neg/anykind4.scala b/tests/neg-kind-polymorphism/anykind4.scala similarity index 100% rename from tests/neg/anykind4.scala rename to tests/neg-kind-polymorphism/anykind4.scala diff --git a/tests/pos/anykind.scala b/tests/pos-kind-polymorphism/anykind.scala similarity index 100% rename from tests/pos/anykind.scala rename to tests/pos-kind-polymorphism/anykind.scala diff --git a/tests/pos/kindPolySemiGroup.scala b/tests/pos-kind-polymorphism/kindPolySemiGroup.scala similarity index 100% rename from tests/pos/kindPolySemiGroup.scala rename to tests/pos-kind-polymorphism/kindPolySemiGroup.scala From ee44f8fd68467f84072f5698d6b977c503a7af2e Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Fri, 23 Mar 2018 15:57:41 +0100 Subject: [PATCH 13/16] Fix flag description --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index ec2585f8f199..0ae3a1ae93c8 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -123,8 +123,8 @@ class ScalaSettings extends Settings.SettingGroup { val YprofileRunGcBetweenPhases = PhasesSetting("-Yprofile-run-gc", "Run a GC between phases - this allows heap size to be accurate at the expense of more time. Specify a list of phases, or *", "_") //.withPostSetHook( _ => YprofileEnabled.value = true ) - // Extremely experimetnal language feature - val YkindPolymorphism = BooleanSetting("-Ykind-polymorphism", "Potentially unsound.") + // Extremely experimental language features + val YkindPolymorphism = BooleanSetting("-Ykind-polymorphism", "Enable kind polymorphism (see http://dotty.epfl.ch/docs/reference/kind-polymorphism.html). Potentially unsound.") /** Area-specific debug output */ val YexplainLowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") From 05162fe64e08952ca2f0d34665b043e899c98099 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Fri, 23 Mar 2018 15:58:46 +0100 Subject: [PATCH 14/16] Disable tracing again --- compiler/src/dotty/tools/dotc/config/Config.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 4d8fcac61f50..8abbb81bae85 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -160,7 +160,7 @@ object Config { final val showCompletions = false /** If set, enables tracing */ - final val tracingEnabled = true + final val tracingEnabled = false /** Initial capacity of uniques HashMap. * Note: This MUST BE a power of two to work with util.HashSet From e51db1c7c52791be89f00fce28f0d1be40413b9c Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Fri, 23 Mar 2018 16:01:40 +0100 Subject: [PATCH 15/16] Fix typos in new docs --- docs/docs/reference/kind-polymorphism.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/reference/kind-polymorphism.md b/docs/docs/reference/kind-polymorphism.md index 9c375c8e3a07..0d520d764641 100644 --- a/docs/docs/reference/kind-polymorphism.md +++ b/docs/docs/reference/kind-polymorphism.md @@ -3,7 +3,7 @@ layout: doc-page title: "Kind Polymorphism" --- -Normally type parameters in Scala are partitioned into _kinds_. First-level types are types of values. Higher-kinded types are type constructurs +Normally type parameters in Scala are partitioned into _kinds_. First-level types are types of values. Higher-kinded types are type constructors such as `List` or `Map`. The kind of a type is indicated by the top type of which it is a subtype. Normal types are subtypes of `Any`, covariant single argument type constructors such as `List` are subtypes of `[+X] => Any`, and the `Map` type constructor is a subtype of `[X, +Y] => Any`. @@ -32,7 +32,7 @@ We call type parameters and abstract types with an `AnyKind` upper bound _any-ki Since the actual kind of an any-kinded type is unknown, its usage must be heavily restricted: An any-kinded type can be neither the type of a value, nor can it be instantiated with type parameters. So about the only thing one can do with an any-kinded type is to pass it to another any-kinded type argument. -Nevertheless, this is enough to achieve some interesting generalizations that work across kinds, typicially +Nevertheless, this is enough to achieve some interesting generalizations that work across kinds, typically through advanced uses of implicits. (todo: insert good concise example) @@ -40,4 +40,4 @@ through advanced uses of implicits. Some technical details: `AnyKind` is a synthesized class just like `Any`, but without any members. It extends no other class. It is declared `abstract` and `final`, so it can be neither instantiated nor extended. -`AnyKind` plays a special role in Scala's subtype system: It is a supertype of all other types no matter what their kind is. It is also assumed to be kind-compatible with all other types. Furthermore, `AnyKind` is treated as a higher-kinded type (so it cannot be used as a type of values), but at the same time it has no type parameters (so it cannot be instantiated). \ No newline at end of file +`AnyKind` plays a special role in Scala's subtype system: It is a supertype of all other types no matter what their kind is. It is also assumed to be kind-compatible with all other types. Furthermore, `AnyKind` is treated as a higher-kinded type (so it cannot be used as a type of values), but at the same time it has no type parameters (so it cannot be instantiated). From 91e7fc4147e85f25b89ce201535e7a2b3dabbeea Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Fri, 23 Mar 2018 17:19:23 +0100 Subject: [PATCH 16/16] Fix #4167: Add regression test --- tests/pos/i4167/Test_2.scala | 6 ++++++ tests/pos/i4167/collections_1.scala | 11 +++++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/pos/i4167/Test_2.scala create mode 100644 tests/pos/i4167/collections_1.scala diff --git a/tests/pos/i4167/Test_2.scala b/tests/pos/i4167/Test_2.scala new file mode 100644 index 000000000000..a0dcf504cf6b --- /dev/null +++ b/tests/pos/i4167/Test_2.scala @@ -0,0 +1,6 @@ +package collection + +object Test { + type AnyConstr[X] = Any + val test: SeqOps[Char, AnyConstr, _] = null.asInstanceOf[StringOps] +} diff --git a/tests/pos/i4167/collections_1.scala b/tests/pos/i4167/collections_1.scala new file mode 100644 index 000000000000..a8bc24d2bac7 --- /dev/null +++ b/tests/pos/i4167/collections_1.scala @@ -0,0 +1,11 @@ +package collection + +trait Seq[+A] extends SeqOps[A, Seq, Seq[A]] +trait SeqOps[+A, +CC[_], +C] extends Any + +package immutable { + trait Seq[+A] extends collection.Seq[A] with SeqOps[A, Seq, Seq[A]] + trait SeqOps[+A, +CC[_], +C] extends collection.SeqOps[A, CC, C] +} + +class StringOps extends collection.SeqOps[Char, immutable.Seq, String] \ No newline at end of file