Skip to content

Commit

Permalink
Merge pull request #4108 from dotty-staging/add-kind-poly
Browse files Browse the repository at this point in the history
Add kind polymorphism
  • Loading branch information
smarter authored Mar 23, 2018
2 parents 03489c5 + 91e7fc4 commit a0e1000
Show file tree
Hide file tree
Showing 24 changed files with 613 additions and 54 deletions.
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 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.")
val YnoDoubleBindings = BooleanSetting("-Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).")
Expand Down
11 changes: 11 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,16 @@ 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 = {
val cls = ctx.newCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal | Permanent, Nil)
if (ctx.settings.YkindPolymorphism.value) {
// Enable kind-polymorphism by exposing scala.AnyKind
cls.entered
}
cls
}
def AnyKindType = AnyKindClass.typeRef

/** Marker method to indicate an argument to a call-by-name parameter.
* Created by byNameClosures and elimByName, eliminated by Erasure,
*/
Expand Down Expand Up @@ -1158,6 +1168,7 @@ class Definitions {
lazy val syntheticScalaClasses = List(
AnyClass,
AnyRefAlias,
AnyKindClass,
RepeatedParamClass,
ByNameParamClass2x,
AnyValClass,
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 33 additions & 17 deletions compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -207,12 +207,19 @@ class TypeApplications(val self: Type) extends AnyVal {
case _ => Nil
}

/** Is self type higher-kinded (i.e. of kind != "*")? */
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

/** If self type is higher-kinded, its result type, otherwise NoType */
/** 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.
*/
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
Expand All @@ -226,17 +233,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 */
Expand All @@ -256,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 = {
Expand Down Expand Up @@ -355,7 +369,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 {
Expand Down
60 changes: 37 additions & 23 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -367,19 +377,25 @@ 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))
else {
if (cls2.typeParams.isEmpty) {
if (cls2 eq AnyKindClass) return true
if (tp1.isRef(defn.NothingClass)) return true
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) {
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.isLambdaSub && !tp1.isRef(defn.AnyKindClass))
return recur(tp1, EtaExpansion(cls2.typeRef))
}
fourthTry
}
Expand Down Expand Up @@ -475,13 +491,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)
Expand Down Expand Up @@ -540,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) || isSubType(hi1, hi2))
((hi2 eq AnyType) && !hi1.isLambdaSub || (hi2 eq AnyKindType) || isSubType(hi1, hi2))
case tp1: ClassInfo =>
tp2 contains tp1
case _ =>
Expand Down Expand Up @@ -610,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
Expand Down Expand Up @@ -718,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))
}
}
Expand Down Expand Up @@ -801,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
}
Expand Down Expand Up @@ -1216,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 isRef NothingClass)) tp2
else if ((tp2 isRef AnyClass) || (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
Expand Down Expand Up @@ -1265,8 +1279,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder
// 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
val tp2 = if (!tp.isLambdaSub) tp.dealiasKeepAnnots else tp
tp2 match {
case NoPrefix | NoType =>
Constants.emptyType
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
21 changes: 19 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,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.isLambdaSub &&
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]]
Expand Down Expand Up @@ -2146,7 +2163,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

Expand Down
6 changes: 0 additions & 6 deletions compiler/test-resources/repl/i2492

This file was deleted.

2 changes: 2 additions & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) +
Expand Down
Loading

0 comments on commit a0e1000

Please sign in to comment.