Skip to content

Commit 37ae92b

Browse files
Implement HList based Tuples
Changes are as follows: - New types for tuples in dotty library: Tuple = Unit | TupleCons Head Tail <: Tuple - Desugaring uses this structure instead of the scala.TupleN for types and expressions - New TupleRewrites phase does rewrites the HList structure into scala.TupleN like case classes (for small arities) or a case class wrapping an Array (for large arities)
1 parent 0ad6d90 commit 37ae92b

File tree

9 files changed

+364
-49
lines changed

9 files changed

+364
-49
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ class Compiler {
4848
List(new Pickler), // Generate TASTY info
4949
List(new FirstTransform, // Some transformations to put trees into a canonical form
5050
new CheckReentrant), // Internal use only: Check that compiled program has no data races involving global vars
51-
List(new CheckStatic, // Check restrictions that apply to @static members
51+
List(new TupleRewrites, // Rewrite tuple apply and unapply
52+
new CheckStatic, // Check restrictions that apply to @static members
5253
new ElimRepeated, // Rewrite vararg parameters and arguments
5354
new RefChecks, // Various checks mostly related to abstract members and overriding
5455
new NormalizeFlags, // Rewrite some definition flags

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

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -414,10 +414,10 @@ object desugar {
414414
}
415415
}
416416

417-
// Above MaxTupleArity we extend Product instead of ProductN, in this
417+
// Above MaxImplementedTupleArity we extend Product instead of ProductN, in this
418418
// case we need to synthesise productElement & productArity.
419419
def largeProductMeths =
420-
if (arity > Definitions.MaxTupleArity) productElement :: productArity :: Nil
420+
if (arity > Definitions.MaxImplementedTupleArity) productElement :: productArity :: Nil
421421
else Nil
422422

423423
if (isCaseClass)
@@ -432,7 +432,7 @@ object desugar {
432432
if (targs.isEmpty) tycon else AppliedTypeTree(tycon, targs)
433433
}
434434
def product =
435-
if (arity > Definitions.MaxTupleArity) scalaDot(str.Product.toTypeName)
435+
if (arity > Definitions.MaxImplementedTupleArity) scalaDot(str.Product.toTypeName)
436436
else productConstr(arity)
437437

438438
// Case classes and case objects get Product/ProductN parents
@@ -1068,14 +1068,22 @@ object desugar {
10681068
t
10691069
case Tuple(ts) =>
10701070
val arity = ts.length
1071-
def tupleTypeRef = defn.TupleType(arity)
1072-
if (arity > Definitions.MaxTupleArity) {
1073-
ctx.error(TupleTooLong(ts), tree.pos)
1074-
unitLiteral
1075-
} else if (arity == 1) ts.head
1076-
else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts)
1077-
else if (arity == 0) unitLiteral
1078-
else Apply(ref(tupleTypeRef.classSymbol.companionModule.valRef), ts)
1071+
arity match {
1072+
case 0 => unitLiteral
1073+
case _ if ctx.mode is Mode.Type =>
1074+
// Transforming Tuple types: (T1, T2) → TupleCons[T1, TupleCons[T2, Unit]]
1075+
val nil: Tree = TypeTree(defn.UnitType)
1076+
def hconsType(l: Tree, r: Tree): Tree =
1077+
AppliedTypeTree(ref(defn.TupleConsType), l :: r :: Nil)
1078+
ts.foldRight(nil)(hconsType)
1079+
case _ =>
1080+
// Transforming Tuple trees: (T1, T2, ..., TN) → TupleCons(T1, TupleCons(T2, ... (TupleCons(TN, ())))
1081+
val nil: Tree = unitLiteral
1082+
val cons = defn.TupleConsType.classSymbol.companionModule.valRef
1083+
def consTree(l: Tree, r: Tree): Tree =
1084+
Apply(ref(cons), l :: r :: Nil)
1085+
ts.foldRight(nil)(consTree)
1086+
}
10791087
case WhileDo(cond, body) =>
10801088
// { <label> def while$(): Unit = if (cond) { body; while$() } ; while$() }
10811089
val call = Apply(Ident(nme.WHILE_PREFIX), Nil).withPos(tree.pos)

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

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import collection.mutable
1212
import util.common.alwaysZero
1313

1414
object Definitions {
15-
16-
/** The maximum number of elements in a tuple or product.
17-
* This should be removed once we go to hlists.
15+
/** The maximum arity N of Tuple implemented as `scala.TupleN` case classes.
16+
* Tuple of higher arity us an array based representation (`dotty.LargeTuple`).
17+
* The limit 22 is chosen for Scala2x interop. It could be something
18+
* else without affecting the set of programs that can be compiled.
1819
*/
19-
val MaxTupleArity = 22
20+
val MaxImplementedTupleArity = 6
2021

2122
/** The maximum arity N of a function type that's implemented
2223
* as a trait `scala.FunctionN`. Functions of higher arity are possible,
@@ -698,8 +699,19 @@ class Definitions {
698699
private lazy val ImplementedFunctionType = mkArityArray("scala.Function", MaxImplementedFunctionArity, 0)
699700
def FunctionClassPerRun = new PerRun[Array[Symbol]](implicit ctx => ImplementedFunctionType.map(_.symbol.asClass))
700701

701-
lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2)
702-
lazy val ProductNType = mkArityArray("scala.Product", MaxTupleArity, 0)
702+
lazy val TupleNType = mkArityArray("scala.Tuple", MaxImplementedTupleArity, 1)
703+
lazy val TupleNSymbol = TupleNType.map(t => if (t == null) t else t.classSymbol)
704+
lazy val DottyTupleNType = mkArityArray("dotty.DottyTuple", MaxImplementedTupleArity, 1)
705+
lazy val DottyTupleNModule = DottyTupleNType.map(t => if (t == null) t else t.classSymbol.companionModule.symbol)
706+
lazy val ProductNType = mkArityArray("scala.Product", MaxImplementedTupleArity, 0)
707+
708+
lazy val TupleType = ctx.requiredClassRef("dotty.Tuple")
709+
lazy val TupleConsType = ctx.requiredClassRef("dotty.TupleCons")
710+
lazy val TupleConsModule = TupleConsType.classSymbol.companionModule.symbol
711+
lazy val TupleUnapplySeqType = ctx.requiredClassRef("dotty.LargeTupleUnapplySeq$")
712+
lazy val TupleUnapplySeqModule = TupleUnapplySeqType.classSymbol.companionModule.symbol
713+
lazy val LargeTupleType = ctx.requiredClassRef("dotty.LargeTuple")
714+
lazy val LargeTupleModule = LargeTupleType.classSymbol.companionModule.symbol
703715

704716
def FunctionClass(n: Int, isImplicit: Boolean = false)(implicit ctx: Context) =
705717
if (isImplicit) ctx.requiredClass("scala.ImplicitFunction" + n.toString)
@@ -713,9 +725,6 @@ class Definitions {
713725
if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes)) ImplementedFunctionType(n)
714726
else FunctionClass(n, isImplicit).typeRef
715727

716-
private lazy val TupleTypes: Set[TypeRef] = TupleType.toSet
717-
private lazy val ProductTypes: Set[TypeRef] = ProductNType.toSet
718-
719728
/** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */
720729
def scalaClassName(cls: Symbol)(implicit ctx: Context): TypeName =
721730
if (cls.isClass && cls.owner == ScalaPackageClass) cls.asClass.name else EmptyTypeName
@@ -835,14 +844,8 @@ class Definitions {
835844
def isPolymorphicAfterErasure(sym: Symbol) =
836845
(sym eq Any_isInstanceOf) || (sym eq Any_asInstanceOf)
837846

838-
def isTupleType(tp: Type)(implicit ctx: Context) = {
839-
val arity = tp.dealias.argInfos.length
840-
arity <= MaxTupleArity && TupleType(arity) != null && (tp isRef TupleType(arity).symbol)
841-
}
842-
843-
def tupleType(elems: List[Type]) = {
844-
TupleType(elems.size).appliedTo(elems)
845-
}
847+
def isTupleType(tp: Type)(implicit ctx: Context) =
848+
tp.derivesFrom(TupleType.symbol)
846849

847850
def isProductSubType(tp: Type)(implicit ctx: Context) =
848851
tp.derivesFrom(ProductType.symbol)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,7 @@ object StdNames {
518518
val unapply: N = "unapply"
519519
val unapplySeq: N = "unapplySeq"
520520
val unbox: N = "unbox"
521+
val underlying: N = "underlying"
521522
val universe: N = "universe"
522523
val update: N = "update"
523524
val updateDynamic: N = "updateDynamic"

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
114114

115115
override def toText(tp: Type): Text = controlled {
116116
def toTextTuple(args: List[Type]): Text =
117-
"(" ~ Text(args.map(argText), ", ") ~ ")"
117+
s"scala.Tuple${args.size}[" ~ Text(args.map(argText), ", ") ~ "]"
118118
def toTextFunction(args: List[Type], isImplicit: Boolean): Text =
119119
changePrec(GlobalPrec) {
120120
val argStr: Text =

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -401,22 +401,6 @@ object messages {
401401
}
402402
}
403403

404-
case class TupleTooLong(ts: List[untpd.Tree])(implicit ctx: Context)
405-
extends Message(TupleTooLongID) {
406-
import Definitions.MaxTupleArity
407-
val kind = "Syntax"
408-
val msg = hl"""A ${"tuple"} cannot have more than ${MaxTupleArity} members"""
409-
410-
val explanation = {
411-
val members = ts.map(_.showSummary).grouped(MaxTupleArity)
412-
val nestedRepresentation = members.map(_.mkString(", ")).mkString(")(")
413-
hl"""|This restriction will be removed in the future.
414-
|Currently it is possible to use nested tuples when more than $MaxTupleArity are needed, for example:
415-
|
416-
|((${nestedRepresentation}))"""
417-
}
418-
}
419-
420404
case class RepeatedModifier(modifier: String)(implicit ctx:Context)
421405
extends Message(RepeatedModifierID) {
422406
val kind = "Syntax"
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import ast.Trees._
5+
import core.Constants.Constant
6+
import core.Contexts.Context
7+
import core.Definitions.MaxImplementedTupleArity
8+
import core.StdNames._
9+
import core.Symbols._
10+
import core.Types._
11+
import transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo}
12+
13+
/** Local rewrites for tuple expressions. Nested apply and unapply trees coming
14+
* from desugaring into single apply/unapply nodes on DottyTupleN/LargeTuple.
15+
*/
16+
class TupleRewrites extends MiniPhaseTransform {
17+
import ast.tpd._
18+
import TupleRewrites._
19+
20+
def phaseName: String = "tupleRewrites"
21+
22+
override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit =
23+
tree match {
24+
case Select(ident, _) if ident.symbol == defn.TupleConsModule && start != end =>
25+
assert(false, s"Reference to TupleCons comming from desugaring survived tupleRewrites.")
26+
case _ => ()
27+
}
28+
29+
/** Rewrites `TupleCons(a, TupleCons(b, ..., TNit))` to implementation specific constructors.
30+
*
31+
* Below `MaxImplementedTupleArity`, they become `DottyTuple$i(a, b, ...)`.
32+
* Above `MaxImplementedTupleArity`, they become `LargeTuple(Array.apply(a, b, ...)`.
33+
*
34+
* Note that because of bottom up traversal, the transformation of a tuple constructor of size `N`
35+
* will go thought this transformation `N` times, thus generating `N` `TupleCons(a, opt)` where `opt`
36+
* is the optimized transformation for previous arity.
37+
*/
38+
override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = {
39+
// Matches a tree with shape `TupleCons.apply(head, tail)` where `tail` itself a tuple
40+
// with statically known lenght (Unit, DottyTuple1, DottyTuple2...). */
41+
object TupleApplies {
42+
def unapply(tree: Apply)(implicit ctx: Context): Option[(List[Tree], TupleType)] =
43+
tree match {
44+
case Apply(TypeApply(Select(ident, nme.apply), fstTpe :: _), head :: tail :: Nil)
45+
if ident.symbol == defn.TupleConsModule =>
46+
tail match {
47+
case Literal(Constant(())) =>
48+
Some((head :: Nil, UnfoldedTupleType(fstTpe.tpe :: Nil)))
49+
50+
case Typed(Apply(TypeApply(Select(tailIdent, nme.apply), tailTpes), args), tt)
51+
if defn.DottyTupleNModule contains tailIdent.symbol =>
52+
Some((head :: args, UnfoldedTupleType(fstTpe.tpe :: tailTpes.map(_.tpe))))
53+
54+
case Typed(Apply(TypeApply(Select(tailIdent, nme.wrap), tailTpes), SeqLiteral(args, _) :: Nil), _)
55+
if tailIdent.symbol == defn.LargeTupleModule =>
56+
val foldedTailType = defn.TupleConsType.safeAppliedTo(tailTpes.map(_.tpe))
57+
Some((head :: args, FoldedTupleType(fstTpe.tpe, foldedTailType)))
58+
59+
case _ => None
60+
}
61+
case _ => None
62+
}
63+
}
64+
65+
tree match {
66+
case TupleApplies(args, types) =>
67+
val arity = args.length
68+
val newSelect =
69+
if (arity <= MaxImplementedTupleArity)
70+
ref(defn.DottyTupleNType(arity).classSymbol.companionModule) // DottyTuple${arity}(args)
71+
.select(nme.apply)
72+
.appliedToTypes(types.unfolded.types)
73+
.appliedToArgs(args)
74+
else
75+
ref(defn.LargeTupleType.classSymbol.companionModule) // LargeTuple.wrap(args)
76+
.select(nme.wrap)
77+
.appliedToTypes(types.folded.asArgumentList)
78+
.appliedTo(SeqLiteral(args, ref(defn.AnyType)))
79+
Typed(newSelect, TypeTree(tree.tpe))
80+
case _ => tree
81+
}
82+
}
83+
84+
// Matches a tree with shape `TupleCons.unapply(head, tail)` where `tail`
85+
// itself a tuple with statically known length (`Unit`, `DottyTuple1`,
86+
// `DottyTuple2`...). Extracts the trees and types corresponding to each
87+
// tuple element.
88+
//
89+
// Note that the hlist representation contains more type information than
90+
// the scala.tupleN one, indeed, with C <: B one could write the following:
91+
//
92+
// TupleCons[A, TupleCons[B, Unit]](a, TupleCons[C, Unit](c, ()))
93+
//
94+
// This transformation keeps the more precise type, such that the example
95+
// above is rewritten to scala.Tuple2[A, C](a, b) (using C and not B).
96+
private object TupleUnapplies {
97+
def unapply(tree: UnApply)(implicit ctx: Context): Option[(List[Tree], TupleType)] =
98+
tree match {
99+
case UnApply(TypeApply(Select(selectIndent, nme.unapply), fstTpe :: _), Nil, fstPat :: sndPat :: Nil)
100+
if selectIndent.symbol == defn.TupleConsModule =>
101+
sndPat match {
102+
case Literal(Constant(())) =>
103+
Some((List(fstPat), UnfoldedTupleType(fstTpe.tpe :: Nil)))
104+
105+
case UnApply(TypeApply(Select(ident, nme.unapply), tailTpes), Nil, tailPats)
106+
if defn.DottyTupleNModule contains ident.symbol =>
107+
Some((fstPat :: tailPats, UnfoldedTupleType(fstTpe.tpe :: tailTpes.map(_.tpe))))
108+
109+
case UnApply(TypeApply(Select(ident, nme.unapplySeq), tailTpes), Nil, tailPat)
110+
if ident.symbol == defn.TupleUnapplySeqModule =>
111+
val foldedTailType = defn.TupleConsType.safeAppliedTo(tailTpes.map(_.tpe))
112+
Some((fstPat :: tailPat, FoldedTupleType(fstTpe.tpe, foldedTailType)))
113+
114+
case Typed(UnApply(TypeApply(Select(ident, nme.unapply), tailTpes), Nil, tailPats), _)
115+
if defn.DottyTupleNModule contains ident.symbol =>
116+
Some((fstPat :: tailPats, UnfoldedTupleType(fstTpe.tpe :: tailTpes.map(_.tpe))))
117+
118+
case Typed(UnApply(TypeApply(Select(ident, nme.unapplySeq), tailTpes), Nil, tailPats), _)
119+
if ident.symbol == defn.TupleUnapplySeqModule =>
120+
val foldedTailType = defn.TupleConsType.safeAppliedTo(tailTpes.map(_.tpe))
121+
Some((fstPat :: tailPats, FoldedTupleType(fstTpe.tpe, foldedTailType)))
122+
123+
case _ => None
124+
}
125+
case _ => None
126+
}
127+
}
128+
129+
/** Rewrites `TupleCons.unapply(a, TupleCons.unapply(b, ..., TNit))` to implementation specific extractors.
130+
*
131+
* Below `MaxImplementedTupleArity`, they become `DottyTuple$i.unapply(a, b, ...)`.
132+
* Above `MaxImplementedTupleArity`, they become `TupleUnapplySeq.unapply(a, b, ...)`.
133+
*
134+
* Similarly to `transformApply`, size `N` extractors will pass `N` times thought this transformation.
135+
*/
136+
override def transformUnApply(tree: UnApply)(implicit ctx: Context, info: TransformerInfo): Tree =
137+
tree match {
138+
case TupleUnapplies(patterns, types) =>
139+
transformUnApplyPatterns(tree, patterns, types)
140+
case _ => tree
141+
}
142+
143+
/** Same then `transformUnApply` for unapply wrapped in Typed trees. */
144+
override def transformTyped(tree: Typed)(implicit ctx: Context, info: TransformerInfo): Tree =
145+
tree match {
146+
case Typed(TupleUnapplies(patterns, types), tpe) =>
147+
val unapply = transformUnApplyPatterns(tree, patterns, types)
148+
Typed(unapply, tpe)
149+
case _ => tree
150+
}
151+
152+
// Create an `UnApply` tree from a list of patters, used in both transformUnApply and transformTyped.
153+
private def transformUnApplyPatterns(tree: Tree, patterns: List[Tree], types: TupleType)(implicit ctx: Context): UnApply = {
154+
val arity = patterns.length
155+
if (arity <= MaxImplementedTupleArity) {
156+
val unfoldedTypes: List[Type] = types.unfolded.types
157+
val refinedType = defn.TupleNType(arity).safeAppliedTo(unfoldedTypes)
158+
val newCall = // DottyTuple${arity}.unapply(patterns)
159+
ref(defn.DottyTupleNType(arity).classSymbol.companionModule)
160+
.select(nme.unapply)
161+
.appliedToTypes(unfoldedTypes)
162+
UnApply(fun = newCall, implicits = Nil, patterns = patterns, proto = refinedType)
163+
} else {
164+
val newCall = // TupleUnapplySeq.unapplySeq(patterns)
165+
ref(defn.TupleUnapplySeqType.classSymbol.companionModule)
166+
.select(nme.unapplySeq)
167+
.appliedToTypes(types.folded.asArgumentList)
168+
UnApply(fun = newCall, implicits = Nil, patterns = patterns, proto = tree.tpe)
169+
}
170+
}
171+
172+
}
173+
174+
object TupleRewrites {
175+
/** Helper to go back and forth between representations of tuple types.
176+
* `.folded` is `head :: tail :: Nil` where tail is a big hlist type
177+
* `.unfolded` is a flat list of every type in the tuple.
178+
*/
179+
sealed trait TupleType {
180+
def folded(implicit ctx: Context): FoldedTupleType
181+
def unfolded(implicit ctx: Context): UnfoldedTupleType
182+
}
183+
184+
case class FoldedTupleType(head: Type, tail: Type) extends TupleType {
185+
def asArgumentList: List[Type] = head :: tail :: Nil
186+
187+
def asTupleConsType(implicit ctx: Context): Type = defn.TupleConsType.safeAppliedTo(head :: tail :: Nil)
188+
189+
def folded(implicit ctx: Context): FoldedTupleType = this
190+
191+
def unfolded(implicit ctx: Context): UnfoldedTupleType = {
192+
def unfold(acc: List[Type], next: Type): List[Type] = next match {
193+
case RefinedType(RefinedType(_, _, TypeAlias(headType)), _, TypeAlias(tailType)) =>
194+
unfold(headType :: acc, tailType)
195+
case _ => acc
196+
}
197+
UnfoldedTupleType(unfold(List(head), tail).reverse)
198+
}
199+
}
200+
201+
case class UnfoldedTupleType(types: List[Type]) extends TupleType {
202+
def unfolded(implicit ctx: Context): UnfoldedTupleType = this
203+
204+
def folded(implicit ctx: Context): FoldedTupleType =
205+
FoldedTupleType(
206+
types.head,
207+
types.tail
208+
.map(t => TypeAlias(t, variance = 1))
209+
.reverse
210+
.foldLeft[Type](defn.UnitType) {
211+
case (acc, el) => defn.TupleConsType.safeAppliedTo(el :: acc :: Nil)
212+
}
213+
)
214+
}
215+
}

0 commit comments

Comments
 (0)