Skip to content

Commit 0d93a82

Browse files
committed
Optimize simple tuple extraction to avoid creating unnecessary tuple objects
1 parent 372f889 commit 0d93a82

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

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

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,14 +1483,14 @@ object desugar {
14831483
|please bind to an identifier and use an alias given.""", bind)
14841484
false
14851485

1486-
def isTuplePattern(arity: Int): Boolean = pat match {
1487-
case Tuple(pats) if pats.size == arity =>
1488-
pats.forall(isVarPattern)
1489-
case _ => false
1486+
// The arity of the tuple pattern if it only contains simple variables or wildcards.
1487+
val varTuplePatternArity = pat match {
1488+
case Tuple(pats) if pats.forall(isVarPattern) => pats.length
1489+
case _ => -1
14901490
}
14911491

14921492
val isMatchingTuple: Tree => Boolean = {
1493-
case Tuple(es) => isTuplePattern(es.length) && !hasNamedArg(es)
1493+
case Tuple(es) => varTuplePatternArity == es.length && !hasNamedArg(es)
14941494
case _ => false
14951495
}
14961496

@@ -1519,10 +1519,28 @@ object desugar {
15191519

15201520
val ids = for ((named, _) <- vars) yield Ident(named.name)
15211521
val matchExpr =
1522-
if (tupleOptimizable) rhs
1522+
if tupleOptimizable then rhs
15231523
else
1524-
val caseDef = CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ()))
1524+
val caseDef =
1525+
if varTuplePatternArity >= 0 && ids.length > 1 then
1526+
// If the pattern contains only simple variables or wildcards,
1527+
// we don't need to create a new tuple.
1528+
// If there is only one variable (ids.length == 1),
1529+
// `makeTuple` will optimize it to `Ident(named)`,
1530+
// so we don't need to handle that case here.
1531+
val tmpTuple = UniqueName.fresh()
1532+
// Replace all variables with wildcards in the pattern
1533+
val pat1 = pat match
1534+
case Tuple(pats) =>
1535+
Tuple(pats.map(pat => Ident(nme.WILDCARD).withSpan(pat.span)))
1536+
CaseDef(
1537+
Bind(tmpTuple, pat1),
1538+
EmptyTree,
1539+
Ident(tmpTuple).withAttachment(ForArtifact, ())
1540+
)
1541+
else CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ()))
15251542
Match(makeSelector(rhs, MatchCheck.IrrefutablePatDef), caseDef :: Nil)
1543+
15261544
vars match {
15271545
case Nil if !mods.is(Lazy) =>
15281546
matchExpr

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2774,6 +2774,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27742774
if !isFullyDefined(pt, ForceDegree.all) then
27752775
return errorTree(tree, em"expected type of $tree is not fully defined")
27762776
val body1 = typed(tree.body, pt)
2777+
2778+
// If the body is a named tuple pattern, we need to use pt for symbol type,
2779+
// because the desugared body is a regular tuple unapply.
2780+
def isNamedTuplePattern =
2781+
ctx.mode.is(Mode.Pattern)
2782+
&& pt.dealias.isNamedTupleType
2783+
&& tree.body.match
2784+
case untpd.Tuple((_: NamedArg) :: _) => true
2785+
case _ => false
2786+
27772787
body1 match {
27782788
case UnApply(fn, Nil, arg :: Nil)
27792789
if fn.symbol.exists && (fn.symbol.owner.derivesFrom(defn.TypeTestClass) || fn.symbol.owner == defn.ClassTagClass) && !body1.tpe.isError =>
@@ -2799,8 +2809,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27992809
body1.isInstanceOf[RefTree] && !isWildcardArg(body1)
28002810
|| body1.isInstanceOf[Literal]
28012811
val symTp =
2802-
if isStableIdentifierOrLiteral || pt.dealias.isNamedTupleType then pt
2803-
// need to combine tuple element types with expected named type
2812+
if isStableIdentifierOrLiteral || isNamedTuplePattern then pt
28042813
else if isWildcardStarArg(body1)
28052814
|| pt == defn.ImplicitScrutineeTypeRef
28062815
|| body1.tpe <:< pt // There is some strange interaction with gadt matching.

tests/pos/simple-tuple-extract.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
class Test:
3+
def f1: (Int, Int, Int) = (1, 2, 3)
4+
def f2: (x: Int, y: Int) = (3, 4)
5+
6+
def test1 =
7+
val (a, b, c) = f1
8+
// Desugared to:
9+
// val $2$: (Int, Int, Int) =
10+
// this.f1:(Int, Int, Int) @unchecked match
11+
// {
12+
// case $1$ @ Tuple3.unapply[Int, Int, Int](_, _, _) =>
13+
// $1$:(Int, Int, Int)
14+
// }
15+
// val a: Int = $2$._1
16+
// val b: Int = $2$._2
17+
// val c: Int = $2$._3
18+
a + b + c
19+
20+
def test2 =
21+
val (_, d, e) = f1
22+
e + e
23+
24+
def test3 =
25+
val (_, f, _) = f1
26+
f + f
27+
28+
def test4 =
29+
val (x, y) = f2
30+
x + y
31+
32+
def test5 =
33+
val (_, a) = f2
34+
a + a

0 commit comments

Comments
 (0)