Skip to content

Commit 661da00

Browse files
committed
Eta expand according to expected type parameter variance
When eta expanding a type `C` to `[vX] => C[X]` the variance `v` is now the variance of the expected type, not the variance of the type constructor `C`, as long as the variance of the expected type is compatible with the variance of `C`.
1 parent 522c1f2 commit 661da00

File tree

3 files changed

+66
-15
lines changed

3 files changed

+66
-15
lines changed

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -524,29 +524,36 @@ class TypeApplications(val self: Type) extends AnyVal {
524524
}
525525
}
526526

527-
/** Convert a type constructor `TC` with type parameters `T1, ..., Tn` to
527+
/** Convert a type constructor `TC` which has type parameters `T1, ..., Tn`
528+
* in a context where type parameters `U1,...,Un` are expected to
528529
*
529530
* LambdaXYZ { Apply = TC[hk$0, ..., hk$n] }
530531
*
531-
* where XYZ is a corresponds to the variances of the type parameters.
532+
* Here, XYZ corresponds to the variances of
533+
* - `U1,...,Un` if the variances of `T1,...,Tn` are pairwise compatible with `U1,...,Un`,
534+
* - `T1,...,Tn` otherwise.
535+
* v1 is compatible with v2, if v1 = v2 or v2 is non-variant.
532536
*/
533-
def EtaExpand(implicit ctx: Context): Type = {
534-
val tparams = typeParams
535-
self.appliedTo(tparams map (_.typeRef)).LambdaAbstract(tparams)
537+
def EtaExpand(tparams: List[Symbol])(implicit ctx: Context): Type = {
538+
def varianceCompatible(actual: Symbol, formal: Symbol) =
539+
formal.variance == 0 || actual.variance == formal.variance
540+
val tparamsToUse =
541+
if (typeParams.corresponds(tparams)(varianceCompatible)) tparams else typeParams
542+
self.appliedTo(tparams map (_.typeRef)).LambdaAbstract(tparamsToUse)
536543
//.ensuring(res => res.EtaReduce =:= self, s"res = $res, core = ${res.EtaReduce}, self = $self, hc = ${res.hashCode}")
537544
}
538545

539546
/** Eta expand if `bound` is a higher-kinded type */
540547
def EtaExpandIfHK(bound: Type)(implicit ctx: Context): Type =
541-
if (bound.isHK && !isHK && self.typeSymbol.isClass && typeParams.nonEmpty) EtaExpand
548+
if (bound.isHK && !isHK && self.typeSymbol.isClass && typeParams.nonEmpty) EtaExpand(bound.typeParams)
542549
else self
543550

544551
/** Eta expand the prefix in front of any refinements. */
545552
def EtaExpandCore(implicit ctx: Context): Type = self.stripTypeVar match {
546553
case self: RefinedType =>
547554
self.derivedRefinedType(self.parent.EtaExpandCore, self.refinedName, self.refinedInfo)
548555
case _ =>
549-
self.EtaExpand
556+
self.EtaExpand(self.typeParams)
550557
}
551558

552559
/** If `self` is a (potentially partially instantiated) eta expansion of type T, return T,
@@ -645,7 +652,7 @@ class TypeApplications(val self: Type) extends AnyVal {
645652
param2.variance == param2.variance || param2.variance == 0
646653
if (classBounds.exists(tycon.derivesFrom(_)) &&
647654
tycon.typeParams.corresponds(tparams)(variancesMatch)) {
648-
val expanded = tycon.EtaExpand
655+
val expanded = tycon.EtaExpand(tparams)
649656
val lifted = (expanded /: targs) { (partialInst, targ) =>
650657
val tparam = partialInst.typeParams.head
651658
RefinedType(partialInst, tparam.name, targ.bounds.withVariance(tparam.variance))
@@ -659,7 +666,7 @@ class TypeApplications(val self: Type) extends AnyVal {
659666
false
660667
}
661668
tparams.nonEmpty &&
662-
(typeParams.nonEmpty && p(EtaExpand) ||
669+
(typeParams.hasSameLengthAs(tparams) && p(EtaExpand(tparams)) ||
663670
classBounds.nonEmpty && tryLift(self.baseClasses))
664671
}
665672
}

src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,51 @@ object Scala2Unpickler {
147147
denot.info = ClassInfo( // final info
148148
denot.owner.thisType, denot.classSymbol, parentRefs, declsInRightOrder, ost)
149149
}
150+
151+
/** Adapt arguments to type parameters so that variance of type lambda arguments
152+
* agrees with variance of corresponding higherkinded type parameters. Example:
153+
*
154+
* class Companion[+CC[X]]
155+
* Companion[List]
156+
*
157+
* with adaptArgs, this will expand to
158+
*
159+
* Companion[[X] => List[X]]
160+
*
161+
* instead of
162+
*
163+
* Companion[[+X] => List[X]]
164+
*
165+
* even though `List` is covariant. This adaptation is necessary to ignore conflicting
166+
* variances in overriding members that have types of hk-type parameters such as `Companion[GenTraversable]`
167+
* or `Companion[ListBuffer]`. Without the adaptation we would end up with
168+
*
169+
* Companion[[+X] => GenTraversable[X]]
170+
* Companion[[X] => List[X]]
171+
*
172+
* and the second is not a subtype of the first. So if we have overridding memebrs of the two
173+
* types we get an error.
174+
*/
175+
def adaptArgs(tparams: List[Symbol], args: List[Type])(implicit ctx: Context): List[Type] = tparams match {
176+
case tparam :: tparams1 =>
177+
val boundLambda = tparam.infoOrCompleter match {
178+
case TypeBounds(_, hi) => hi.LambdaClass(forcing = false)
179+
case _ => NoSymbol
180+
}
181+
def adaptArg(arg: Type): Type = arg match {
182+
case arg: TypeRef if arg.symbol.isLambdaTrait =>
183+
assert(arg.symbol.typeParams.length == boundLambda.typeParams.length)
184+
arg.prefix.select(boundLambda)
185+
case arg: RefinedType =>
186+
arg.derivedRefinedType(adaptArg(arg.parent), arg.refinedName, arg.refinedInfo)
187+
case _ =>
188+
arg
189+
}
190+
val arg = args.head
191+
val adapted = if (boundLambda.exists) adaptArg(arg) else arg
192+
adapted :: adaptArgs(tparams1, args.tail)
193+
case nil => args
194+
}
150195
}
151196

