Skip to content

Commit 00e396c

Browse files
Merge pull request #4938 from dotty-staging/add-tuples
Add support for generic tuples
2 parents c6687a3 + 42a5a9d commit 00e396c

40 files changed

+824
-157
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,23 @@ object desugar {
815815
makeOp(right, left, Position(op.pos.start, right.pos.end))
816816
}
817817

818+
/** Translate tuple expressions of arity <= 22
819+
*
820+
* () ==> ()
821+
* (t) ==> t
822+
* (t1, ..., tN) ==> TupleN(t1, ..., tN)
823+
*/
824+
def smallTuple(tree: Tuple)(implicit ctx: Context): Tree = {
825+
val ts = tree.trees
826+
val arity = ts.length
827+
assert(arity <= Definitions.MaxTupleArity)
828+
def tupleTypeRef = defn.TupleType(arity)
829+
if (arity == 1) ts.head
830+
else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts)
831+
else if (arity == 0) unitLiteral
832+
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
833+
}
834+
818835
/** Make closure corresponding to function.
819836
* params => body
820837
* ==>
@@ -1141,16 +1158,6 @@ object desugar {
11411158
case PrefixOp(op, t) =>
11421159
val nspace = if (ctx.mode.is(Mode.Type)) tpnme else nme
11431160
Select(t, nspace.UNARY_PREFIX ++ op.name)
1144-
case Tuple(ts) =>
1145-
val arity = ts.length
1146-
def tupleTypeRef = defn.TupleType(arity)
1147-
if (arity > Definitions.MaxTupleArity) {
1148-
ctx.error(TupleTooLong(ts), tree.pos)
1149-
unitLiteral
1150-
} else if (arity == 1) ts.head
1151-
else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts)
1152-
else if (arity == 0) unitLiteral
1153-
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
11541161
case WhileDo(cond, body) =>
11551162
// { <label> def while$(): Unit = if (cond) { body; while$() } ; while$() }
11561163
val call = Apply(Ident(nme.WHILE_PREFIX), Nil).withPos(tree.pos)

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,19 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
716716
Nil
717717
}
718718

719+
/** If `tree` is an instance of `TupleN[...](e1, ..., eN)`, the arguments `e1, ..., eN`
720+
* otherwise the empty list.
721+
*/
722+
def tupleArgs(tree: Tree)(implicit ctx: Context): List[Tree] = tree match {
723+
case Block(Nil, expr) => tupleArgs(expr)
724+
case Inlined(_, Nil, expr) => tupleArgs(expr)
725+
case Apply(fn, args)
726+
if fn.symbol.name == nme.apply &&
727+
fn.symbol.owner.is(Module) &&
728+
defn.isTupleClass(fn.symbol.owner.companionClass) => args
729+
case _ => Nil
730+
}
731+
719732
/** The qualifier part of a Select or Ident.
720733
* For an Ident, this is the `This` of the current class.
721734
*/

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

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,13 @@ trait ConstraintHandling {
251251
}
252252
}
253253

