@@ -144,49 +144,43 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
144
144
def compareNamed (tp1 : Type , tp2 : NamedType ): Boolean = {
145
145
implicit val ctx : Context = this .ctx
146
146
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)
165
148
case _ => tp1 match {
166
149
case tp1 : NamedType =>
167
150
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.
170
164
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)
187
165
}
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)
188
182
case _ =>
189
- compareHK (tp2, tp1, inOrder = false ) ||
183
+ compareHkApply (tp2, tp1, inOrder = false ) ||
190
184
secondTry(tp1, tp2)
191
185
}
192
186
}
@@ -258,11 +252,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
258
252
private def secondTry (tp1 : Type , tp2 : Type ): Boolean = tp1 match {
259
253
case tp1 : NamedType =>
260
254
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
262
258
case _ =>
263
- compareHK(tp1, tp2, inOrder = true ) ||
264
- thirdTry(tp1, tp2)
265
259
}
260
+ compareHkApply(tp1, tp2, inOrder = true ) ||
261
+ thirdTry(tp1, tp2)
266
262
case tp1 : PolyParam =>
267
263
def flagNothingBound = {
268
264
if (! frozenConstraint && tp2.isRef(defn.NothingClass ) && state.isGlobalCommittable) {
@@ -356,8 +352,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
356
352
isSubType(tp1, tp2.parent) &&
357
353
(name2 == nme.WILDCARD || hasMatchingMember(name2, tp1, tp2))
358
354
}
359
- def etaExpandedSubType (tp1 : Type ) =
360
- isSubType(tp1.typeConstructor.EtaExpand (tp2.typeParams), tp2)
361
355
def compareRefined : Boolean = {
362
356
val tp1w = tp1.widen
363
357
val skipped2 = skipMatching(tp1w, tp2)
@@ -371,7 +365,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
371
365
case _ =>
372
366
compareRefinedSlow ||
373
367
fourthTry(tp1, tp2) ||
374
- needsEtaLift(tp1, tp2) && testLifted( tp1, tp2, tp2.typeParams, etaExpandedSubType )
368
+ compareHkLambda(tp2, tp1, inOrder = false )
375
369
}
376
370
else // fast path, in particular for refinements resulting from parameterization.
377
371
isSubType(tp1, skipped2) &&
@@ -491,10 +485,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
491
485
}
492
486
isNewSubType(tp1.underlying.widenExpr, tp2) || comparePaths
493
487
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 )
498
489
case AndType (tp11, tp12) =>
499
490
// Rewrite (T111 | T112) & T12 <: T2 to (T111 & T12) <: T2 and (T112 | T12) <: T2
500
491
// and analogously for T11 & (T121 | T122) & T12 <: T2
@@ -525,14 +516,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
525
516
false
526
517
}
527
518
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
-
536
519
/** Test whether `tp1` has a base type of the form `B[T1, ..., Tn]` where
537
520
* - `B` derives from one of the class symbols of `tp2`,
538
521
* - 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 {
574
557
*
575
558
* (4) If `inOrder`, test `projection <: other` else test `other <: projection`.
576
559
*/
577
- def compareHK (projection : NamedType , other : Type , inOrder : Boolean ): Boolean = {
560
+ def compareHkApply (projection : NamedType , other : Type , inOrder : Boolean ): Boolean = {
578
561
def tryInfer (tp : Type ): Boolean = ctx.traceIndented(i " compareHK( $projection, $other, inOrder = $inOrder, constr = $tp) " , subtyping) {
579
562
tp match {
580
563
case tp : TypeVar => tryInfer(tp.underlying)
@@ -609,6 +592,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
609
592
tryInfer(projection.prefix.typeConstructor.dealias)
610
593
}
611
594
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
+
612
607
/** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time
613
608
* to keep the constraint as wide as possible. Specifically, if
614
609
*
@@ -1007,12 +1002,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
1007
1002
val t2 = distributeAnd(tp2, tp1)
1008
1003
if (t2.exists) t2
1009
1004
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 (_, _))
1016
1006
}
1017
1007
}
1018
1008
@@ -1036,14 +1026,23 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
1036
1026
val t2 = distributeOr(tp2, tp1)
1037
1027
if (t2.exists) t2
1038
1028
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 (_, _))
1044
1030
}
1045
1031
}
1046
1032
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
+
1047
1046
/** Try to distribute `&` inside type, detect and handle conflicts */
1048
1047
private def distributeAnd (tp1 : Type , tp2 : Type ): Type = tp1 match {
1049
1048
// opportunistically merge same-named refinements
@@ -1106,18 +1105,15 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
1106
1105
NoType
1107
1106
}
1108
1107
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
+ */
1110
1116
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
- }
1121
1117
case tp1 : TypeBounds =>
1122
1118
tp2 match {
1123
1119
case tp2 : TypeBounds => tp1 | tp2
@@ -1325,12 +1321,12 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
1325
1321
1326
1322
override def copyIn (ctx : Context ) = new ExplainingTypeComparer (ctx)
1327
1323
1328
- override def compareHK (projection : NamedType , other : Type , inOrder : Boolean ) =
1324
+ override def compareHkApply (projection : NamedType , other : Type , inOrder : Boolean ) =
1329
1325
if (projection.name == tpnme.hkApply)
1330
1326
traceIndented(i " compareHK $projection, $other, $inOrder" ) {
1331
- super .compareHK (projection, other, inOrder)
1327
+ super .compareHkApply (projection, other, inOrder)
1332
1328
}
1333
- else super .compareHK (projection, other, inOrder)
1329
+ else super .compareHkApply (projection, other, inOrder)
1334
1330
1335
1331
override def toString = " Subtype trace:" + { try b.toString finally b.clear() }
1336
1332
}
0 commit comments