152197
/** Unpickle symbol table information descending from a class and/or module root
@@ -719,8 +764,8 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
719764
else TypeRef(pre, sym.name.asTypeName)
720765
val args = until(end, readTypeRef)
721766
if (sym == defn.ByNameParamClass2x) ExprType(args.head)
722-
else if (args.isEmpty && sym.typeParams.nonEmpty) tycon.EtaExpand
723-
else tycon.appliedTo(args)
767+
else if (args.isEmpty && sym.typeParams.nonEmpty) tycon.EtaExpand(sym.typeParams)
768+
else tycon.appliedTo(adaptArgs(sym.typeParams, args))
724769
case TYPEBOUNDStpe =>
725770
TypeBounds(readTypeRef(), readTypeRef())
726771
case REFINEDtpe =>

test/dotc/scala-collections.whitelist

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@
8282
./scala-scala/src/library/scala/collection/immutable/Queue.scala
8383

8484
# https://github.com/lampepfl/dotty/issues/916
85-
#./scala-scala/src/library/scala/collection/immutable/Seq.scala
86-
#./scala-scala/src/library/scala/collection/mutable/IndexedSeq.scala
87-
#./scala-scala/src/library/scala/collection/mutable/ListBuffer.scala
85+
./scala-scala/src/library/scala/collection/immutable/Seq.scala
86+
./scala-scala/src/library/scala/collection/mutable/IndexedSeq.scala
87+
./scala-scala/src/library/scala/collection/mutable/ListBuffer.scala
8888

8989
./scala-scala/src/library/scala/collection/immutable/Stack.scala
9090
./scala-scala/src/library/scala/collection/immutable/StringLike.scala
@@ -224,7 +224,6 @@
224224
./scala-scala/src/library/scala/collection/immutable/NumericRange.scala
225225
./scala-scala/src/library/scala/collection/immutable/Range.scala
226226
./scala-scala/src/library/scala/collection/immutable/RedBlackTree.scala
227-
./scala-scala/src/library/scala/collection/immutable/Seq.scala
228227

229228
# uses refinements that dotty does not support
230229
#./scala-scala/src/library/scala/collection/immutable/Set.scala

0 commit comments

Comments
 (0)