254-
/** The instance type of `param` in the current constraint (which contains `param`).
255-
* If `fromBelow` is true, the instance type is the lub of the parameter's
256-
* lower bounds; otherwise it is the glb of its upper bounds. However,
257-
* a lower bound instantiation can be a singleton type only if the upper bound
258-
* is also a singleton type.
254+
/** Widen inferred type `tp` with upper bound `bound`, according to the following rules:
255+
* 1. If `tp` is a singleton type, yet `bound` is not a singleton type, nor a subtype
256+
* of `scala.Singleton`, widen `tp`.
257+
* 2. If `tp` is a union type, yet upper bound is not a union type,
258+
* approximate the union type from above by an intersection of all common base types.
259259
*/
260-
def instanceType(param: TypeParamRef, fromBelow: Boolean): Type = {
261-
def upperBound = constraint.fullUpperBound(param)
260+
def widenInferred(tp: Type, bound: Type): Type = {
262261
def isMultiSingleton(tp: Type): Boolean = tp.stripAnnots match {
263262
case tp: SingletonType => true
264263
case AndType(tp1, tp2) => isMultiSingleton(tp1) | isMultiSingleton(tp2)
@@ -268,39 +267,32 @@ trait ConstraintHandling {
268267
case tp: TypeParamRef => isMultiSingleton(bounds(tp).hi)
269268
case _ => false
270269
}
271-
def isFullyDefined(tp: Type): Boolean = tp match {
272-
case tp: TypeVar => tp.isInstantiated && isFullyDefined(tp.instanceOpt)
273-
case tp: TypeProxy => isFullyDefined(tp.underlying)
274-
case tp: AndType => isFullyDefined(tp.tp1) && isFullyDefined(tp.tp2)
275-
case tp: OrType => isFullyDefined(tp.tp1) && isFullyDefined(tp.tp2)
276-
case _ => true
277-
}
278270
def isOrType(tp: Type): Boolean = tp.dealias match {
279271
case tp: OrType => true
280272
case tp: RefinedOrRecType => isOrType(tp.parent)
281273
case AndType(tp1, tp2) => isOrType(tp1) | isOrType(tp2)
282274
case WildcardType(bounds: TypeBounds) => isOrType(bounds.hi)
283275
case _ => false
284276
}
277+
def widenOr(tp: Type) =
278+
if (isOrType(tp) && !isOrType(bound)) tp.widenUnion
279+
else tp
280+
def widenSingle(tp: Type) =
281+
if (isMultiSingleton(tp) && !isMultiSingleton(bound) &&
282+
!isSubTypeWhenFrozen(bound, defn.SingletonType)) tp.widen
283+
else tp
284+
widenOr(widenSingle(tp))
285+
}
285286

286-
// First, solve the constraint.
287-
var inst = approximation(param, fromBelow).simplified
288-
289-
// Then, approximate by (1.) - (3.) and simplify as follows.
290-
// 1. If instance is from below and is a singleton type, yet upper bound is
291-
// not a singleton type or a subtype of `scala.Singleton`, widen the
292-
// instance.
293-
if (fromBelow && isMultiSingleton(inst) && !isMultiSingleton(upperBound)
294-
&& !isSubTypeWhenFrozen(upperBound, defn.SingletonType))
295-
inst = inst.widen
296-
297-
// 2. If instance is from below and is a fully-defined union type, yet upper bound
298-
// is not a union type, approximate the union type from above by an intersection
299-
// of all common base types.
300-
if (fromBelow && isOrType(inst) && !isOrType(upperBound))
301-
inst = inst.widenUnion
302-
303-
inst
287+
/** The instance type of `param` in the current constraint (which contains `param`).
288+
* If `fromBelow` is true, the instance type is the lub of the parameter's
289+
* lower bounds; otherwise it is the glb of its upper bounds. However,
290+
* a lower bound instantiation can be a singleton type only if the upper bound
291+
* is also a singleton type.
292+
*/
293+
def instanceType(param: TypeParamRef, fromBelow: Boolean): Type = {
294+
val inst = approximation(param, fromBelow).simplified
295+
if (fromBelow) widenInferred(inst, constraint.fullUpperBound(param)) else inst
304296
}
305297

306298
/** Constraint `c1` subsumes constraint `c2`, if under `c2` as constraint we have

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

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,18 @@ class Definitions {
706706

707707
lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope")
708708

709+
lazy val TupleTypeRef = ctx.requiredClassRef("scala.Tuple")
710+
def TupleClass(implicit ctx: Context) = TupleTypeRef.symbol.asClass
711+
712+
lazy val PairType = ctx.requiredClassRef("scala.*:")
713+
def PairClass(implicit ctx: Context) = PairType.symbol.asClass
714+
lazy val TupleXXLType = ctx.requiredClassRef("scala.TupleXXL")
715+
def TupleXXLClass(implicit ctx: Context) = TupleXXLType.symbol.asClass
716+
def TupleXXLModule(implicit ctx: Context) = TupleXXLClass.companionModule
717+
718+
def TupleXXL_apply(implicit ctx: Context) =
719+
TupleXXLModule.info.member(nme.apply).requiredSymbol(_.info.isVarArgsMethod)
720+
709721
// Annotation base classes
710722
lazy val AnnotationType = ctx.requiredClassRef("scala.annotation.Annotation")
711723
def AnnotationClass(implicit ctx: Context) = AnnotationType.symbol.asClass
@@ -880,7 +892,7 @@ class Definitions {
880892
private lazy val ImplementedFunctionType = mkArityArray("scala.Function", MaxImplementedFunctionArity, 0)
881893
def FunctionClassPerRun = new PerRun[Array[Symbol]](implicit ctx => ImplementedFunctionType.map(_.symbol.asClass))
882894

883-
lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2)
895+
lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 1)
884896

885897
def FunctionClass(n: Int, isImplicit: Boolean = false, isErased: Boolean = false)(implicit ctx: Context) =
886898
if (isImplicit && isErased)
@@ -901,8 +913,6 @@ class Definitions {
901913
if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes) && !isErased) ImplementedFunctionType(n)
902914
else FunctionClass(n, isImplicit, isErased).typeRef
903915

904-
private lazy val TupleTypes: Set[TypeRef] = TupleType.toSet
905-
906916
/** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */
907917
def scalaClassName(cls: Symbol)(implicit ctx: Context): TypeName =
908918
if (cls.isClass && cls.owner == ScalaPackageClass) cls.asClass.name else EmptyTypeName
@@ -1127,6 +1137,10 @@ class Definitions {
11271137
def isErasedFunctionType(tp: Type)(implicit ctx: Context) =
11281138
isFunctionType(tp) && tp.dealias.typeSymbol.name.isErasedFunction
11291139

1140+
/** A whitelist of Scala-2 classes that are known to be pure */
1141+
def isAssuredNoInits(sym: Symbol) =
1142+
(sym `eq` SomeClass) || isTupleClass(sym)
1143+
11301144
// ----- primitive value class machinery ------------------------------------------
11311145

11321146
/** This class would also be obviated by the implicit function type design */
@@ -1199,6 +1213,8 @@ class Definitions {
11991213
def isValueSubClass(sym1: Symbol, sym2: Symbol) =
12001214
valueTypeEnc(sym2.asClass.name) % valueTypeEnc(sym1.asClass.name) == 0
12011215

1216+
lazy val erasedToObject = Set[Symbol](AnyClass, AnyValClass, TupleClass, SingletonClass)
1217+
12021218
// ----- Initialization ---------------------------------------------------
12031219

12041220
/** Lists core classes that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */
@@ -1226,6 +1242,27 @@ class Definitions {
12261242

12271243
private[this] var isInitialized = false
12281244

1245+
/** Add a `Tuple` as a parent to `Unit`.
1246+
* Add the right `*:` instance as a parent to Tuple1..Tuple22
1247+
*/
1248+
def fixTupleCompleter(cls: ClassSymbol): Unit = cls.infoOrCompleter match {
1249+
case completer: LazyType =>
1250+
cls.info = new LazyType {
1251+
def syntheticParent(tparams: List[TypeSymbol]): Type =
1252+
if (tparams.isEmpty) TupleTypeRef
1253+
else (tparams :\ (UnitType: Type)) ((tparam, tail) => PairType.appliedTo(tparam.typeRef, tail))
1254+
override def complete(denot: SymDenotation)(implicit ctx: Context) = {
1255+
completer.complete(denot)
1256+
denot.info match {
1257+
case info: ClassInfo =>
1258+
denot.info = info.derivedClassInfo(
1259+
classParents = info.classParents :+ syntheticParent(cls.typeParams))
1260+
}
1261+
}
1262+
}
1263+
case _ =>
1264+
}
1265+
12291266
def init()(implicit ctx: Context) = {
12301267
this.ctx = ctx
12311268
if (!isInitialized) {
@@ -1243,6 +1280,10 @@ class Definitions {
12431280
// force initialization of every symbol that is synthesized or hijacked by the compiler
12441281
val forced = syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses()
12451282

1283+
fixTupleCompleter(UnitClass)
1284+
for (i <- 1 to MaxTupleArity)
1285+
fixTupleCompleter(TupleType(i).symbol.asClass)
1286+
12461287
isInitialized = true
12471288
}
12481289
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,8 @@ object SymDenotations {
596596
* initaliazion code?
597597
*/
598598
def isNoInitsClass(implicit ctx: Context) =
599-
isClass && asClass.baseClasses.forall(_.is(NoInits))
599+
isClass &&
600+
(asClass.baseClasses.forall(_.is(NoInits)) || defn.isAssuredNoInits(symbol))
600601

601602
/** Is this a "real" method? A real method is a method which is:
602603
* - not an accessor

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

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Uniques.unique
99
import dotc.transform.ExplicitOuter._
1010
import dotc.transform.ValueClasses._
1111
import util.DotClass
12+
import transform.TypeUtils._
1213
import Definitions.MaxImplementedFunctionArity
1314
import scala.annotation.tailrec
1415

@@ -33,6 +34,9 @@ import scala.annotation.tailrec
3334
*/
3435
object TypeErasure {
3536

37+
private def erasureDependsOnArgs(tp: Type)(implicit ctx: Context) =
38+
tp.isRef(defn.ArrayClass) || tp.isRef(defn.PairClass)
39+
3640
/** A predicate that tests whether a type is a legal erased type. Only asInstanceOf and
3741
* isInstanceOf may have types that do not satisfy the predicate.
3842
* ErasedValueType is considered an erased type because it is valid after Erasure (it is
@@ -44,7 +48,8 @@ object TypeErasure {
4448
case tp: TypeRef =>
4549
val sym = tp.symbol
4650
sym.isClass &&
47-
sym != defn.AnyClass && sym != defn.ArrayClass &&
51+
!erasureDependsOnArgs(tp) &&
52+
!defn.erasedToObject.contains(sym) &&
4853
!defn.isSyntheticFunctionClass(sym)
4954
case _: TermRef =>
5055
true
@@ -280,10 +285,8 @@ object TypeErasure {
280285

281286
// Pick the last minimum to prioritise classes over traits
282287
minimums.lastOption match {
283-
case Some(lub) if lub != defn.AnyClass && lub != defn.AnyValClass =>
284-
lub.typeRef
285-
case _ => // Any/AnyVal only exist before erasure
286-
defn.ObjectType
288+
case Some(lub) => valueErasure(lub.typeRef)
289+
case _ => defn.ObjectType
287290
}
288291
}
289292
}
@@ -354,7 +357,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
354357
* - otherwise, if T is a type parameter coming from Java, []Object
355358
* - otherwise, Object
356359
* - For a term ref p.x, the type <noprefix> # x.
357-
* - For a typeref scala.Any, scala.AnyVal or scala.Singleton: |java.lang.Object|
360+
* - For a typeref scala.Any, scala.AnyVal, scala.Singleton, scala.Tuple, or scala.*: : |java.lang.Object|
358361
* - For a typeref scala.Unit, |scala.runtime.BoxedUnit|.
359362
* - For a typeref scala.FunctionN, where N > MaxImplementedFunctionArity, scala.FunctionXXL
360363
* - For a typeref scala.ImplicitFunctionN, | scala.FunctionN |
@@ -390,6 +393,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
390393
else eraseNormalClassRef(tp)
391394
case tp: AppliedType =>
392395
if (tp.tycon.isRef(defn.ArrayClass)) eraseArray(tp)
396+
else if (tp.tycon.isRef(defn.PairClass)) erasePair(tp)
393397
else if (tp.isRepeatedParam) apply(tp.underlyingIfRepeated(isJava))
394398
else apply(tp.superType)
395399
case _: TermRef | _: ThisType =>
@@ -420,9 +424,13 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
420424
case tp @ ClassInfo(pre, cls, parents, decls, _) =>
421425
if (cls is Package) tp
422426
else {
427+
def eraseParent(tp: Type) = tp.dealias match {
428+
case tp: AppliedType if tp.tycon.isRef(defn.PairClass) => defn.ObjectType
429+
case _ => apply(tp)
430+
}
423431
val erasedParents: List[Type] =
424432
if ((cls eq defn.ObjectClass) || cls.isPrimitiveValueClass) Nil
425-
else parents.mapConserve(apply) match {
433+
else parents.mapConserve(eraseParent) match {
426434
case tr :: trs1 =>
427435
assert(!tr.classSymbol.is(Trait), cls)
428436
val tr1 = if (cls is Trait) defn.ObjectType else tr
@@ -450,6 +458,13 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
450458
else JavaArrayType(arrayErasure(elemtp))
451459
}
452460

461+
private def erasePair(tp: Type)(implicit ctx: Context): Type = {
462+
val arity = tp.tupleArity
463+
if (arity < 0) defn.ObjectType
464+
else if (arity <= Definitions.MaxTupleArity) defn.TupleType(arity)
465+
else defn.TupleXXLType
466+
}
467+
453468
/** The erasure of a symbol's info. This is different from `apply` in the way `ExprType`s and
454469
* `PolyType`s are treated. `eraseInfo` maps them them to method types, whereas `apply` maps them
455470
* to the underlying type.
@@ -492,15 +507,15 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
492507
// constructor method should not be semi-erased.
493508
else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp)
494509
else this(tp)
495-
case AppliedType(tycon, _) if !(tycon isRef defn.ArrayClass) =>
510+
case AppliedType(tycon, _) if !erasureDependsOnArgs(tycon) =>
496511
eraseResult(tycon)
497512
case _ =>
498513
this(tp)
499514
}
500515

501516
private def normalizeClass(cls: ClassSymbol)(implicit ctx: Context): ClassSymbol = {
502517
if (cls.owner == defn.ScalaPackageClass) {
503-
if (cls == defn.AnyClass || cls == defn.AnyValClass || cls == defn.SingletonClass)
518+
if (defn.erasedToObject.contains(cls))
504519
return defn.ObjectClass
505520
if (cls == defn.UnitClass)
506521
return defn.BoxedUnitClass
@@ -534,7 +549,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
534549
normalizeClass(sym.asClass).fullName.asTypeName
535550
case tp: AppliedType =>
536551
sigName(
537-
if (tp.tycon.isRef(defn.ArrayClass)) this(tp)
552+
if (erasureDependsOnArgs(tp.tycon)) this(tp)
538553
else if (tp.tycon.typeSymbol.isClass) tp.underlying
539554
else tp.superType)
540555
case ErasedValueType(_, underlying) =>

compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ Standard-Section: "ASTs" TopLevelStat*
211211
TYPEDSPLICE Length splice_Term
212212
FUNCTION Length body_Term arg_Term*
213213
INFIXOP Length op_NameRef left_Term right_Term
214+
TUPLE Length elem_Term*
214215
PATDEF Length type_Term rhs_Term pattern_Term* Modifier*
215216
EMPTYTYPETREE
216217
@@ -437,6 +438,7 @@ object TastyFormat {
437438
final val FUNCTION = 201
438439
final val INFIXOP = 202
439440
final val PATDEF = 203
441+
final val TUPLE = 204
440442

441443
def methodType(isImplicit: Boolean = false, isErased: Boolean = false) = {
442444
val implicitOffset = if (isImplicit) 1 else 0
@@ -656,6 +658,7 @@ object TastyFormat {
656658
case TYPEDSPLICE => "TYPEDSPLICE"
657659
case FUNCTION => "FUNCTION"
658660
case INFIXOP => "INFIXOP"
661+
case TUPLE => "TUPLE"
659662
case PATDEF => "PATDEF"
660663
}
661664

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,9 @@ class TreePickler(pickler: TastyPickler) {
853853
case untpd.InfixOp(l, op, r) =>
854854
writeByte(INFIXOP)
855855
withLength { pickleUntyped(l); pickleUntyped(op); pickleUntyped(r) }
856+
case untpd.Tuple(elems) =>
857+
writeByte(TUPLE)
858+
withLength { elems.foreach(pickleUntyped) }
856859
case untpd.PatDef(mods, pats, tpt, rhs) =>
857860
writeByte(PATDEF)
858861
withLength {

0 commit comments

Comments
 (0)