Skip to content

Commit

Permalink
Allow tuple literals to extend beyond 22
Browse files Browse the repository at this point in the history
  • Loading branch information
odersky committed Aug 15, 2018
1 parent 1c7be37 commit 6cb9868
Show file tree
Hide file tree
Showing 18 changed files with 189 additions and 71 deletions.
27 changes: 17 additions & 10 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,23 @@ object desugar {
makeOp(right, left, Position(op.pos.start, right.pos.end))
}

/** Translate tuple expressions of arity <= 22
*
* () ==> ()
* (t) ==> t
* (t1, ..., tN) ==> TupleN(t1, ..., tN)
*/
def smallTuple(tree: Tuple)(implicit ctx: Context): Tree = {
val ts = tree.trees
val arity = ts.length
assert(arity <= Definitions.MaxTupleArity)
def tupleTypeRef = defn.TupleType(arity)
if (arity == 1) ts.head
else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts)
else if (arity == 0) unitLiteral
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
}

/** Make closure corresponding to function.
* params => body
* ==>
Expand Down Expand Up @@ -1141,16 +1158,6 @@ object desugar {
case PrefixOp(op, t) =>
val nspace = if (ctx.mode.is(Mode.Type)) tpnme else nme
Select(t, nspace.UNARY_PREFIX ++ op.name)
case Tuple(ts) =>
val arity = ts.length
def tupleTypeRef = defn.TupleType(arity)
if (arity > Definitions.MaxTupleArity) {
ctx.error(TupleTooLong(ts), tree.pos)
unitLiteral
} else if (arity == 1) ts.head
else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts)
else if (arity == 0) unitLiteral
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
case WhileDo(cond, body) =>
// { <label> def while$(): Unit = if (cond) { body; while$() } ; while$() }
val call = Apply(Ident(nme.WHILE_PREFIX), Nil).withPos(tree.pos)
Expand Down
56 changes: 24 additions & 32 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -251,14 +251,13 @@ trait ConstraintHandling {
}
}

/** The instance type of `param` in the current constraint (which contains `param`).
* If `fromBelow` is true, the instance type is the lub of the parameter's
* lower bounds; otherwise it is the glb of its upper bounds. However,
* a lower bound instantiation can be a singleton type only if the upper bound
* is also a singleton type.
/** Widen inferred type `tp` with upper bound `bound`, according to the following rules:
* 1. If `tp` is a singleton type, yet `bound` is not a singleton type, nor a subtype
* of `scala.Singleton`, widen `tp`.
* 2. If `tp` is a union type, yet upper bound is not a union type,
* approximate the union type from above by an intersection of all common base types.
*/
def instanceType(param: TypeParamRef, fromBelow: Boolean): Type = {
def upperBound = constraint.fullUpperBound(param)
def widenInferred(tp: Type, bound: Type): Type = {
def isMultiSingleton(tp: Type): Boolean = tp.stripAnnots match {
case tp: SingletonType => true
case AndType(tp1, tp2) => isMultiSingleton(tp1) | isMultiSingleton(tp2)
Expand All @@ -268,39 +267,32 @@ trait ConstraintHandling {
case tp: TypeParamRef => isMultiSingleton(bounds(tp).hi)
case _ => false
}
def isFullyDefined(tp: Type): Boolean = tp match {
case tp: TypeVar => tp.isInstantiated && isFullyDefined(tp.instanceOpt)
case tp: TypeProxy => isFullyDefined(tp.underlying)
case tp: AndType => isFullyDefined(tp.tp1) && isFullyDefined(tp.tp2)
case tp: OrType => isFullyDefined(tp.tp1) && isFullyDefined(tp.tp2)
case _ => true
}
def isOrType(tp: Type): Boolean = tp.dealias match {
case tp: OrType => true
case tp: RefinedOrRecType => isOrType(tp.parent)
case AndType(tp1, tp2) => isOrType(tp1) | isOrType(tp2)
case WildcardType(bounds: TypeBounds) => isOrType(bounds.hi)
case _ => false
}
def widenOr(tp: Type) =
if (isOrType(tp) && !isOrType(bound)) tp.widenUnion
else tp
def widenSingle(tp: Type) =
if (isMultiSingleton(tp) && !isMultiSingleton(bound) &&
!isSubTypeWhenFrozen(bound, defn.SingletonType)) tp.widen
else tp
widenOr(widenSingle(tp))
}

// First, solve the constraint.
var inst = approximation(param, fromBelow).simplified

// Then, approximate by (1.) - (3.) and simplify as follows.
// 1. If instance is from below and is a singleton type, yet upper bound is
// not a singleton type or a subtype of `scala.Singleton`, widen the
// instance.
if (fromBelow && isMultiSingleton(inst) && !isMultiSingleton(upperBound)
&& !isSubTypeWhenFrozen(upperBound, defn.SingletonType))
inst = inst.widen

// 2. If instance is from below and is a fully-defined union type, yet upper bound
// is not a union type, approximate the union type from above by an intersection
// of all common base types.
if (fromBelow && isOrType(inst) && !isOrType(upperBound))
inst = inst.widenUnion

inst
/** The instance type of `param` in the current constraint (which contains `param`).
* If `fromBelow` is true, the instance type is the lub of the parameter's
* lower bounds; otherwise it is the glb of its upper bounds. However,
* a lower bound instantiation can be a singleton type only if the upper bound
* is also a singleton type.
*/
def instanceType(param: TypeParamRef, fromBelow: Boolean): Type = {
val inst = approximation(param, fromBelow).simplified
if (fromBelow) widenInferred(inst, constraint.fullUpperBound(param)) else inst
}

/** Constraint `c1` subsumes constraint `c2`, if under `c2` as constraint we have
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,10 @@ class Definitions {
def PairClass(implicit ctx: Context) = PairType.symbol.asClass
lazy val TupleXXLType = ctx.requiredClassRef("scala.TupleXXL")
def TupleXXLClass(implicit ctx: Context) = TupleXXLType.symbol.asClass
def TupleXXLModule(implicit ctx: Context) = TupleXXLClass.companionModule

def TupleXXL_apply(implicit ctx: Context) =
TupleXXLModule.info.member(nme.apply).requiredSymbol(_.info.isVarArgsMethod)

// Annotation base classes
lazy val AnnotationType = ctx.requiredClassRef("scala.annotation.Annotation")
Expand Down
12 changes: 2 additions & 10 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Uniques.unique
import dotc.transform.ExplicitOuter._
import dotc.transform.ValueClasses._
import util.DotClass
import transform.TypeUtils._
import Definitions.MaxImplementedFunctionArity
import scala.annotation.tailrec

Expand Down Expand Up @@ -457,16 +458,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
}

private def erasePair(tp: Type)(implicit ctx: Context): Type = {
def tupleArity(tp: Type): Int = tp.dealias match {
case AppliedType(tycon, _ :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
val arity = tupleArity(tl)
if (arity < 0) arity else arity + 1
case tp1 =>
if (tp1.isRef(defn.UnitClass)) 0
else if (defn.isTupleClass(tp1.classSymbol)) tp1.dealias.argInfos.length
else -1
}
val arity = tupleArity(tp)
val arity = tp.tupleArity
if (arity < 0) defn.ObjectType
else if (arity <= Definitions.MaxTupleArity) defn.TupleType(arity)
else defn.TupleXXLType
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ Standard-Section: "ASTs" TopLevelStat*
TYPEDSPLICE Length splice_Term
FUNCTION Length body_Term arg_Term*
INFIXOP Length op_NameRef left_Term right_Term
TUPLE Length elem_Term*
PATDEF Length type_Term rhs_Term pattern_Term* Modifier*
EMPTYTYPETREE
Expand Down Expand Up @@ -438,6 +439,7 @@ object TastyFormat {
final val FUNCTION = 201
final val INFIXOP = 202
final val PATDEF = 203
final val TUPLE = 204

def methodType(isImplicit: Boolean = false, isErased: Boolean = false) = {
val implicitOffset = if (isImplicit) 1 else 0
Expand Down Expand Up @@ -657,6 +659,7 @@ object TastyFormat {
case TYPEDSPLICE => "TYPEDSPLICE"
case FUNCTION => "FUNCTION"
case INFIXOP => "INFIXOP"
case TUPLE => "TUPLE"
case PATDEF => "PATDEF"
}

Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,9 @@ class TreePickler(pickler: TastyPickler) {
case untpd.InfixOp(l, op, r) =>
writeByte(INFIXOP)
withLength { pickleUntyped(l); pickleUntyped(op); pickleUntyped(r) }
case untpd.Tuple(elems) =>
writeByte(TUPLE)
withLength { elems.foreach(pickleUntyped) }
case untpd.PatDef(mods, pats, tpt, rhs) =>
writeByte(PATDEF)
withLength {
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,8 @@ class TreeUnpickler(reader: TastyReader,
untpd.Function(params, body)
case INFIXOP =>
untpd.InfixOp(readUntyped(), readIdent(), readUntyped())
case TUPLE =>
untpd.Tuple(until(end)(readUntyped()))
case PATDEF =>
val tpt = readUntyped()
val rhs = readUntyped()
Expand Down
16 changes: 0 additions & 16 deletions compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -490,22 +490,6 @@ object messages {
}
}

case class TupleTooLong(ts: List[untpd.Tree])(implicit ctx: Context)
extends Message(TupleTooLongID) {
import Definitions.MaxTupleArity
val kind = "Syntax"
val msg = hl"""A ${"tuple"} cannot have more than ${MaxTupleArity} members"""

val explanation = {
val members = ts.map(_.showSummary).grouped(MaxTupleArity)
val nestedRepresentation = members.map(_.mkString(", ")).mkString(")(")
hl"""|This restriction will be removed in the future.
|Currently it is possible to use nested tuples when more than $MaxTupleArity are needed, for example:
|
|((${nestedRepresentation}))"""
}
}

case class RepeatedModifier(modifier: String)(implicit ctx:Context)
extends Message(RepeatedModifierID) {
val kind = "Syntax"
Expand Down
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,19 @@ object TypeTestsCasts {
def derivedTree(expr1: Tree, sym: Symbol, tp: Type) =
cpy.TypeApply(tree)(expr1.select(sym).withPos(expr.pos), List(TypeTree(tp)))

def foundCls = expr.tpe.widen.classSymbol
def effectiveClass(tp: Type): Symbol =
if (tp.isRef(defn.PairClass)) effectiveClass(erasure(tp))
else tp.classSymbol

def foundCls = effectiveClass(expr.tpe.widen)
// println(i"ta $tree, found = $foundCls")

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

def transformIsInstanceOf(expr:Tree, testType: Type, flagUnrelated: Boolean): Tree = {
def testCls = testType.classSymbol
def testCls = effectiveClass(testType.widen)

def unreachable(why: => String) =
if (flagUnrelated)
Expand Down
23 changes: 23 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/TypeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,28 @@ object TypeUtils {
case self: MethodicType => self
case _ => if (ctx.erasedTypes) MethodType(Nil, self) else ExprType(self)
}

/** The arity of this tuple type, which can be made up of Unit, TupleX and `*:` pairs,
* or -1 if this is not a tuple type.
*/
def tupleArity(implicit ctx: Context): Int = self match {
case AppliedType(tycon, _ :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
val arity = tl.tupleArity
if (arity < 0) arity else arity + 1
case tp1 =>
if (tp1.isRef(defn.UnitClass)) 0
else if (defn.isTupleClass(tp1.classSymbol)) tp1.dealias.argInfos.length
else -1
}

/** The element types of this tuple type, which can be made up of Unit, TupleX and `*:` pairs */
def tupleElementTypes(implicit ctx: Context): List[Type] = self match {
case AppliedType(tycon, hd :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
hd :: tl.tupleElementTypes
case tp1 =>
if (tp1.isRef(defn.UnitClass)) Nil
else if (defn.isTupleClass(tp1.classSymbol)) tp1.dealias.argInfos
else throw new AssertionError("not a tuple")
}
}
}
33 changes: 33 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import config.Printers.{gadts, typr}
import rewrites.Rewrites.patch
import NavigateAST._
import transform.SymUtils._
import transform.TypeUtils._
import reporting.trace
import config.Config

Expand Down Expand Up @@ -1764,6 +1765,37 @@ class Typer extends Namer
}
}

