Skip to content

Commit a3cf927

Browse files
committed
Try to optimize typed vars
1 parent 232835a commit a3cf927

File tree

3 files changed

+88
-31
lines changed

3 files changed

+88
-31
lines changed

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

Lines changed: 78 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,6 +1450,34 @@ object desugar {
14501450
sel
14511451
end match
14521452

1453+
case class TuplePatternInfo(arity: Int, varNum: Int, wildcardNum: Int, typedVarNum: Int, typedWildcardNum: Int)
1454+
object TuplePatternInfo:
1455+
def apply(pat: Tree)(using Context): TuplePatternInfo = pat match
1456+
case Tuple(pats) =>
1457+
var arity = 0
1458+
var varNum = 0
1459+
var wildcardNum = 0
1460+
var typedVarNum = 0
1461+
var typedWildcardNum = 0
1462+
pats.foreach: p =>
1463+
arity += 1
1464+
p match
1465+
case id: Ident if !isBackquoted(id) =>
1466+
if id.name.isVarPattern then
1467+
varNum += 1
1468+
if id.name == nme.WILDCARD then
1469+
wildcardNum += 1
1470+
case Typed(id: Ident, _) if !isBackquoted(id) =>
1471+
if id.name.isVarPattern then
1472+
typedVarNum += 1
1473+
if id.name == nme.WILDCARD then
1474+
typedWildcardNum += 1
1475+
case _ =>
1476+
TuplePatternInfo(arity, varNum, wildcardNum, typedVarNum, typedWildcardNum)
1477+
case _ =>
1478+
TuplePatternInfo(-1, -1, -1, -1, -1)
1479+
end TuplePatternInfo
1480+
14531481
/** If `pat` is a variable pattern,
14541482
*
14551483
* val/var/lazy val p = e
@@ -1483,35 +1511,50 @@ object desugar {
14831511
|please bind to an identifier and use an alias given.""", bind)
14841512
false
14851513

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
1490-
}
1491-
1492-
val nonWildcardVars = pat match {
1493-
case Tuple(pats) => pats.filterNot(isWildcardPattern).length
1494-
case _ => -1
1495-
}
1496-
1497-
val isMatchingTuple: Tree => Boolean = {
1498-
case Tuple(es) => varTuplePatternArity == es.length && !hasNamedArg(es)
1499-
case _ => false
1500-
}
1514+
val tuplePatternInfo = TuplePatternInfo(pat)
1515+
1516+
// When desugaring a PatDef in general, we use pattern matching on the rhs
1517+
// and collect the variable values in a tuple, then outside the match
1518+
// we destructure the tuple to get the individual variables.
1519+
// We can achieve two kinds of tuple optimizations if the pattern is a tuple
1520+
// of simple variables or wildcards:
1521+
// 1. Full optimization:
1522+
// If the rhs is known to produce a literal tuple of the same arity,
1523+
// we can directly fetch the values from the tuple.
1524+
// For example: `val (x, y) = if ... then (1, "a") else (2, "b")` becomes
1525+
// `val $1$ = if ...; val x = $1$._1; val y = $1$._2`.
1526+
// 2. Partial optimization:
1527+
// If the rhs can be typed as a tuple and matched with correct arity,
1528+
// we can return the tuple itself if there are no more than one variable
1529+
// in the pattern, or return the the value if there is only one variable.
1530+
1531+
val fullTupleOptimizable =
1532+
val isMatchingTuple: Tree => Boolean = {
1533+
case Tuple(es) => tuplePatternInfo.varNum == es.length && !hasNamedArg(es)
1534+
case _ => false
1535+
}
1536+
tuplePatternInfo.arity > 0
1537+
&& tuplePatternInfo.arity == tuplePatternInfo.varNum
1538+
&& forallResults(rhs, isMatchingTuple)
15011539

1502-
// We can only optimize `val pat = if (...) e1 else e2` if:
1503-
// - `e1` and `e2` are both literal tuples of arity N
1504-
// - `pat` is a tuple of N variables or wildcard patterns like `(x1, x2, ..., xN)`
1505-
val tupleOptimizable = forallResults(rhs, isMatchingTuple)
1540+
val partialTupleOptimizable =
1541+
tuplePatternInfo.arity > 0
1542+
&& tuplePatternInfo.arity == tuplePatternInfo.varNum + tuplePatternInfo.typedVarNum
1543+
// We exclude the case where there is only one variable,
1544+
// because it should be handled by `makeTuple` directly.
1545+
&& tuplePatternInfo.wildcardNum + tuplePatternInfo.typedWildcardNum < tuplePatternInfo.arity - 1
15061546

15071547
val inAliasGenerator = original match
15081548
case _: GenAlias => true
15091549
case _ => false
15101550

1511-
val vars =
1512-
if varTuplePatternArity > 0 && nonWildcardVars > 1 then // include `_`
1551+
val vars: List[VarInfo] =
1552+
if fullTupleOptimizable || partialTupleOptimizable then // include `_`
15131553
pat match
1514-
case Tuple(pats) => pats.map { case id: Ident => id -> TypeTree() }
1554+
case Tuple(pats) => pats.map {
1555+
case id: Ident => (id, TypeTree())
1556+
case Typed(id: Ident, tpt) => (id, tpt)
1557+
}
15151558
else
15161559
getVariables(
15171560
tree = pat,
@@ -1522,22 +1565,24 @@ object desugar {
15221565
errorOnGivenBinding
15231566
) // no `_`
15241567

1525-
val ids = for ((named, _) <- vars) yield Ident(named.name)
1568+
val ids = for ((named, tpt) <- vars) yield Ident(named.name)
1569+
1570+
// println(s"fullTupleOptimizable = $fullTupleOptimizable, partialTupleOptimizable = $partialTupleOptimizable, ids = $ids")
1571+
15261572
val matchExpr =
1527-
if tupleOptimizable then rhs
1573+
if fullTupleOptimizable then rhs
15281574
else
15291575
val caseDef =
1530-
if varTuplePatternArity > 0 && ids.length > 1 then
1531-
// If the pattern contains only simple variables or wildcards,
1532-
// we don't need to create a new tuple.
1533-
// If there is only one variable (ids.length == 1),
1534-
// `makeTuple` will optimize it to `Ident(named)`,
1535-
// so we don't need to handle that case here.
1576+
if partialTupleOptimizable then
15361577
val tmpTuple = UniqueName.fresh()
15371578
// Replace all variables with wildcards in the pattern
15381579
val pat1 = pat match
15391580
case Tuple(pats) =>
1540-
Tuple(pats.map(pat => Ident(nme.WILDCARD).withSpan(pat.span)))
1581+
val wildcardPats = pats.map {
1582+
case id: Ident => Ident(nme.WILDCARD).withSpan(id.span)
1583+
case p @ Typed(_: Ident, tpt) => Typed(Ident(nme.WILDCARD), tpt).withSpan(p.span)
1584+
}
1585+
Tuple(wildcardPats).withSpan(pat.span)
15411586
CaseDef(
15421587
Bind(tmpTuple, pat1),
15431588
EmptyTree,
@@ -1546,6 +1591,8 @@ object desugar {
15461591
else CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ()))
15471592
Match(makeSelector(rhs, MatchCheck.IrrefutablePatDef), caseDef :: Nil)
15481593

1594+
// println(i"matchExpr = $matchExpr")
1595+
15491596
vars match {
15501597
case Nil if !mods.is(Lazy) =>
15511598
matchExpr

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,11 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] =>
213213
case _ => false
214214
}
215215

216+
def isTypedVarPattern(pat: Tree): Boolean = unsplice(pat) match {
217+
case Typed(id: Ident, _) if id.name.isVarPattern && !isBackquoted(id) => true
218+
case _ => false
219+
}
220+
216221
/** The first constructor definition in `stats` */
217222
def firstConstructor(stats: List[Tree]): Tree = stats match {
218223
case (meth: DefDef) :: _ if meth.name.isConstructorName => meth
@@ -412,6 +417,8 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] =>
412417
tree.tpe.isInstanceOf[ThisType]
413418
}
414419

420+
421+
415422
/** Under x.modularity: Extractor for `annotation.internal.WitnessNames(name_1, ..., name_n)`
416423
* represented as an untyped or typed tree.
417424
*/

tests/pos/simple-tuple-extract.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class Test:
2424
val (a2, _, c2) = f1
2525
a2 + c2.toString.length()
2626

27+
val (a3, _, _) = f1
28+
a3 + 1
29+
2730
def test3 =
2831
val (_, b, _) = f1
2932
b.length() + 1

0 commit comments

Comments
 (0)