Skip to content

Commit 51e6298

Browse files
committed
Fix #9033, fix #9056: Add EmptyTuple
We change to the following hierarchy where all generic tuple types erase to `Product`. ``` Product -- Tuple -+- EmptyTuple | +- NonEmptyTuple -- *:[Head, Tail <: Tuple] ``` These are encoded using the following compile-time only classes ```scalla sealed trait Tuple extends Product { ... } object EmptyTuple extends Tuple { ... } type EmptyTuple = EmptyTuple.type sealed trait NonEmptyTuple extends Tuple { ... } sealed abstract class *:[+H, +T <: Tuple] extends NonEmptyTuple { ... } ``` `TupleN` classes for `1 < N <= 22` are made subtypes of `*:` with precise type (as done before). But `Unit` is not anymore related to `Tuple`. Construction of empty tuples can be done with `Tuple()`, tuples with one element with `Tuple(e)` while any other tuple are created with `(e1, ..., en)`.
1 parent 345c8bc commit 51e6298

File tree

90 files changed

+612
-409
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+612
-409
lines changed

bench-run/src/main/scala/dotty/tools/benchmarks/tuples/Apply.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class Apply {
1313
def setup(): Unit = {
1414
val size = sizeAndIndex.split(' ')(0).toInt
1515
index = sizeAndIndex.split(' ')(1).toInt
16-
tuple = "elem" *: ()
16+
tuple = "elem" *: Tuple()
1717

1818
for (i <- 1 until size)
1919
tuple = "elem" *: tuple

bench-run/src/main/scala/dotty/tools/benchmarks/tuples/Concat.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class Concat {
1111
var tuple2: Tuple = _
1212

1313
def tupleOfSize(n: Int): Tuple = {
14-
var t: Tuple = ()
14+
var t: Tuple = Tuple()
1515
for (i <- 1 to n)
1616
t = "elem" *: t
1717
t

bench-run/src/main/scala/dotty/tools/benchmarks/tuples/Cons.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Cons {
1212

1313
@Setup
1414
def setup(): Unit = {
15-
tuple = ()
15+
tuple = Tuple()
1616

1717
for (i <- 1 to size)
1818
tuple = "elem" *: tuple

bench-run/src/main/scala/dotty/tools/benchmarks/tuples/Conversions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Conversions {
1212

1313
@Setup
1414
def setup(): Unit = {
15-
tuple = ()
15+
tuple = Tuple()
1616

1717
for (i <- 1 to size)
1818
tuple = "elem" *: tuple

bench-run/src/main/scala/dotty/tools/benchmarks/tuples/Map.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class Map {
1111

1212
@Setup
1313
def setup(): Unit = {
14-
tuple = ()
14+
tuple = Tuple()
1515

1616
for (i <- 1 to size)
1717
tuple = "elem" *: tuple

bench-run/src/main/scala/dotty/tools/benchmarks/tuples/Tail.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class Tail {
1111

1212
@Setup
1313
def setup(): Unit = {
14-
tuple = "elem" *: ()
14+
tuple = "elem" *: Tuple()
1515

1616
for (i <- 1 until size)
1717
tuple = "elem" *: tuple

bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ class TupleOps {
1111

1212
@Setup
1313
def setup(): Unit = {
14-
tuple1 = ()
14+
tuple1 = Tuple()
1515
for (i <- 1 until 15)
1616
tuple1 = s"elem$i" *: tuple1
1717

18-
tuple2 = ()
18+
tuple2 = Tuple()
1919
for (i <- 1 until 10)
2020
tuple2 = s"elem$i" *: tuple2
2121

@@ -25,23 +25,23 @@ class TupleOps {
2525

2626
def tupleFlatMap(tuple: Tuple, f: [A] => A => Tuple): Tuple = {
2727
def tailRecFlatMap(t: Tuple, acc: Tuple): Tuple = t match {
28-
case () => acc
28+
case Tuple() => acc
2929
case x *: rest => tailRecFlatMap(rest, acc ++ f(x))
3030
}
31-
tailRecFlatMap(tuple, ())
31+
tailRecFlatMap(tuple, Tuple())
3232
}
3333

3434
def tupleReverse(tuple: Tuple): Tuple = {
3535
def tailRecReverse(t: Tuple, acc: Tuple): Tuple = t match {
36-
case () => acc
36+
case Tuple() => acc
3737
case x *: rest => tailRecReverse(rest, x *: acc)
3838
}
39-
tailRecReverse(tuple, ())
39+
tailRecReverse(tuple, Tuple())
4040
}
4141

4242
def tupleMerge(tuple1: Tuple, tuple2: Tuple): Tuple = (tuple1, tuple2) match {
43-
case (_, ()) => tuple1
44-
case ((), _) => tuple2
43+
case (_, Tuple()) => tuple1
44+
case (Tuple(), _) => tuple2
4545
case (x *: xs, y *: ys) =>
4646
if (x.asInstanceOf[Int] <= y.asInstanceOf[Int]) x *: tupleMerge(xs, tuple2)
4747
else y *: tupleMerge(tuple1, ys)

bench-run/src/main/scala/dotty/tools/benchmarks/tuples/Zip.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ class Zip {
1313

1414
@Setup
1515
def setup(): Unit = {
16-
tuple1 = ()
17-
tuple2 = ()
16+
tuple1 = Tuple()
17+
tuple2 = Tuple()
1818

1919
for (i <- 1 to size) {
2020
tuple1 = "el" *: tuple1

bench-run/src/main/scala/tuples/Drop.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Drop {
1212

1313
@Setup
1414
def setup(): Unit = {
15-
tuple = ()
15+
tuple = Tuple()
1616
half = size / 2
1717

1818
for (i <- 1 to size)

bench-run/src/main/scala/tuples/Split.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Split {
1212

1313
@Setup
1414
def setup(): Unit = {
15-
tuple = ()
15+
tuple = Tuple()
1616
half = size / 2
1717

1818
for (i <- 1 to size)

bench-run/src/main/scala/tuples/Take.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Take {
1212

1313
@Setup
1414
def setup(): Unit = {
15-
tuple = ()
15+
tuple = Tuple()
1616
half = size / 2
1717

1818
for (i <- 1 to size)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1337,7 +1337,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
13371337

13381338
/** Creates the nested pairs type tree repesentation of the type trees in `ts` */
13391339
def nestedPairsTypeTree(ts: List[Tree])(implicit ctx: Context): Tree =
1340-
ts.foldRight[Tree](TypeTree(defn.UnitType))((x, acc) => AppliedTypeTree(TypeTree(defn.PairClass.typeRef), x :: acc :: Nil))
1340+
ts.foldRight[Tree](TypeTree(defn.EmptyTupleModule.termRef))((x, acc) => AppliedTypeTree(TypeTree(defn.PairClass.typeRef), x :: acc :: Nil))
13411341

13421342
/** Replaces all positions in `tree` with zero-extent positions */
13431343
private def focusPositions(tree: Tree)(implicit ctx: Context): Tree = {

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -749,11 +749,12 @@ class Definitions {
749749
@tu lazy val TupleTypeRef: TypeRef = ctx.requiredClassRef("scala.Tuple")
750750
def TupleClass(implicit ctx: Context): ClassSymbol = TupleTypeRef.symbol.asClass
751751
@tu lazy val Tuple_cons: Symbol = TupleClass.requiredMethod("*:")
752+
@tu lazy val EmptyTupleModule: Symbol = ctx.requiredModule("scala.EmptyTuple")
752753
@tu lazy val NonEmptyTupleTypeRef: TypeRef = ctx.requiredClassRef("scala.NonEmptyTuple")
753754
def NonEmptyTupleClass(implicit ctx: Context): ClassSymbol = NonEmptyTupleTypeRef.symbol.asClass
754755
lazy val NonEmptyTuple_tail: Symbol = NonEmptyTupleClass.requiredMethod("tail")
755-
756756
@tu lazy val PairClass: ClassSymbol = ctx.requiredClass("scala.*:")
757+
757758
@tu lazy val TupleXXLClass: ClassSymbol = ctx.requiredClass("scala.runtime.TupleXXL")
758759
def TupleXXLModule(implicit ctx: Context): Symbol = TupleXXLClass.companionModule
759760

@@ -770,6 +771,9 @@ class Definitions {
770771
lazy val RuntimeTuple_concat: Symbol = RuntimeTupleModule.requiredMethod("concat")
771772
lazy val RuntimeTuple_toArray: Symbol = RuntimeTupleModule.requiredMethod("toArray")
772773
lazy val RuntimeTuple_productToArray: Symbol = RuntimeTupleModule.requiredMethod("productToArray")
774+
lazy val RuntimeTuple_isInstanceOfTuple: Symbol = RuntimeTupleModule.requiredMethod("isInstanceOfTuple")
775+
lazy val RuntimeTuple_isInstanceOfEmptyTuple: Symbol = RuntimeTupleModule.requiredMethod("isInstanceOfEmptyTuple")
776+
lazy val RuntimeTuple_isInstanceOfNonEmptyTuple: Symbol = RuntimeTupleModule.requiredMethod("isInstanceOfNonEmptyTuple")
773777

774778
@tu lazy val TupledFunctionTypeRef: TypeRef = ctx.requiredClassRef("scala.TupledFunction")
775779
def TupledFunctionClass(implicit ctx: Context): ClassSymbol = TupledFunctionTypeRef.symbol.asClass
@@ -1189,7 +1193,7 @@ class Definitions {
11891193
case _ if bound < 0 => Some(acc.reverse)
11901194
case tp: AppliedType if defn.PairClass == tp.classSymbol => rec(tp.args(1), tp.args.head :: acc, bound - 1)
11911195
case tp: AppliedType if defn.isTupleClass(tp.tycon.classSymbol) => Some(acc.reverse ::: tp.args)
1192-
case tp if tp.classSymbol == defn.UnitClass => Some(acc.reverse)
1196+
case tp: TermRef if tp.symbol == defn.EmptyTupleModule => Some(acc.reverse)
11931197
case _ => None
11941198
}
11951199
rec(tp.stripTypeVar, Nil, bound)
@@ -1300,7 +1304,7 @@ class Definitions {
13001304
def syntheticParent(tparams: List[TypeSymbol]): Type =
13011305
if (tparams.isEmpty) TupleTypeRef
13021306
else TypeOps.nestedPairs(tparams.map(_.typeRef))
1303-
if (isTupleClass(cls) || cls == UnitClass) parents :+ syntheticParent(tparams)
1307+
if (isTupleClass(cls)) parents :+ syntheticParent(tparams)
13041308
else parents
13051309
}
13061310

@@ -1397,7 +1401,7 @@ class Definitions {
13971401
.updated(AnyClass, ObjectClass)
13981402
.updated(AnyValClass, ObjectClass)
13991403
.updated(SingletonClass, ObjectClass)
1400-
.updated(TupleClass, ObjectClass)
1404+
.updated(TupleClass, ProductClass)
14011405
.updated(NonEmptyTupleClass, ProductClass)
14021406

14031407
// ----- Initialization ---------------------------------------------------

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,6 @@ object TypeOps:
729729
}
730730

731731
def nestedPairs(ts: List[Type])(using Context): Type =
732-
ts.foldRight(defn.UnitType: Type)(defn.PairClass.typeRef.appliedTo(_, _))
732+
ts.foldRight(defn.EmptyTupleModule.termRef: Type)(defn.PairClass.typeRef.appliedTo(_, _))
733733

734734
end TypeOps

compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2005,7 +2005,10 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
20052005
def Definitions_NothingType: Type = defn.NothingType
20062006
def Definitions_NullType: Type = defn.NullType
20072007
def Definitions_StringType: Type = defn.StringType
2008-
2008+
def Definitions_TupleType: Type = defn.TupleTypeRef
2009+
def Definitions_EmptyTupleType: Type = defn.EmptyTupleModule.termRef
2010+
def Definitions_NonEmptyTupleType: Type = defn.NonEmptyTupleClass.typeRef
2011+
def Definitions_TupleConsType: Type = defn.PairClass.typeRef
20092012

20102013
///////////////
20112014
// IMPLICITS //

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ class TupleOptimizations extends MiniPhase with IdentityDenotTransformer {
6969
val size = tpes.size
7070
assert(size > 0)
7171
if (size == 1)
72-
// ()
73-
Literal(Constant(()))
72+
// scala.EmptyTuple
73+
ref(defn.EmptyTupleModule.termRef)
7474
else if (size <= 5)
7575
// val t = tup.asInstanceOf[TupleN[...]]
7676
// TupleN-1(t._2, ..., t._n)
@@ -197,8 +197,8 @@ class TupleOptimizations extends MiniPhase with IdentityDenotTransformer {
197197

198198
private def knownTupleFromIterator(size: Int, it: Tree)(implicit ctx: Context): Tree =
199199
if (size == 0)
200-
// Unit for empty tuple
201-
Literal(Constant(())) // TODO should this code be here? Or assert(size > specializedSize)
200+
// EmptyTuple for empty tuple
201+
ref(defn.EmptyTupleModule.termRef) // TODO should this code be here? Or assert(size > specializedSize)
202202
else if (size <= MaxTupleArity) {
203203
// TupleN(it.next(), ..., it.next())
204204

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,11 +305,17 @@ object TypeTestsCasts {
305305
*
306306
* expr.isInstanceOf[A | B] ~~> expr.isInstanceOf[A] | expr.isInstanceOf[B]
307307
* expr.isInstanceOf[A & B] ~~> expr.isInstanceOf[A] & expr.isInstanceOf[B]
308+
* expr.isInstanceOf[Tuple] ~~> scala.runtime.Tuple.isInstanceOfTuple(expr)
309+
* expr.isInstanceOf[EmptyTuple] ~~> scala.runtime.Tuple.isInstanceOfEmptyTuple(expr)
310+
* expr.isInstanceOf[NonEmptyTuple] ~~> scala.runtime.Tuple.isInstanceOfNonEmptyTuple(expr)
311+
* expr.isInstanceOf[*:[_, _]] ~~> scala.runtime.Tuple.isInstanceOfNonEmptyTuple(expr)
308312
*
309313
* The transform happens before erasure of `testType`, thus cannot be merged
310314
* with `transformIsInstanceOf`, which depends on erased type of `testType`.
311315
*/
312316
def transformTypeTest(expr: Tree, testType: Type, flagUnrelated: Boolean): Tree = testType.dealias match {
317+
case tref: TermRef if tref.symbol == defn.EmptyTupleModule =>
318+
ref(defn.RuntimeTuple_isInstanceOfEmptyTuple).appliedTo(expr)
313319
case _: SingletonType =>
314320
expr.isInstance(testType).withSpan(tree.span)
315321
case OrType(tp1, tp2) =>
@@ -330,6 +336,12 @@ object TypeTestsCasts {
330336
derivedTree(e, defn.Any_isInstanceOf, e.tpe)
331337
.and(isArrayTest(e))
332338
}
339+
case tref: TypeRef if tref.symbol == defn.TupleClass =>
340+
ref(defn.RuntimeTuple_isInstanceOfTuple).appliedTo(expr)
341+
case tref: TypeRef if tref.symbol == defn.NonEmptyTupleClass =>
342+
ref(defn.RuntimeTuple_isInstanceOfNonEmptyTuple).appliedTo(expr)
343+
case AppliedType(tref: TypeRef, _) if tref.symbol == defn.PairClass =>
344+
ref(defn.RuntimeTuple_isInstanceOfNonEmptyTuple).appliedTo(expr)
333345
case _ =>
334346
transformIsInstanceOf(expr, erasure(testType), flagUnrelated)
335347
}

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

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,31 @@ object TypeUtils {
3131
case ps => ps.reduceLeft(AndType(_, _))
3232
}
3333

34-
/** The arity of this tuple type, which can be made up of Unit, TupleX and `*:` pairs,
34+
/** The arity of this tuple type, which can be made up of EmptyTuple, TupleX and `*:` pairs,
3535
* or -1 if this is not a tuple type.
3636
*/
3737
def tupleArity(implicit ctx: Context): Int = self match {
3838
case AppliedType(tycon, _ :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
3939
val arity = tl.tupleArity
4040
if (arity < 0) arity else arity + 1
41-
case tp1 =>
42-
if (tp1.isRef(defn.UnitClass)) 0
43-
else if (defn.isTupleClass(tp1.classSymbol)) tp1.dealias.argInfos.length
44-
else -1
41+
case self: TermRef if self.symbol == defn.EmptyTupleModule =>
42+
0
43+
case self if defn.isTupleClass(self.classSymbol) =>
44+
self.dealias.argInfos.length
45+
case _ =>
46+
-1
4547
}
4648

47-
/** The element types of this tuple type, which can be made up of Unit, TupleX and `*:` pairs */
49+
/** The element types of this tuple type, which can be made up of EmptyTuple, TupleX and `*:` pairs */
4850
def tupleElementTypes(implicit ctx: Context): List[Type] = self match {
4951
case AppliedType(tycon, hd :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
5052
hd :: tl.tupleElementTypes
51-
case tp1 =>
52-
if (tp1.isRef(defn.UnitClass)) Nil
53-
else if (defn.isTupleClass(tp1.classSymbol)) tp1.dealias.argInfos
54-
else throw new AssertionError("not a tuple")
53+
case self: TermRef if self.symbol == defn.EmptyTupleModule =>
54+
Nil
55+
case self if defn.isTupleClass(self.classSymbol) =>
56+
self.dealias.argInfos
57+
case _ =>
58+
throw new AssertionError("not a tuple")
5559
}
5660

5761
/** The `*:` equivalent of an instance of a Tuple class */

compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,9 @@ trait QuotesAndSplices {
432432
}
433433
}
434434

435-
val splicePat = typed(untpd.Tuple(splices.map(x => untpd.TypedSplice(replaceBindingsInTree.transform(x)))).withSpan(quoted.span), patType)
435+
val splicePat =
436+
if splices.isEmpty then ref(defn.EmptyTupleModule.termRef)
437+
else typed(untpd.Tuple(splices.map(x => untpd.TypedSplice(replaceBindingsInTree.transform(x)))).withSpan(quoted.span), patType)
436438

437439
val unapplySym = if (tree.quoted.isTerm) defn.InternalQuotedExpr_unapply else defn.InternalQuotedType_unapply
438440
val quoteClass = if (tree.quoted.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2309,7 +2309,7 @@ class Typer extends Namer
23092309
else List.fill(arity)(defn.AnyType)
23102310
val elems = tree.trees.lazyZip(pts).map(typed(_, _))
23112311
if (ctx.mode.is(Mode.Type))
2312-
elems.foldRight(TypeTree(defn.UnitType): Tree)((elemTpt, elemTpts) =>
2312+
elems.foldRight(TypeTree(defn.EmptyTupleModule.termRef): Tree)((elemTpt, elemTpts) =>
23132313
AppliedTypeTree(TypeTree(defn.PairClass.typeRef), List(elemTpt, elemTpts)))
23142314
.withSpan(tree.span)
23152315
else {

0 commit comments

Comments
 (0)