/** Translate tuples of all arities */
def typedTuple(tree: untpd.Tuple, pt: Type)(implicit ctx: Context) = {
val elems = tree.trees
val arity = elems.length
if (arity <= Definitions.MaxTupleArity)
typed(desugar.smallTuple(tree).withPos(tree.pos), pt)
else {
val pts =
if (arity == pt.tupleArity) pt.tupleElementTypes
else elems.map(_ => defn.AnyType)
val elems1 = (tree.trees, pts).zipped.map(typed(_, _))
if (ctx.mode.is(Mode.Type))
(elems1 :\ (TypeTree(defn.UnitType): Tree))((elemTpt, elemTpts) =>
AppliedTypeTree(TypeTree(defn.PairType), List(elemTpt, elemTpts)))
.withPos(tree.pos)
else {
val tupleXXLobj = untpd.ref(defn.TupleXXLModule.termRef)
val app = untpd.cpy.Apply(tree)(tupleXXLobj, elems1.map(untpd.TypedSplice(_)))
.withPos(tree.pos)
val app1 = typed(app, pt)
if (ctx.mode.is(Mode.Pattern)) app1
else {
val elemTpes = (elems, pts).zipped.map((elem, pt) =>
ctx.typeComparer.widenInferred(elem.tpe, pt))
val resTpe = (elemTpes :\ (defn.UnitType: Type))(defn.PairType.appliedTo(_, _))
app1.asInstance(resTpe)
}
}
}
}

