Skip to content

Commit d6d4e87

Browse files
Erasure/Scala2Unpickler hacks to restore binary compatibility
To stay binary compatibility with scala 2.11 binaries, we hijack the unpickling of types to fake an HList structure. Later on in erasure, HList types are eraised back to scala.TupleN (for N <= 22). In addition because the actual scala.TupleN classes are neither `<: Tuple` now `<: TupleCons`, these types needs to be systematically araised to Object. In addition to all these carefully compensating hacks, this also imposes a new contain: the dotty-library, which defines several `Tuple/TupleCons` methods, can should now *always* be compiled by dotty itself. Indeed, when compiled with scalac, these types will stay as they are instead of being eraised to Object.
1 parent 37ae92b commit d6d4e87

File tree

7 files changed

+309
-22
lines changed

7 files changed

+309
-22
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ object Definitions {
1717
* The limit 22 is chosen for Scala2x interop. It could be something
1818
* else without affecting the set of programs that can be compiled.
1919
*/
20-
val MaxImplementedTupleArity = 6
20+
val MaxImplementedTupleArity = 22
2121

2222
/** The maximum arity N of a function type that's implemented
2323
* as a trait `scala.FunctionN`. Functions of higher arity are possible,

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,10 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
325325

326326
/** The erasure |T| of a type T. This is:
327327
*
328+
* - For dotty.TupleCons:
329+
* - if lenght staticaly known as N, TupleN
330+
* - otherwise Product
331+
* - For dotty.Tuple, java.lang.Object
328332
* - For a refined type scala.Array+[T]:
329333
* - if T is Nothing or Null, []Object
330334
* - otherwise, if T <: Object, []|T|
@@ -356,6 +360,27 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
356360
* - For any other type, exception.
357361
*/
358362
private def apply(tp: Type)(implicit ctx: Context): Type = tp match {
363+
case _ if tp.isRef(defn.TupleConsType.symbol) =>
364+
// Compute the arity of a tuple type, -1 if it's not statically known.
365+
def tupleArity(t: Type, acc: Int = 0): Int = t match {
366+
case RefinedType(RefinedType(_, _, TypeAlias(headType)), _, TypeAlias(tailType)) =>
367+
tupleArity(tailType, acc + 1)
368+
case _ if t.isRef(defn.UnitType.symbol) =>
369+
acc
370+
case AnnotatedType(tpe, _) =>
371+
tupleArity(tpe, acc)
372+
case tp: TypeProxy =>
373+
tupleArity(tp.underlying, acc)
374+
case t =>
375+
-1
376+
}
377+
val arity = tupleArity(tp)
378+
if (arity > 0 && arity <= Definitions.MaxImplementedTupleArity)
379+
defn.TupleNType(arity)
380+
else
381+
defn.ProductType
382+
case _ if tp.isRef(defn.TupleType.symbol) =>
383+
defn.ObjectType
359384
case _: ErasedValueType =>
360385
tp
361386
case tp: TypeRef =>
@@ -469,7 +494,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
469494
// constructor method should not be semi-erased.
470495
else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp)
471496
else this(tp)
472-
case RefinedType(parent, _, _) if !(parent isRef defn.ArrayClass) =>
497+
case RefinedType(parent, _, _) if !(parent isRef defn.ArrayClass) && !(tp isRef defn.TupleConsType.symbol) =>
473498
eraseResult(parent)
474499
case _ =>
475500
this(tp)

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import classfile.ClassfileParser
2828
import scala.collection.{ mutable, immutable }
2929
import scala.collection.mutable.ListBuffer
3030
import scala.annotation.switch
31+
import transform.TupleRewrites.UnfoldedTupleType
3132

3233
object Scala2Unpickler {
3334

@@ -99,7 +100,21 @@ object Scala2Unpickler {
99100
else selfInfo
100101
val tempInfo = new TempClassInfo(denot.owner.thisType, denot.classSymbol, decls, ost)
101102
denot.info = tempInfo // first rough info to avoid CyclicReferences
102-
var parentRefs = ctx.normalizeToClassRefs(parents, cls, decls)
103+
104+
val tupleArity = defn.TupleNSymbol.indexOf(cls)
105+
val tupledParents =
106+
// Changing this test to `cls == defn.UnitClass` leads to initialisation cycle with Any:
107+
if (denot.symbol.associatedFile.path == "scala/Unit.class")
108+
parents :+ defn.TupleType
109+
else if (tupleArity != -1) {
110+
val productClass = defn.ProductNType(tupleArity).classSymbol
111+
val productTypes = parents.collect { case t: RefinedType => t.baseArgTypes(productClass) }.head
112+
val tupleType = UnfoldedTupleType(productTypes).folded.asTupleConsType
113+
parents :+ tupleType
114+
} else parents
115+
116+
var parentRefs = ctx.normalizeToClassRefs(tupledParents, cls, decls)
117+
103118
if (parentRefs.isEmpty) parentRefs = defn.ObjectType :: Nil
104119
for (tparam <- tparams) {
105120
val tsym = decls.lookup(tparam.name)
@@ -738,7 +753,9 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
738753
}
739754
else TypeRef(pre, sym.name.asTypeName)
740755
val args = until(end, readTypeRef)
741-
if (sym == defn.ByNameParamClass2x) ExprType(args.head)
756+
val isTupleClass = """Tuple\d+.class""".r.findFirstIn(source.name).nonEmpty
757+
if (!isTupleClass && defn.TupleNSymbol.contains(sym)) UnfoldedTupleType(args).folded.asTupleConsType
758+
else if (sym == defn.ByNameParamClass2x) ExprType(args.head)
742759
else if (args.nonEmpty) tycon.safeAppliedTo(EtaExpandIfHK(sym.typeParams, args))
743760
else if (sym.typeParams.nonEmpty) tycon.EtaExpand(sym.typeParams)
744761
else tycon

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,13 @@ object Erasure extends TypeTestsCasts {
403403
}
404404
}
405405

406-
recur(typed(tree.qualifier, AnySelectionProto))
406+
if (defn.DottyTupleNModule contains tree.qualifier.symbol) {
407+
val arity = defn.DottyTupleNModule.indexOf(tree.qualifier.symbol)
408+
val tupleCompanion = defn.TupleNType(arity).classSymbol.companionModule.symbol
409+
ref(tupleCompanion).select(tree.name).withPos(tree.pos)
410+
} else {
411+
recur(typed(tree.qualifier, AnySelectionProto))
412+
}
407413
}
408414

409415
override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =

0 commit comments

Comments
 (0)