Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add kind polymorphism #4108

Merged
merged 16 commits into from
Mar 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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.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 @@ -2147,7 +2164,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