/** Retrieve symbol attached to given tree */
protected def retrieveSym(tree: untpd.Tree)(implicit ctx: Context) = tree.removeAttachment(SymOfTree) match {
case Some(sym) =>
Expand Down Expand Up @@ -1848,6 +1880,7 @@ class Typer extends Namer
case tree: untpd.Annotated => typedAnnotated(tree, pt)
case tree: untpd.TypedSplice => typedTypedSplice(tree)
case tree: untpd.UnApply => typedUnApply(tree, pt)
case tree: untpd.Tuple => typedTuple(tree, pt)
case tree: untpd.DependentTypeTree => typed(untpd.TypeTree().withPos(tree.pos), pt)
case tree: untpd.InfixOp if ctx.mode.isExpr => typedInfixOp(tree, pt)
case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt)
Expand Down
2 changes: 1 addition & 1 deletion library/src-scala3/scala/Tuple.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ object Tuple {
private[scala] rewrite def _size(xs: Tuple): Int =
rewrite xs match {
case _: Unit => 0
case _: *:[_, xs1] => _size(erasedValue[xs1]) + 1
case _: (_ *: xs1) => _size(erasedValue[xs1]) + 1
}

private[scala] rewrite def _head(xs: Tuple): Any = rewrite xs match {
Expand Down
2 changes: 2 additions & 0 deletions library/src/scala/TupleXXL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ final class TupleXXL private (es: Array[Object]) {
}
object TupleXXL {
def apply(elems: Array[Object]) = new TupleXXL(elems.clone)
def apply(elems: Any*) = new TupleXXL(elems.asInstanceOf[Seq[Object]].toArray)
def unapplySeq(x: TupleXXL): Option[Seq[Any]] = Some(x.elems.toSeq)
}
8 changes: 8 additions & 0 deletions tests/neg/tuple-patterns.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
object Test {
(1, 2) match {
case (1, x, 3) => println(x) // error: unreachable
}
"A" match {
case (1, 2, y) => println(y) // error: unreachable
}
}
9 changes: 9 additions & 0 deletions tests/run/tuple-patterns.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
2
2
3
1
10
23
1
10
23

0 comments on commit 6cb9868

Please sign in to comment.