Skip to content

Change distribute or #1001

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

Merged
merged 12 commits into from
Dec 21, 2015
9 changes: 9 additions & 0 deletions src/dotty/tools/dotc/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ object Config {
/** Check that certain types cannot be created in erasedTypes phases */
final val checkUnerased = true

/** In `derivedSelect`, rewrite
*
* (S & T)#A --> S#A & T#A
* (S | T)#A --> S#A | T#A
*
* Not sure whether this is useful. Preliminary measurements show a slowdown of about
* 7% for the build when this option is enabled.
*/
final val splitProjections = false

/** Initial size of superId table */
final val InitialSuperIdsSize = 4096
Expand Down
1 change: 1 addition & 0 deletions src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ class ScalaSettings extends Settings.SettingGroup {
val Ypatmatdebug = BooleanSetting("-Ypatmat-debug", "Trace pattern matching translation.")
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).")
val YshowVarBounds = BooleanSetting("-Yshow-var-bounds", "Print type variables with their bounds")

val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying optimisations to the program") withAbbreviation "-optimize"

Expand Down
46 changes: 46 additions & 0 deletions src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,46 @@ object TypeApplications {
if (tparams.isEmpty) args
else args.zipWithConserve(tparams)((arg, tparam) => arg.etaExpandIfHK(tparam.infoOrCompleter))

/** The references `<rt>.this.$hk0, ..., <rt>.this.$hk<n-1>`. */
def argRefs(rt: RefinedType, n: Int)(implicit ctx: Context) =
List.range(0, n).map(i => RefinedThis(rt).select(tpnme.hkArg(i)))

/** Merge `tp1` and `tp2` under a common lambda, combining them with `op`.
* @param tparams1 The type parameters of `tp1`
* @param tparams2 The type parameters of `tp2`
* @pre tparams1.length == tparams2.length
* Produces the type lambda
*
* [v1 X1 B1, ..., vn Xn Bn] -> op(tp1[X1, ..., Xn], tp2[X1, ..., Xn])
*
* where
*
* - variances `vi` are the variances of corresponding type parameters for `tp1`
* or `tp2`, or are 0 of the latter disagree.
* - bounds `Bi` are the intersection of the corresponding type parameter bounds
* of `tp1` and `tp2`.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add @pre tparams1.length == tparams2.length or add an assert.

def hkCombine(tp1: Type, tp2: Type,
tparams1: List[TypeSymbol], tparams2: List[TypeSymbol], op: (Type, Type) => Type)
(implicit ctx: Context): Type = {
val variances = (tparams1, tparams2).zipped.map { (tparam1, tparam2) =>
val v1 = tparam1.variance
val v2 = tparam2.variance
if (v1 == v2) v1 else 0
}
val bounds: List[RefinedType => TypeBounds] =
(tparams1, tparams2).zipped.map { (tparam1, tparam2) =>
val b1: RefinedType => TypeBounds =
tp1.memberInfo(tparam1).bounds.internalizeFrom(tparams1)
val b2: RefinedType => TypeBounds =
tp2.memberInfo(tparam2).bounds.internalizeFrom(tparams2)
(rt: RefinedType) => b1(rt) & b2(rt)
}
val app1: RefinedType => Type = rt => tp1.appliedTo(argRefs(rt, tparams1.length))
val app2: RefinedType => Type = rt => tp2.appliedTo(argRefs(rt, tparams2.length))
val body: RefinedType => Type = rt => op(app1(rt), app2(rt))
TypeLambda(variances, bounds, body)
}
}

import TypeApplications._
Expand Down Expand Up @@ -273,6 +311,14 @@ class TypeApplications(val self: Type) extends AnyVal {
false
}

/** Replace references to type parameters with references to hk arguments `this.$hk_i`
* Care is needed not to cause cyclic reference errors, hence `SafeSubstMap`.
*/
private[TypeApplications] def internalizeFrom[T <: Type](tparams: List[Symbol])(implicit ctx: Context): RefinedType => T =
(rt: RefinedType) =>
new ctx.SafeSubstMap(tparams , argRefs(rt, tparams.length))
.apply(self).asInstanceOf[T]

/** Lambda abstract `self` with given type parameters. Examples:
*
* type T[X] = U becomes type T = [X] -> U
Expand Down
158 changes: 77 additions & 81 deletions src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -144,49 +144,43 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
def compareNamed(tp1: Type, tp2: NamedType): Boolean = {
implicit val ctx: Context = this.ctx
tp2.info match {
case info2: TypeAlias if tp2.prefix.isStable =>
// If prefix is not stable (i.e. is not a path), then we have a true
// projection `T # A` which is treated as the existential type
// `ex(x: T)x.A`. We need to deal with the existential first before
// following the alias. If we did follow the alias we could be
// unsound as well as incomplete. An example of this was discovered in Iter2.scala.
// It failed to validate the subtype test
//
// (([+X] -> Seq[X]) & C)[SA] <: C[SA]
//
// Both sides are projections of $Apply. The left $Apply does have an
// aliased info, namely, Seq[SA]. But that is not a subtype of C[SA].
// The problem is that, with the prefix not being a path, an aliased info
// does not necessarily give all of the information of the original projection.
// So we can't follow the alias without a backup strategy. If the alias
// would appear on the right then I believe this can be turned into a case
// of unsoundness.
isSubType(tp1, info2.alias)
case info2: TypeAlias => isSubType(tp1, info2.alias)
case _ => tp1 match {
case tp1: NamedType =>
tp1.info match {
case info1: TypeAlias if tp1.prefix.isStable =>
isSubType(info1.alias, tp2)
case info1: TypeAlias =>
if (isSubType(info1.alias, tp2)) return true
if (tp1.prefix.isStable) return false
// If tp1.prefix is stable, the alias does contain all information about the original ref, so
// there's no need to try something else. (This is important for performance).
// To see why we cannot in general stop here, consider:
//
// trait C { type A }
// trait D { type A = String }
// (C & D)#A <: C#A
//
// Following the alias leads to the judgment `String <: C#A` which is false.
// However the original judgment should be true.
case _ =>
val sym1 = tp1.symbol
if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol))
ctx.erasedTypes ||
sym1.isStaticOwner ||
isSubType(tp1.prefix, tp2.prefix) ||
thirdTryNamed(tp1, tp2)
else
( (tp1.name eq tp2.name)
&& isSubType(tp1.prefix, tp2.prefix)
&& tp1.signature == tp2.signature
&& !tp1.isInstanceOf[WithFixedSym]
&& !tp2.isInstanceOf[WithFixedSym]
) ||
compareHK(tp1, tp2, inOrder = true) ||
compareHK(tp2, tp1, inOrder = false) ||
thirdTryNamed(tp1, tp2)
}
val sym1 = tp1.symbol
if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol))
ctx.erasedTypes ||
sym1.isStaticOwner ||
isSubType(tp1.prefix, tp2.prefix) ||
thirdTryNamed(tp1, tp2)
else
( (tp1.name eq tp2.name)
&& isSubType(tp1.prefix, tp2.prefix)
&& tp1.signature == tp2.signature
&& !tp1.isInstanceOf[WithFixedSym]
&& !tp2.isInstanceOf[WithFixedSym]
) ||
compareHkApply(tp1, tp2, inOrder = true) ||
compareHkApply(tp2, tp1, inOrder = false) ||
thirdTryNamed(tp1, tp2)
case _ =>
compareHK(tp2, tp1, inOrder = false) ||
compareHkApply(tp2, tp1, inOrder = false) ||
secondTry(tp1, tp2)
}
}
Expand Down Expand Up @@ -258,11 +252,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
private def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match {
case tp1: NamedType =>
tp1.info match {
case info1: TypeAlias => isSubType(info1.alias, tp2)
case info1: TypeAlias =>
if (isSubType(info1.alias, tp2)) return true
if (tp1.prefix.isStable) return false
case _ =>
compareHK(tp1, tp2, inOrder = true) ||
thirdTry(tp1, tp2)
}
compareHkApply(tp1, tp2, inOrder = true) ||
thirdTry(tp1, tp2)
case tp1: PolyParam =>
def flagNothingBound = {
if (!frozenConstraint && tp2.isRef(defn.NothingClass) && state.isGlobalCommittable) {
Expand Down Expand Up @@ -356,8 +352,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
isSubType(tp1, tp2.parent) &&
(name2 == nme.WILDCARD || hasMatchingMember(name2, tp1, tp2))
}
def etaExpandedSubType(tp1: Type) =
isSubType(tp1.typeConstructor.EtaExpand(tp2.typeParams), tp2)
def compareRefined: Boolean = {
val tp1w = tp1.widen
val skipped2 = skipMatching(tp1w, tp2)
Expand All @@ -371,7 +365,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
case _ =>
compareRefinedSlow ||
fourthTry(tp1, tp2) ||
needsEtaLift(tp1, tp2) && testLifted(tp1, tp2, tp2.typeParams, etaExpandedSubType)
compareHkLambda(tp2, tp1, inOrder = false)
}
else // fast path, in particular for refinements resulting from parameterization.
isSubType(tp1, skipped2) &&
Expand Down Expand Up @@ -491,10 +485,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
}
isNewSubType(tp1.underlying.widenExpr, tp2) || comparePaths
case tp1: RefinedType =>
isNewSubType(tp1.parent, tp2) ||
needsEtaLift(tp2, tp1) &&
tp2.typeParams.length == tp1.typeParams.length &&
isSubType(tp1, tp2.EtaExpand(tp1.typeParams))
isNewSubType(tp1.parent, tp2) || compareHkLambda(tp1, tp2, inOrder = true)
case AndType(tp11, tp12) =>
// Rewrite (T111 | T112) & T12 <: T2 to (T111 & T12) <: T2 and (T112 | T12) <: T2
// and analogously for T11 & (T121 | T122) & T12 <: T2
Expand Down Expand Up @@ -525,14 +516,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
false
}

/** Does `tp` need to be eta lifted to be comparable to `target`?
* This is the case if:
* - target is a type lambda, and
* - `tp` is eta-expandable (i.e. is a non-lambda class ref)
*/
private def needsEtaLift(tp: Type, target: RefinedType): Boolean =
target.refinedName == tpnme.hkApply && tp.isEtaExpandable

/** Test whether `tp1` has a base type of the form `B[T1, ..., Tn]` where
* - `B` derives from one of the class symbols of `tp2`,
* - the type parameters of `B` match one-by-one the variances of `tparams`,
Expand Down Expand Up @@ -574,7 +557,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
*
* (4) If `inOrder`, test `projection <: other` else test `other <: projection`.
*/
def compareHK(projection: NamedType, other: Type, inOrder: Boolean): Boolean = {
def compareHkApply(projection: NamedType, other: Type, inOrder: Boolean): Boolean = {
def tryInfer(tp: Type): Boolean = ctx.traceIndented(i"compareHK($projection, $other, inOrder = $inOrder, constr = $tp)", subtyping) {
tp match {
case tp: TypeVar => tryInfer(tp.underlying)
Expand Down Expand Up @@ -609,6 +592,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
tryInfer(projection.prefix.typeConstructor.dealias)
}

/** Compare type lambda with non-lambda type. */
def compareHkLambda(rt: RefinedType, other: Type, inOrder: Boolean) = rt match {
case TypeLambda(vs, args, body) =>
other.isInstanceOf[TypeRef] &&
args.length == other.typeParams.length && {
val applied = other.appliedTo(argRefs(rt, args.length))
if (inOrder) isSubType(body, applied) else isSubType(applied, body)
}
case _ =>
false
}

/** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time
* to keep the constraint as wide as possible. Specifically, if
*
Expand Down Expand Up @@ -1007,12 +1002,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
val t2 = distributeAnd(tp2, tp1)
if (t2.exists) t2
else if (erased) erasedGlb(tp1, tp2, isJava = false)
else {
//if (isHKRef(tp1)) tp2
//else if (isHKRef(tp2)) tp1
//else
AndType(tp1, tp2)
}
else liftIfHK(tp1, tp2, AndType(_, _))
}
}

Expand All @@ -1036,14 +1026,23 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
val t2 = distributeOr(tp2, tp1)
if (t2.exists) t2
else if (erased) erasedLub(tp1, tp2)
else
//if (isHKRef(tp1)) tp1
//else if (isHKRef(tp2)) tp2
//else
OrType(tp1, tp2)
else liftIfHK(tp1, tp2, OrType(_, _))
}
}

/** `op(tp1, tp2)` unless `tp1` and `tp2` are type-constructors.
* In the latter case, combine `tp1` and `tp2` under a type lambda like this:
*
* [X1, ..., Xn] -> op(tp1[X1, ..., Xn], tp2[X1, ..., Xn])
*/
private def liftIfHK(tp1: Type, tp2: Type, op: (Type, Type) => Type) = {
val tparams1 = tp1.typeParams
val tparams2 = tp2.typeParams
if (tparams1.isEmpty || tparams2.isEmpty) op(tp1, tp2)
else if (tparams1.length != tparams2.length) mergeConflict(tp1, tp2)
else hkCombine(tp1, tp2, tparams1, tparams2, op)
}

/** Try to distribute `&` inside type, detect and handle conflicts */
private def distributeAnd(tp1: Type, tp2: Type): Type = tp1 match {
// opportunistically merge same-named refinements
Expand Down Expand Up @@ -1106,18 +1105,15 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
NoType
}

/** Try to distribute `|` inside type, detect and handle conflicts */
/** Try to distribute `|` inside type, detect and handle conflicts
* Note that, unlike for `&`, a disjunction cannot be pushed into
* a refined or applied type. Example:
*
* List[T] | List[U] is not the same as List[T | U].
*
* The rhs is a proper supertype of the lhs.
*/
private def distributeOr(tp1: Type, tp2: Type): Type = tp1 match {
case tp1: RefinedType =>
tp2 match {
case tp2: RefinedType if tp1.refinedName == tp2.refinedName =>
tp1.derivedRefinedType(
tp1.parent | tp2.parent,
tp1.refinedName,
tp1.refinedInfo | tp2.refinedInfo.substRefinedThis(tp2, RefinedThis(tp1)))
case _ =>
NoType
}
case tp1: TypeBounds =>
tp2 match {
case tp2: TypeBounds => tp1 | tp2
Expand Down Expand Up @@ -1325,12 +1321,12 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {

override def copyIn(ctx: Context) = new ExplainingTypeComparer(ctx)

override def compareHK(projection: NamedType, other: Type, inOrder: Boolean) =
override def compareHkApply(projection: NamedType, other: Type, inOrder: Boolean) =
if (projection.name == tpnme.hkApply)
traceIndented(i"compareHK $projection, $other, $inOrder") {
super.compareHK(projection, other, inOrder)
super.compareHkApply(projection, other, inOrder)
}
else super.compareHK(projection, other, inOrder)
else super.compareHkApply(projection, other, inOrder)

override def toString = "Subtype trace:" + { try b.toString finally b.clear() }
}
Loading