Skip to content

Commit d362455

Browse files
committed
Fix higher-kinded unions and intersections
Since And/Or type themselves are parameterless, their the union and intersection of hgiher-kinded types has to be treated specially: The types have to be pulled under a common lambda.
1 parent 570cf0f commit d362455

File tree

3 files changed

+83
-35
lines changed

3 files changed

+83
-35
lines changed

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,45 @@ 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+
* Produces the type lambda
184+
*
185+
* [v1 X1 B1, ..., vn Xn Bn] -> op(tp1[X1, ..., Xn], tp2[X1, ..., Xn])
186+
*
187+
* where
188+
*
189+
* - variances `vi` are the variances of corresponding type parameters for `tp1`
190+
* or `tp2`, or are 0 of the latter disagree.
191+
* - bounds `Bi` are the intersection of the corresponding type parameter bounds
192+
* of `tp1` and `tp2`.
193+
*/
194+
def hkCombine(tp1: Type, tp2: Type,
195+
tparams1: List[TypeSymbol], tparams2: List[TypeSymbol], op: (Type, Type) => Type)
196+
(implicit ctx: Context): Type = {
197+
val variances = (tparams1, tparams2).zipped.map { (tparam1, tparam2) =>
198+
val v1 = tparam1.variance
199+
val v2 = tparam2.variance
200+
if (v1 == v2) v1 else 0
201+
}
202+
val bounds: List[RefinedType => TypeBounds] =
203+
(tparams1, tparams2).zipped.map { (tparam1, tparam2) =>
204+
val b1: RefinedType => TypeBounds =
205+
tp1.memberInfo(tparam1).bounds.internalizeFrom(tparams1)
206+
val b2: RefinedType => TypeBounds =
207+
tp2.memberInfo(tparam2).bounds.internalizeFrom(tparams2)
208+
(rt: RefinedType) => b1(rt) & b2(rt)
209+
}
210+
val app1: RefinedType => Type = rt => tp1.appliedTo(argRefs(rt, tparams1.length))
211+
val app2: RefinedType => Type = rt => tp2.appliedTo(argRefs(rt, tparams2.length))
212+
val body = (rt: RefinedType) => op(app1(rt), app2(rt))
213+
TypeLambda(variances, bounds, body)
214+
}
178215
}
179216

180217
import TypeApplications._
@@ -273,6 +310,14 @@ class TypeApplications(val self: Type) extends AnyVal {
273310
false
274311
}
275312

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

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

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
176176
&& !tp1.isInstanceOf[WithFixedSym]
177177
&& !tp2.isInstanceOf[WithFixedSym]
178178
) ||
179-
compareHK(tp1, tp2, inOrder = true) ||
180-
compareHK(tp2, tp1, inOrder = false) ||
179+
compareHkApply(tp1, tp2, inOrder = true) ||
180+
compareHkApply(tp2, tp1, inOrder = false) ||
181181
thirdTryNamed(tp1, tp2)
182182
case _ =>
183-
compareHK(tp2, tp1, inOrder = false) ||
183+
compareHkApply(tp2, tp1, inOrder = false) ||
184184
secondTry(tp1, tp2)
185185
}
186186
}
@@ -257,7 +257,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
257257
if (tp1.prefix.isStable) return false
258258
case _ =>
259259
}
260-
compareHK(tp1, tp2, inOrder = true) ||
260+
compareHkApply(tp1, tp2, inOrder = true) ||
261261
thirdTry(tp1, tp2)
262262
case tp1: PolyParam =>
263263
def flagNothingBound = {
@@ -352,8 +352,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
352352
isSubType(tp1, tp2.parent) &&
353353
(name2 == nme.WILDCARD || hasMatchingMember(name2, tp1, tp2))
354354
}
355-
def etaExpandedSubType(tp1: Type) =
356-
isSubType(tp1.typeConstructor.EtaExpand(tp2.typeParams), tp2)
357355
def compareRefined: Boolean = {
358356
val tp1w = tp1.widen
359357
val skipped2 = skipMatching(tp1w, tp2)
@@ -367,7 +365,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
367365
case _ =>
368366
compareRefinedSlow ||
369367
fourthTry(tp1, tp2) ||
370-
needsEtaLift(tp1, tp2) && testLifted(tp1, tp2, tp2.typeParams, etaExpandedSubType)
368+
compareHkLambda(tp2, tp1, inOrder = false)
371369
}
372370
else // fast path, in particular for refinements resulting from parameterization.
373371
isSubType(tp1, skipped2) &&
@@ -487,10 +485,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
487485
}
488486
isNewSubType(tp1.underlying.widenExpr, tp2) || comparePaths
489487
case tp1: RefinedType =>
490-
isNewSubType(tp1.parent, tp2) ||
491-
needsEtaLift(tp2, tp1) &&
492-
tp2.typeParams.length == tp1.typeParams.length &&
493-
isSubType(tp1, tp2.EtaExpand(tp1.typeParams))
488+
isNewSubType(tp1.parent, tp2) || compareHkLambda(tp1, tp2, inOrder = true)
494489
case AndType(tp11, tp12) =>
495490
// Rewrite (T111 | T112) & T12 <: T2 to (T111 & T12) <: T2 and (T112 | T12) <: T2
496491
// and analogously for T11 & (T121 | T122) & T12 <: T2
@@ -521,14 +516,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
521516
false
522517
}
523518

