Skip to content

Commit 4163b24

Browse files
committed
Merge pull request #1001 from dotty-staging/change-distribute-or
Change distribute or
2 parents 13513b4 + 9675b84 commit 4163b24

File tree

12 files changed

+215
-90
lines changed

12 files changed

+215
-90
lines changed

src/dotty/tools/dotc/config/Config.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ object Config {
8787
/** Check that certain types cannot be created in erasedTypes phases */
8888
final val checkUnerased = true
8989

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

91100
/** Initial size of superId table */
92101
final val InitialSuperIdsSize = 4096

src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ class ScalaSettings extends Settings.SettingGroup {
173173
val Ypatmatdebug = BooleanSetting("-Ypatmat-debug", "Trace pattern matching translation.")
174174
val Yexplainlowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")
175175
val YnoDoubleBindings = BooleanSetting("-Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).")
176+
val YshowVarBounds = BooleanSetting("-Yshow-var-bounds", "Print type variables with their bounds")
176177

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

src/dotty/tools/dotc/core/TypeApplications.scala

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,46 @@ object TypeApplications {
173173
if (tparams.isEmpty) args
174174
else args.zipWithConserve(tparams)((arg, tparam) => arg.etaExpandIfHK(tparam.infoOrCompleter))
175175

176+
/** The references `<rt>.this.$hk0, ..., <rt>.this.$hk<n-1>`. */
176177
def argRefs(rt: RefinedType, n: Int)(implicit ctx: Context) =
177178
List.range(0, n).map(i => RefinedThis(rt).select(tpnme.hkArg(i)))
179+
180+
/** Merge `tp1` and `tp2` under a common lambda, combining them with `op`.
181+
* @param tparams1 The type parameters of `tp1`
182+
* @param tparams2 The type parameters of `tp2`
183+
* @pre tparams1.length == tparams2.length
184+
* Produces the type lambda
185+
*
186+
* [v1 X1 B1, ..., vn Xn Bn] -> op(tp1[X1, ..., Xn], tp2[X1, ..., Xn])
187+
*
188+
* where
189+
*
190+
* - variances `vi` are the variances of corresponding type parameters for `tp1`
191+
* or `tp2`, or are 0 of the latter disagree.
192+
* - bounds `Bi` are the intersection of the corresponding type parameter bounds
193+
* of `tp1` and `tp2`.
194+
*/
195+
def hkCombine(tp1: Type, tp2: Type,
196+
tparams1: List[TypeSymbol], tparams2: List[TypeSymbol], op: (Type, Type) => Type)
197+
(implicit ctx: Context): Type = {
198+
val variances = (tparams1, tparams2).zipped.map { (tparam1, tparam2) =>
199+
val v1 = tparam1.variance
200+
val v2 = tparam2.variance
201+
if (v1 == v2) v1 else 0
202+
}
203+
val bounds: List[RefinedType => TypeBounds] =
204+
(tparams1, tparams2).zipped.map { (tparam1, tparam2) =>
205+
val b1: RefinedType => TypeBounds =
206+
tp1.memberInfo(tparam1).bounds.internalizeFrom(tparams1)
207+
val b2: RefinedType => TypeBounds =
208+
tp2.memberInfo(tparam2).bounds.internalizeFrom(tparams2)
209+
(rt: RefinedType) => b1(rt) & b2(rt)
210+
}
211+
val app1: RefinedType => Type = rt => tp1.appliedTo(argRefs(rt, tparams1.length))
212+
val app2: RefinedType => Type = rt => tp2.appliedTo(argRefs(rt, tparams2.length))
213+
val body: RefinedType => Type = rt => op(app1(rt), app2(rt))
214+
TypeLambda(variances, bounds, body)
215+
}
178216
}
179217

180218
import TypeApplications._
@@ -273,6 +311,14 @@ class TypeApplications(val self: Type) extends AnyVal {
273311
false
274312
}
275313

314+
/** Replace references to type parameters with references to hk arguments `this.$hk_i`
315+
* Care is needed not to cause cyclic reference errors, hence `SafeSubstMap`.
316+
*/
317+
private[TypeApplications] def internalizeFrom[T <: Type](tparams: List[Symbol])(implicit ctx: Context): RefinedType => T =
318+
(rt: RefinedType) =>
319+
new ctx.SafeSubstMap(tparams , argRefs(rt, tparams.length))
320+
.apply(self).asInstanceOf[T]
321+
276322
/** Lambda abstract `self` with given type parameters. Examples:
277323
*
278324
* type T[X] = U becomes type T = [X] -> U

src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 77 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -144,49 +144,43 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
144144
def compareNamed(tp1: Type, tp2: NamedType): Boolean = {
145145
implicit val ctx: Context = this.ctx
146146
tp2.info match {
147-
case info2: TypeAlias if tp2.prefix.isStable =>
148-
// If prefix is not stable (i.e. is not a path), then we have a true
149-
// projection `T # A` which is treated as the existential type
150-
// `ex(x: T)x.A`. We need to deal with the existential first before
151-
// following the alias. If we did follow the alias we could be
152-
// unsound as well as incomplete. An example of this was discovered in Iter2.scala.
153-
// It failed to validate the subtype test
154-
//
155-
// (([+X] -> Seq[X]) & C)[SA] <: C[SA]
156-
//
157-
// Both sides are projections of $Apply. The left $Apply does have an
158-
// aliased info, namely, Seq[SA]. But that is not a subtype of C[SA].
159-
// The problem is that, with the prefix not being a path, an aliased info
160-
// does not necessarily give all of the information of the original projection.
161-
// So we can't follow the alias without a backup strategy. If the alias
162-
// would appear on the right then I believe this can be turned into a case
163-
// of unsoundness.
164-
isSubType(tp1, info2.alias)
147+
case info2: TypeAlias => isSubType(tp1, info2.alias)
165148
case _ => tp1 match {
166149
case tp1: NamedType =>
167150
tp1.info match {
168-
case info1: TypeAlias if tp1.prefix.isStable =>
169-
isSubType(info1.alias, tp2)
151+
case info1: TypeAlias =>
152+
if (isSubType(info1.alias, tp2)) return true
153+
if (tp1.prefix.isStable) return false
154+
// If tp1.prefix is stable, the alias does contain all information about the original ref, so
155+
// there's no need to try something else. (This is important for performance).
156+
// To see why we cannot in general stop here, consider:
157+
//
158+
// trait C { type A }
159+
// trait D { type A = String }
160+
// (C & D)#A <: C#A
161+
//
162+
// Following the alias leads to the judgment `String <: C#A` which is false.
163+
// However the original judgment should be true.
170164
case _ =>
171-
val sym1 = tp1.symbol
172-
if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol))
173-
ctx.erasedTypes ||
174-
sym1.isStaticOwner ||
175-
isSubType(tp1.prefix, tp2.prefix) ||
176-
thirdTryNamed(tp1, tp2)
177-
else
178-
( (tp1.name eq tp2.name)
179-
&& isSubType(tp1.prefix, tp2.prefix)
180-
&& tp1.signature == tp2.signature
181-
&& !tp1.isInstanceOf[WithFixedSym]
182-
&& !tp2.isInstanceOf[WithFixedSym]
183-
) ||
184-
compareHK(tp1, tp2, inOrder = true) ||
185-
compareHK(tp2, tp1, inOrder = false) ||
186-
thirdTryNamed(tp1, tp2)
187165
}
166+
val sym1 = tp1.symbol
167+
if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol))
168+
ctx.erasedTypes ||
169+
sym1.isStaticOwner ||
170+
isSubType(tp1.prefix, tp2.prefix) ||
171+
thirdTryNamed(tp1, tp2)
172+
else
173+
( (tp1.name eq tp2.name)
174+
&& isSubType(tp1.prefix, tp2.prefix)
175+
&& tp1.signature == tp2.signature
176+
&& !tp1.isInstanceOf[WithFixedSym]
177+
&& !tp2.isInstanceOf[WithFixedSym]
178+
) ||
179+
compareHkApply(tp1, tp2, inOrder = true) ||
180+
compareHkApply(tp2, tp1, inOrder = false) ||
181+
thirdTryNamed(tp1, tp2)
188182
case _ =>
189-
compareHK(tp2, tp1, inOrder = false) ||
183+
compareHkApply(tp2, tp1, inOrder = false) ||
190184
secondTry(tp1, tp2)
191185
}
192186
}
@@ -258,11 +252,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
258252
private def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match {
259253
case tp1: NamedType =>
260254
tp1.info match {
261-
case info1: TypeAlias => isSubType(info1.alias, tp2)
255+
case info1: TypeAlias =>
256+
if (isSubType(info1.alias, tp2)) return true
257+
if (tp1.prefix.isStable) return false
262258
case _ =>
263-
compareHK(tp1, tp2, inOrder = true) ||
264-
thirdTry(tp1, tp2)
265259
}
260+
compareHkApply(tp1, tp2, inOrder = true) ||
261+
thirdTry(tp1, tp2)
266262
case tp1: PolyParam =>
267263
def flagNothingBound = {
268264
if (!frozenConstraint && tp2.isRef(defn.NothingClass) && state.isGlobalCommittable) {
@@ -356,8 +352,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
356352
isSubType(tp1, tp2.parent) &&
357353
(name2 == nme.WILDCARD || hasMatchingMember(name2, tp1, tp2))
358354
}
359-
def etaExpandedSubType(tp1: Type) =
360-
isSubType(tp1.typeConstructor.EtaExpand(tp2.typeParams), tp2)
361355
def compareRefined: Boolean = {
362356
val tp1w = tp1.widen
363357
val skipped2 = skipMatching(tp1w, tp2)
@@ -371,7 +365,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
371365
case _ =>
372366
compareRefinedSlow ||
373367
fourthTry(tp1, tp2) ||
374-
needsEtaLift(tp1, tp2) && testLifted(tp1, tp2, tp2.typeParams, etaExpandedSubType)
368+
compareHkLambda(tp2, tp1, inOrder = false)
375369
}
376370
else // fast path, in particular for refinements resulting from parameterization.
377371
isSubType(tp1, skipped2) &&
@@ -491,10 +485,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
491485
}
492486
isNewSubType(tp1.underlying.widenExpr, tp2) || comparePaths
493487
case tp1: RefinedType =>
494-
isNewSubType(tp1.parent, tp2) ||
495-
needsEtaLift(tp2, tp1) &&
496-
tp2.typeParams.length == tp1.typeParams.length &&
497-
isSubType(tp1, tp2.EtaExpand(tp1.typeParams))
488+
isNewSubType(tp1.parent, tp2) || compareHkLambda(tp1, tp2, inOrder = true)
498489
case AndType(tp11, tp12) =>
499490
// Rewrite (T111 | T112) & T12 <: T2 to (T111 & T12) <: T2 and (T112 | T12) <: T2
500491
// and analogously for T11 & (T121 | T122) & T12 <: T2
@@ -525,14 +516,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
525516
false
526517
}
527518

528-
/** Does `tp` need to be eta lifted to be comparable to `target`?
529-
* This is the case if:
530-
* - target is a type lambda, and
531-
* - `tp` is eta-expandable (i.e. is a non-lambda class ref)
532-
*/
533-
private def needsEtaLift(tp: Type, target: RefinedType): Boolean =
534-
target.refinedName == tpnme.hkApply && tp.isEtaExpandable
535-
536519
/** Test whether `tp1` has a base type of the form `B[T1, ..., Tn]` where
537520
* - `B` derives from one of the class symbols of `tp2`,
538521
* - the type parameters of `B` match one-by-one the variances of `tparams`,
@@ -574,7 +557,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
574557
*
575558
* (4) If `inOrder`, test `projection <: other` else test `other <: projection`.
576559
*/
577-
def compareHK(projection: NamedType, other: Type, inOrder: Boolean): Boolean = {
560+
def compareHkApply(projection: NamedType, other: Type, inOrder: Boolean): Boolean = {
578561
def tryInfer(tp: Type): Boolean = ctx.traceIndented(i"compareHK($projection, $other, inOrder = $inOrder, constr = $tp)", subtyping) {
579562
tp match {
580563
case tp: TypeVar => tryInfer(tp.underlying)
@@ -609,6 +592,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
609592
tryInfer(projection.prefix.typeConstructor.dealias)
610593
}
611594

595+
/** Compare type lambda with non-lambda type. */
596+
def compareHkLambda(rt: RefinedType, other: Type, inOrder: Boolean) = rt match {
597+
case TypeLambda(vs, args, body) =>
598+
other.isInstanceOf[TypeRef] &&
599+
args.length == other.typeParams.length && {
600+
val applied = other.appliedTo(argRefs(rt, args.length))
601+
if (inOrder) isSubType(body, applied) else isSubType(applied, body)
602+
}
603+
case _ =>
604+
false
605+
}
606+
612607
/** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time
613608
* to keep the constraint as wide as possible. Specifically, if
614609
*
@@ -1007,12 +1002,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
10071002
val t2 = distributeAnd(tp2, tp1)
10081003
if (t2.exists) t2
10091004
else if (erased) erasedGlb(tp1, tp2, isJava = false)
1010-
else {
1011-
//if (isHKRef(tp1)) tp2
1012-
//else if (isHKRef(tp2)) tp1
1013-
//else
1014-
AndType(tp1, tp2)
1015-
}
1005+
else liftIfHK(tp1, tp2, AndType(_, _))
10161006
}
10171007
}
10181008

@@ -1036,14 +1026,23 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
10361026
val t2 = distributeOr(tp2, tp1)
10371027
if (t2.exists) t2
10381028
else if (erased) erasedLub(tp1, tp2)
1039-
else
1040-
//if (isHKRef(tp1)) tp1
1041-
//else if (isHKRef(tp2)) tp2
1042-
//else
1043-
OrType(tp1, tp2)
1029+
else liftIfHK(tp1, tp2, OrType(_, _))
10441030
}
10451031
}
10461032

1033+
/** `op(tp1, tp2)` unless `tp1` and `tp2` are type-constructors.
1034+
* In the latter case, combine `tp1` and `tp2` under a type lambda like this:
1035+
*
1036+
* [X1, ..., Xn] -> op(tp1[X1, ..., Xn], tp2[X1, ..., Xn])
1037+
*/
1038+
private def liftIfHK(tp1: Type, tp2: Type, op: (Type, Type) => Type) = {
1039+
val tparams1 = tp1.typeParams
1040+
val tparams2 = tp2.typeParams
1041+
if (tparams1.isEmpty || tparams2.isEmpty) op(tp1, tp2)
1042+
else if (tparams1.length != tparams2.length) mergeConflict(tp1, tp2)
1043+
else hkCombine(tp1, tp2, tparams1, tparams2, op)
1044+
}
1045+
10471046
/** Try to distribute `&` inside type, detect and handle conflicts */
10481047
private def distributeAnd(tp1: Type, tp2: Type): Type = tp1 match {
10491048
// opportunistically merge same-named refinements
@@ -1106,18 +1105,15 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
11061105
NoType
11071106
}
11081107

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

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

1328-
override def compareHK(projection: NamedType, other: Type, inOrder: Boolean) =
1324+
override def compareHkApply(projection: NamedType, other: Type, inOrder: Boolean) =
13291325
if (projection.name == tpnme.hkApply)
13301326
traceIndented(i"compareHK $projection, $other, $inOrder") {
1331-
super.compareHK(projection, other, inOrder)
1327+
super.compareHkApply(projection, other, inOrder)
13321328
}
1333-
else super.compareHK(projection, other, inOrder)
1329+
else super.compareHkApply(projection, other, inOrder)
13341330

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

0 commit comments

Comments
 (0)