Skip to content

Commit 6cb9868

Browse files
committed
Allow tuple literals to extend beyond 22
1 parent 1c7be37 commit 6cb9868

18 files changed

+189
-71
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/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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,10 @@ class Definitions {
710710
def PairClass(implicit ctx: Context) = PairType.symbol.asClass
711711
lazy val TupleXXLType = ctx.requiredClassRef("scala.TupleXXL")
712712
def TupleXXLClass(implicit ctx: Context) = TupleXXLType.symbol.asClass
713+
def TupleXXLModule(implicit ctx: Context) = TupleXXLClass.companionModule
714+
715+
def TupleXXL_apply(implicit ctx: Context) =
716+
TupleXXLModule.info.member(nme.apply).requiredSymbol(_.info.isVarArgsMethod)
713717

714718
// Annotation base classes
715719
lazy val AnnotationType = ctx.requiredClassRef("scala.annotation.Annotation")

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

Lines changed: 2 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

@@ -457,16 +458,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
457458
}
458459

459460
private def erasePair(tp: Type)(implicit ctx: Context): Type = {
460-
def tupleArity(tp: Type): Int = tp.dealias match {
461-
case AppliedType(tycon, _ :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
462-
val arity = tupleArity(tl)
463-
if (arity < 0) arity else arity + 1
464-
case tp1 =>
465-
if (tp1.isRef(defn.UnitClass)) 0
466-
else if (defn.isTupleClass(tp1.classSymbol)) tp1.dealias.argInfos.length
467-
else -1
468-
}
469-
val arity = tupleArity(tp)
461+
val arity = tp.tupleArity
470462
if (arity < 0) defn.ObjectType
471463
else if (arity <= Definitions.MaxTupleArity) defn.TupleType(arity)
472464
else defn.TupleXXLType

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
@@ -438,6 +439,7 @@ object TastyFormat {
438439
final val FUNCTION = 201
439440
final val INFIXOP = 202
440441
final val PATDEF = 203
442+
final val TUPLE = 204
441443

442444
def methodType(isImplicit: Boolean = false, isErased: Boolean = false) = {
443445
val implicitOffset = if (isImplicit) 1 else 0
@@ -657,6 +659,7 @@ object TastyFormat {
657659
case TYPEDSPLICE => "TYPEDSPLICE"
658660
case FUNCTION => "FUNCTION"
659661
case INFIXOP => "INFIXOP"
662+
case TUPLE => "TUPLE"
660663
case PATDEF => "PATDEF"
661664
}
662665

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 {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,8 @@ class TreeUnpickler(reader: TastyReader,
13851385
untpd.Function(params, body)
13861386
case INFIXOP =>
13871387
untpd.InfixOp(readUntyped(), readIdent(), readUntyped())
1388+
case TUPLE =>
1389+
untpd.Tuple(until(end)(readUntyped()))
13881390
case PATDEF =>
13891391
val tpt = readUntyped()
13901392
val rhs = readUntyped()

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

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -490,22 +490,6 @@ object messages {
490490
}
491491
}
492492

493-
case class TupleTooLong(ts: List[untpd.Tree])(implicit ctx: Context)
494-
extends Message(TupleTooLongID) {
495-
import Definitions.MaxTupleArity
496-
val kind = "Syntax"
497-
val msg = hl"""A ${"tuple"} cannot have more than ${MaxTupleArity} members"""
498-
499-
val explanation = {
500-
val members = ts.map(_.showSummary).grouped(MaxTupleArity)
501-
val nestedRepresentation = members.map(_.mkString(", ")).mkString(")(")
502-
hl"""|This restriction will be removed in the future.
503-
|Currently it is possible to use nested tuples when more than $MaxTupleArity are needed, for example:
504-
|
505-
|((${nestedRepresentation}))"""
506-
}
507-
}
508-
509493
case class RepeatedModifier(modifier: String)(implicit ctx:Context)
510494
extends Message(RepeatedModifierID) {
511495
val kind = "Syntax"

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,19 @@ object TypeTestsCasts {
149149
def derivedTree(expr1: Tree, sym: Symbol, tp: Type) =
150150
cpy.TypeApply(tree)(expr1.select(sym).withPos(expr.pos), List(TypeTree(tp)))
151151

152-
def foundCls = expr.tpe.widen.classSymbol
152+
def effectiveClass(tp: Type): Symbol =
153+
if (tp.isRef(defn.PairClass)) effectiveClass(erasure(tp))
154+
else tp.classSymbol
155+
156+
def foundCls = effectiveClass(expr.tpe.widen)
153157
// println(i"ta $tree, found = $foundCls")
154158

155159
def inMatch =
156160
fun.symbol == defn.Any_typeTest || // new scheme
157161
expr.symbol.is(Case) // old scheme
158162

159163
def transformIsInstanceOf(expr:Tree, testType: Type, flagUnrelated: Boolean): Tree = {
160-
def testCls = testType.classSymbol
164+
def testCls = effectiveClass(testType.widen)
161165

162166
def unreachable(why: => String) =
163167
if (flagUnrelated)

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,28 @@ object TypeUtils {
2727
case self: MethodicType => self
2828
case _ => if (ctx.erasedTypes) MethodType(Nil, self) else ExprType(self)
2929
}
30+
31+
/** The arity of this tuple type, which can be made up of Unit, TupleX and `*:` pairs,
32+
* or -1 if this is not a tuple type.
33+
*/
34+
def tupleArity(implicit ctx: Context): Int = self match {
35+
case AppliedType(tycon, _ :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
36+
val arity = tl.tupleArity
37+
if (arity < 0) arity else arity + 1
38+
case tp1 =>
39+
if (tp1.isRef(defn.UnitClass)) 0
40+
else if (defn.isTupleClass(tp1.classSymbol)) tp1.dealias.argInfos.length
41+
else -1
42+
}
43+
44+
/** The element types of this tuple type, which can be made up of Unit, TupleX and `*:` pairs */
45+
def tupleElementTypes(implicit ctx: Context): List[Type] = self match {
46+
case AppliedType(tycon, hd :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
47+
hd :: tl.tupleElementTypes
48+
case tp1 =>
49+
if (tp1.isRef(defn.UnitClass)) Nil
50+
else if (defn.isTupleClass(tp1.classSymbol)) tp1.dealias.argInfos
51+
else throw new AssertionError("not a tuple")
52+
}
3053
}
3154
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import config.Printers.{gadts, typr}
3737
import rewrites.Rewrites.patch
3838
import NavigateAST._
3939
import transform.SymUtils._
40+
import transform.TypeUtils._
4041
import reporting.trace
4142
import config.Config
4243

@@ -1764,6 +1765,37 @@ class Typer extends Namer
17641765
}
17651766
}
17661767

1768+
/** Translate tuples of all arities */
1769+
def typedTuple(tree: untpd.Tuple, pt: Type)(implicit ctx: Context) = {
1770+
val elems = tree.trees
1771+
val arity = elems.length
1772+
if (arity <= Definitions.MaxTupleArity)
1773+
typed(desugar.smallTuple(tree).withPos(tree.pos), pt)
1774+
else {
1775+
val pts =
1776+
if (arity == pt.tupleArity) pt.tupleElementTypes
1777+
else elems.map(_ => defn.AnyType)
1778+
val elems1 = (tree.trees, pts).zipped.map(typed(_, _))
1779+
if (ctx.mode.is(Mode.Type))
1780+
(elems1 :\ (TypeTree(defn.UnitType): Tree))((elemTpt, elemTpts) =>
1781+
AppliedTypeTree(TypeTree(defn.PairType), List(elemTpt, elemTpts)))
1782+
.withPos(tree.pos)
1783+
else {
1784+
val tupleXXLobj = untpd.ref(defn.TupleXXLModule.termRef)
1785+
val app = untpd.cpy.Apply(tree)(tupleXXLobj, elems1.map(untpd.TypedSplice(_)))
1786+
.withPos(tree.pos)
1787+
val app1 = typed(app, pt)
1788+
if (ctx.mode.is(Mode.Pattern)) app1
1789+
else {
1790+
val elemTpes = (elems, pts).zipped.map((elem, pt) =>
1791+
ctx.typeComparer.widenInferred(elem.tpe, pt))
1792+
val resTpe = (elemTpes :\ (defn.UnitType: Type))(defn.PairType.appliedTo(_, _))
1793+
app1.asInstance(resTpe)
1794+
}
1795+
}
1796+
}
1797+
}
1798+
17671799
/** Retrieve symbol attached to given tree */
17681800
protected def retrieveSym(tree: untpd.Tree)(implicit ctx: Context) = tree.removeAttachment(SymOfTree) match {
17691801
case Some(sym) =>
@@ -1848,6 +1880,7 @@ class Typer extends Namer
18481880
case tree: untpd.Annotated => typedAnnotated(tree, pt)
18491881
case tree: untpd.TypedSplice => typedTypedSplice(tree)
18501882
case tree: untpd.UnApply => typedUnApply(tree, pt)
1883+
case tree: untpd.Tuple => typedTuple(tree, pt)
18511884
case tree: untpd.DependentTypeTree => typed(untpd.TypeTree().withPos(tree.pos), pt)
18521885
case tree: untpd.InfixOp if ctx.mode.isExpr => typedInfixOp(tree, pt)
18531886
case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt)

library/src-scala3/scala/Tuple.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ object Tuple {
116116
private[scala] rewrite def _size(xs: Tuple): Int =
117117
rewrite xs match {
118118
case _: Unit => 0
119-
case _: *:[_, xs1] => _size(erasedValue[xs1]) + 1
119+
case _: (_ *: xs1) => _size(erasedValue[xs1]) + 1
120120
}
121121

122122
private[scala] rewrite def _head(xs: Tuple): Any = rewrite xs match {

library/src/scala/TupleXXL.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ final class TupleXXL private (es: Array[Object]) {
1212
}
1313
object TupleXXL {
1414
def apply(elems: Array[Object]) = new TupleXXL(elems.clone)
15+
def apply(elems: Any*) = new TupleXXL(elems.asInstanceOf[Seq[Object]].toArray)
16+
def unapplySeq(x: TupleXXL): Option[Seq[Any]] = Some(x.elems.toSeq)
1517
}

tests/neg/tuple-patterns.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
object Test {
2+
(1, 2) match {
3+
case (1, x, 3) => println(x) // error: unreachable
4+
}
5+
"A" match {
6+
case (1, 2, y) => println(y) // error: unreachable
7+
}
8+
}

tests/run/tuple-patterns.check

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2
2+
2
3+
3
4+
1
5+
10
6+
23
7+
1
8+
10
9+
23

0 commit comments

Comments
 (0)