524-
/** Does `tp` need to be eta lifted to be comparable to `target`?
525-
* This is the case if:
526-
* - target is a type lambda, and
527-
* - `tp` is eta-expandable (i.e. is a non-lambda class ref)
528-
*/
529-
private def needsEtaLift(tp: Type, target: RefinedType): Boolean =
530-
target.refinedName == tpnme.hkApply && tp.isEtaExpandable
531-
532519
/** Test whether `tp1` has a base type of the form `B[T1, ..., Tn]` where
533520
* - `B` derives from one of the class symbols of `tp2`,
534521
* - the type parameters of `B` match one-by-one the variances of `tparams`,
@@ -570,7 +557,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
570557
*
571558
* (4) If `inOrder`, test `projection <: other` else test `other <: projection`.
572559
*/
573-
def compareHK(projection: NamedType, other: Type, inOrder: Boolean): Boolean = {
560+
def compareHkApply(projection: NamedType, other: Type, inOrder: Boolean): Boolean = {
574561
def tryInfer(tp: Type): Boolean = ctx.traceIndented(i"compareHK($projection, $other, inOrder = $inOrder, constr = $tp)", subtyping) {
575562
tp match {
576563
case tp: TypeVar => tryInfer(tp.underlying)
@@ -605,6 +592,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
605592
tryInfer(projection.prefix.typeConstructor.dealias)
606593
}
607594

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+
608607
/** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time
609608
* to keep the constraint as wide as possible. Specifically, if
610609
*
@@ -1003,12 +1002,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
10031002
val t2 = distributeAnd(tp2, tp1)
10041003
if (t2.exists) t2
10051004
else if (erased) erasedGlb(tp1, tp2, isJava = false)
1006-
else {
1007-
//if (isHKRef(tp1)) tp2
1008-
//else if (isHKRef(tp2)) tp1
1009-
//else
1010-
AndType(tp1, tp2)
1011-
}
1005+
else liftIfHK(tp1, tp2, AndType(_, _))
10121006
}
10131007
}
10141008

@@ -1032,14 +1026,23 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
10321026
val t2 = distributeOr(tp2, tp1)
10331027
if (t2.exists) t2
10341028
else if (erased) erasedLub(tp1, tp2)
1035-
else
1036-
//if (isHKRef(tp1)) tp1
1037-
//else if (isHKRef(tp2)) tp2
1038-
//else
1039-
OrType(tp1, tp2)
1029+
else liftIfHK(tp1, tp2, OrType(_, _))
10401030
}
10411031
}
10421032

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+
10431046
/** Try to distribute `&` inside type, detect and handle conflicts */
10441047
private def distributeAnd(tp1: Type, tp2: Type): Type = tp1 match {
10451048
// opportunistically merge same-named refinements
@@ -1318,12 +1321,12 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
13181321

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

1321-
override def compareHK(projection: NamedType, other: Type, inOrder: Boolean) =
1324+
override def compareHkApply(projection: NamedType, other: Type, inOrder: Boolean) =
13221325
if (projection.name == tpnme.hkApply)
13231326
traceIndented(i"compareHK $projection, $other, $inOrder") {
1324-
super.compareHK(projection, other, inOrder)
1327+
super.compareHkApply(projection, other, inOrder)
13251328
}
1326-
else super.compareHK(projection, other, inOrder)
1329+
else super.compareHkApply(projection, other, inOrder)
13271330

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

src/dotty/tools/dotc/typer/TypeAssigner.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ trait TypeAssigner {
282282
else if (!mix.isEmpty) findMixinSuper(cls.info)
283283
else if (inConstrCall || ctx.erasedTypes) cls.info.firstParent
284284
else {
285-
val ps = cls.info.parents
285+
val ps = cls.classInfo.instantiatedParents
286286
if (ps.isEmpty) defn.AnyType else ps.reduceLeft((x: Type, y: Type) => x & y)
287287
}
288288
tree.withType(SuperType(cls.thisType, owntype))

0 commit comments

Comments
 (0)