From 6e466de5cf44146c84057b34091c0aa6fae9aed6 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 16 Jun 2025 02:19:58 +0200 Subject: [PATCH 1/9] Optimize simple tuple extraction to avoid creating unnecessary tuple objects --- .../src/dotty/tools/dotc/ast/Desugar.scala | 32 +++++++++++++---- tests/pos/simple-tuple-extract.scala | 34 +++++++++++++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 tests/pos/simple-tuple-extract.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 98eb8f895f5b..5732fecf1800 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1483,14 +1483,14 @@ object desugar { |please bind to an identifier and use an alias given.""", bind) false - def isTuplePattern(arity: Int): Boolean = pat match { - case Tuple(pats) if pats.size == arity => - pats.forall(isVarPattern) - case _ => false + // The arity of the tuple pattern if it only contains simple variables or wildcards. + val varTuplePatternArity = pat match { + case Tuple(pats) if pats.forall(isVarPattern) => pats.length + case _ => -1 } val isMatchingTuple: Tree => Boolean = { - case Tuple(es) => isTuplePattern(es.length) && !hasNamedArg(es) + case Tuple(es) => varTuplePatternArity == es.length && !hasNamedArg(es) case _ => false } @@ -1519,10 +1519,28 @@ object desugar { val ids = for ((named, _) <- vars) yield Ident(named.name) val matchExpr = - if (tupleOptimizable) rhs + if tupleOptimizable then rhs else - val caseDef = CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ())) + val caseDef = + if varTuplePatternArity >= 0 && ids.length > 1 then + // If the pattern contains only simple variables or wildcards, + // we don't need to create a new tuple. + // If there is only one variable (ids.length == 1), + // `makeTuple` will optimize it to `Ident(named)`, + // so we don't need to handle that case here. + val tmpTuple = UniqueName.fresh() + // Replace all variables with wildcards in the pattern + val pat1 = pat match + case Tuple(pats) => + Tuple(pats.map(pat => Ident(nme.WILDCARD).withSpan(pat.span))) + CaseDef( + Bind(tmpTuple, pat1), + EmptyTree, + Ident(tmpTuple).withAttachment(ForArtifact, ()) + ) + else CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ())) Match(makeSelector(rhs, MatchCheck.IrrefutablePatDef), caseDef :: Nil) + vars match { case Nil if !mods.is(Lazy) => matchExpr diff --git a/tests/pos/simple-tuple-extract.scala b/tests/pos/simple-tuple-extract.scala new file mode 100644 index 000000000000..849473e8b87d --- /dev/null +++ b/tests/pos/simple-tuple-extract.scala @@ -0,0 +1,34 @@ + +class Test: + def f1: (Int, Int, Int) = (1, 2, 3) + def f2: (x: Int, y: Int) = (3, 4) + + def test1 = + val (a, b, c) = f1 + // Desugared to: + // val $2$: (Int, Int, Int) = + // this.f1:(Int, Int, Int) @unchecked match + // { + // case $1$ @ Tuple3.unapply[Int, Int, Int](_, _, _) => + // $1$:(Int, Int, Int) + // } + // val a: Int = $2$._1 + // val b: Int = $2$._2 + // val c: Int = $2$._3 + a + b + c + + def test2 = + val (_, d, e) = f1 + e + e + + def test3 = + val (_, f, _) = f1 + f + f + + def test4 = + val (x, y) = f2 + x + y + + def test5 = + val (_, a) = f2 + a + a From 0463b761158a31f8abc7eed06184c3c66b6cc66e Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 16 Jun 2025 03:14:13 +0200 Subject: [PATCH 2/9] Fix patterns containing wildcards; enhance test --- .../src/dotty/tools/dotc/ast/Desugar.scala | 11 ++++-- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 6 ++++ tests/pos/simple-tuple-extract.scala | 35 ++++++++++--------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 5732fecf1800..f173cd5e7ba5 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1489,13 +1489,18 @@ object desugar { case _ => -1 } + val nonWildcardVars = pat match { + case Tuple(pats) => pats.filterNot(isWildcardPattern).length + case _ => -1 + } + val isMatchingTuple: Tree => Boolean = { case Tuple(es) => varTuplePatternArity == es.length && !hasNamedArg(es) case _ => false } // We can only optimize `val pat = if (...) e1 else e2` if: - // - `e1` and `e2` are both tuples of arity N + // - `e1` and `e2` are both literal tuples of arity N // - `pat` is a tuple of N variables or wildcard patterns like `(x1, x2, ..., xN)` val tupleOptimizable = forallResults(rhs, isMatchingTuple) @@ -1504,7 +1509,7 @@ object desugar { case _ => false val vars = - if (tupleOptimizable) // include `_` + if varTuplePatternArity > 0 && nonWildcardVars > 1 then // include `_` pat match case Tuple(pats) => pats.map { case id: Ident => id -> TypeTree() } else @@ -1522,7 +1527,7 @@ object desugar { if tupleOptimizable then rhs else val caseDef = - if varTuplePatternArity >= 0 && ids.length > 1 then + if varTuplePatternArity > 0 && ids.length > 1 then // If the pattern contains only simple variables or wildcards, // we don't need to create a new tuple. // If there is only one variable (ids.length == 1), diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 0a2c0c850e5d..51d140e458b6 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -207,6 +207,12 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => case _ => false } + /** Is tree a wildcard pattern? Not including `x @ _` */ + def isWildcardPattern(pat: Tree): Boolean = unsplice(pat) match { + case x: Ident => x.name == nme.WILDCARD && !isBackquoted(x) + case _ => false + } + /** The first constructor definition in `stats` */ def firstConstructor(stats: List[Tree]): Tree = stats match { case (meth: DefDef) :: _ if meth.name.isConstructorName => meth diff --git a/tests/pos/simple-tuple-extract.scala b/tests/pos/simple-tuple-extract.scala index 849473e8b87d..ee8ce04084ee 100644 --- a/tests/pos/simple-tuple-extract.scala +++ b/tests/pos/simple-tuple-extract.scala @@ -1,34 +1,37 @@ class Test: - def f1: (Int, Int, Int) = (1, 2, 3) - def f2: (x: Int, y: Int) = (3, 4) + def f1: (Int, String, AnyRef) = (1, "2", "3") + def f2: (x: Int, y: String) = (0, "y") def test1 = val (a, b, c) = f1 // Desugared to: - // val $2$: (Int, Int, Int) = - // this.f1:(Int, Int, Int) @unchecked match + // val $2$: (Int, String, AnyRef) = + // this.f1:(Int, String, AnyRef) @unchecked match // { - // case $1$ @ Tuple3.unapply[Int, Int, Int](_, _, _) => - // $1$:(Int, Int, Int) + // case $1$ @ Tuple3.unapply[Int, String, Object](_, _, _) => + // $1$:(Int, String, AnyRef) // } // val a: Int = $2$._1 - // val b: Int = $2$._2 - // val c: Int = $2$._3 - a + b + c + // val b: String = $2$._2 + // val c: AnyRef = $2$._3 + a + b.length() + c.toString.length() def test2 = - val (_, d, e) = f1 - e + e + val (_, b, c) = f1 + b.length() + c.toString.length() + + val (a2, _, c2) = f1 + a2 + c2.toString.length() def test3 = - val (_, f, _) = f1 - f + f + val (_, b, _) = f1 + b.length() + 1 def test4 = val (x, y) = f2 - x + y + x + y.length() def test5 = - val (_, a) = f2 - a + a + val (_, b) = f2 + b.length() + 1 \ No newline at end of file From 6841cd1cf950bb8581ce43606d5ed1ea9771781d Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 16 Jun 2025 21:25:32 +0200 Subject: [PATCH 3/9] Try to optimize typed vars --- .../src/dotty/tools/dotc/ast/Desugar.scala | 109 +++++++++++++----- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 7 ++ tests/pos/simple-tuple-extract.scala | 3 + 3 files changed, 88 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index f173cd5e7ba5..9aa1aa7f7b78 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1450,6 +1450,34 @@ object desugar { sel end match + case class TuplePatternInfo(arity: Int, varNum: Int, wildcardNum: Int, typedVarNum: Int, typedWildcardNum: Int) + object TuplePatternInfo: + def apply(pat: Tree)(using Context): TuplePatternInfo = pat match + case Tuple(pats) => + var arity = 0 + var varNum = 0 + var wildcardNum = 0 + var typedVarNum = 0 + var typedWildcardNum = 0 + pats.foreach: p => + arity += 1 + p match + case id: Ident if !isBackquoted(id) => + if id.name.isVarPattern then + varNum += 1 + if id.name == nme.WILDCARD then + wildcardNum += 1 + case Typed(id: Ident, _) if !isBackquoted(id) => + if id.name.isVarPattern then + typedVarNum += 1 + if id.name == nme.WILDCARD then + typedWildcardNum += 1 + case _ => + TuplePatternInfo(arity, varNum, wildcardNum, typedVarNum, typedWildcardNum) + case _ => + TuplePatternInfo(-1, -1, -1, -1, -1) + end TuplePatternInfo + /** If `pat` is a variable pattern, * * val/var/lazy val p = e @@ -1483,35 +1511,50 @@ object desugar { |please bind to an identifier and use an alias given.""", bind) false - // The arity of the tuple pattern if it only contains simple variables or wildcards. - val varTuplePatternArity = pat match { - case Tuple(pats) if pats.forall(isVarPattern) => pats.length - case _ => -1 - } - - val nonWildcardVars = pat match { - case Tuple(pats) => pats.filterNot(isWildcardPattern).length - case _ => -1 - } - - val isMatchingTuple: Tree => Boolean = { - case Tuple(es) => varTuplePatternArity == es.length && !hasNamedArg(es) - case _ => false - } + val tuplePatternInfo = TuplePatternInfo(pat) + + // When desugaring a PatDef in general, we use pattern matching on the rhs + // and collect the variable values in a tuple, then outside the match + // we destructure the tuple to get the individual variables. + // We can achieve two kinds of tuple optimizations if the pattern is a tuple + // of simple variables or wildcards: + // 1. Full optimization: + // If the rhs is known to produce a literal tuple of the same arity, + // we can directly fetch the values from the tuple. + // For example: `val (x, y) = if ... then (1, "a") else (2, "b")` becomes + // `val $1$ = if ...; val x = $1$._1; val y = $1$._2`. + // 2. Partial optimization: + // If the rhs can be typed as a tuple and matched with correct arity, + // we can return the tuple itself if there are no more than one variable + // in the pattern, or return the the value if there is only one variable. + + val fullTupleOptimizable = + val isMatchingTuple: Tree => Boolean = { + case Tuple(es) => tuplePatternInfo.varNum == es.length && !hasNamedArg(es) + case _ => false + } + tuplePatternInfo.arity > 0 + && tuplePatternInfo.arity == tuplePatternInfo.varNum + && forallResults(rhs, isMatchingTuple) - // We can only optimize `val pat = if (...) e1 else e2` if: - // - `e1` and `e2` are both literal tuples of arity N - // - `pat` is a tuple of N variables or wildcard patterns like `(x1, x2, ..., xN)` - val tupleOptimizable = forallResults(rhs, isMatchingTuple) + val partialTupleOptimizable = + tuplePatternInfo.arity > 0 + && tuplePatternInfo.arity == tuplePatternInfo.varNum + tuplePatternInfo.typedVarNum + // We exclude the case where there is only one variable, + // because it should be handled by `makeTuple` directly. + && tuplePatternInfo.wildcardNum + tuplePatternInfo.typedWildcardNum < tuplePatternInfo.arity - 1 val inAliasGenerator = original match case _: GenAlias => true case _ => false - val vars = - if varTuplePatternArity > 0 && nonWildcardVars > 1 then // include `_` + val vars: List[VarInfo] = + if fullTupleOptimizable || partialTupleOptimizable then // include `_` pat match - case Tuple(pats) => pats.map { case id: Ident => id -> TypeTree() } + case Tuple(pats) => pats.map { + case id: Ident => (id, TypeTree()) + case Typed(id: Ident, tpt) => (id, tpt) + } else getVariables( tree = pat, @@ -1522,22 +1565,24 @@ object desugar { errorOnGivenBinding ) // no `_` - val ids = for ((named, _) <- vars) yield Ident(named.name) + val ids = for ((named, tpt) <- vars) yield Ident(named.name) + + // println(s"fullTupleOptimizable = $fullTupleOptimizable, partialTupleOptimizable = $partialTupleOptimizable, ids = $ids") + val matchExpr = - if tupleOptimizable then rhs + if fullTupleOptimizable then rhs else val caseDef = - if varTuplePatternArity > 0 && ids.length > 1 then - // If the pattern contains only simple variables or wildcards, - // we don't need to create a new tuple. - // If there is only one variable (ids.length == 1), - // `makeTuple` will optimize it to `Ident(named)`, - // so we don't need to handle that case here. + if partialTupleOptimizable then val tmpTuple = UniqueName.fresh() // Replace all variables with wildcards in the pattern val pat1 = pat match case Tuple(pats) => - Tuple(pats.map(pat => Ident(nme.WILDCARD).withSpan(pat.span))) + val wildcardPats = pats.map { + case id: Ident => Ident(nme.WILDCARD).withSpan(id.span) + case p @ Typed(_: Ident, tpt) => Typed(Ident(nme.WILDCARD), tpt).withSpan(p.span) + } + Tuple(wildcardPats).withSpan(pat.span) CaseDef( Bind(tmpTuple, pat1), EmptyTree, @@ -1546,6 +1591,8 @@ object desugar { else CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ())) Match(makeSelector(rhs, MatchCheck.IrrefutablePatDef), caseDef :: Nil) + // println(i"matchExpr = $matchExpr") + vars match { case Nil if !mods.is(Lazy) => matchExpr diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 51d140e458b6..69c45416e4ff 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -213,6 +213,11 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => case _ => false } + def isTypedVarPattern(pat: Tree): Boolean = unsplice(pat) match { + case Typed(id: Ident, _) if id.name.isVarPattern && !isBackquoted(id) => true + case _ => false + } + /** The first constructor definition in `stats` */ def firstConstructor(stats: List[Tree]): Tree = stats match { case (meth: DefDef) :: _ if meth.name.isConstructorName => meth @@ -412,6 +417,8 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => tree.tpe.isInstanceOf[ThisType] } + + /** Under x.modularity: Extractor for `annotation.internal.WitnessNames(name_1, ..., name_n)` * represented as an untyped or typed tree. */ diff --git a/tests/pos/simple-tuple-extract.scala b/tests/pos/simple-tuple-extract.scala index ee8ce04084ee..1325cd0ecfc2 100644 --- a/tests/pos/simple-tuple-extract.scala +++ b/tests/pos/simple-tuple-extract.scala @@ -24,6 +24,9 @@ class Test: val (a2, _, c2) = f1 a2 + c2.toString.length() + val (a3, _, _) = f1 + a3 + 1 + def test3 = val (_, b, _) = f1 b.length() + 1 From 74e2b767859eed354828de530615a99213e1174c Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 16 Jun 2025 21:39:16 +0200 Subject: [PATCH 4/9] Remove typed vars logic --- .../src/dotty/tools/dotc/ast/Desugar.scala | 35 ++++++------------- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 13 ------- tests/pos/simple-tuple-extract.scala | 3 ++ 3 files changed, 13 insertions(+), 38 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 9aa1aa7f7b78..9eee43959a75 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1450,15 +1450,13 @@ object desugar { sel end match - case class TuplePatternInfo(arity: Int, varNum: Int, wildcardNum: Int, typedVarNum: Int, typedWildcardNum: Int) + case class TuplePatternInfo(arity: Int, varNum: Int, wildcardNum: Int) object TuplePatternInfo: def apply(pat: Tree)(using Context): TuplePatternInfo = pat match case Tuple(pats) => var arity = 0 var varNum = 0 var wildcardNum = 0 - var typedVarNum = 0 - var typedWildcardNum = 0 pats.foreach: p => arity += 1 p match @@ -1467,15 +1465,10 @@ object desugar { varNum += 1 if id.name == nme.WILDCARD then wildcardNum += 1 - case Typed(id: Ident, _) if !isBackquoted(id) => - if id.name.isVarPattern then - typedVarNum += 1 - if id.name == nme.WILDCARD then - typedWildcardNum += 1 case _ => - TuplePatternInfo(arity, varNum, wildcardNum, typedVarNum, typedWildcardNum) + TuplePatternInfo(arity, varNum, wildcardNum) case _ => - TuplePatternInfo(-1, -1, -1, -1, -1) + TuplePatternInfo(-1, -1, -1) end TuplePatternInfo /** If `pat` is a variable pattern, @@ -1514,7 +1507,7 @@ object desugar { val tuplePatternInfo = TuplePatternInfo(pat) // When desugaring a PatDef in general, we use pattern matching on the rhs - // and collect the variable values in a tuple, then outside the match + // and collect the variable values in a tuple, then outside the match, // we destructure the tuple to get the individual variables. // We can achieve two kinds of tuple optimizations if the pattern is a tuple // of simple variables or wildcards: @@ -1524,8 +1517,8 @@ object desugar { // For example: `val (x, y) = if ... then (1, "a") else (2, "b")` becomes // `val $1$ = if ...; val x = $1$._1; val y = $1$._2`. // 2. Partial optimization: - // If the rhs can be typed as a tuple and matched with correct arity, - // we can return the tuple itself if there are no more than one variable + // If the rhs can be typed as a tuple and matched with correct arity, we can + // return the tuple itself in the case if there are no more than one variable // in the pattern, or return the the value if there is only one variable. val fullTupleOptimizable = @@ -1539,10 +1532,10 @@ object desugar { val partialTupleOptimizable = tuplePatternInfo.arity > 0 - && tuplePatternInfo.arity == tuplePatternInfo.varNum + tuplePatternInfo.typedVarNum + && tuplePatternInfo.arity == tuplePatternInfo.varNum // We exclude the case where there is only one variable, // because it should be handled by `makeTuple` directly. - && tuplePatternInfo.wildcardNum + tuplePatternInfo.typedWildcardNum < tuplePatternInfo.arity - 1 + && tuplePatternInfo.wildcardNum < tuplePatternInfo.arity - 1 val inAliasGenerator = original match case _: GenAlias => true @@ -1551,10 +1544,7 @@ object desugar { val vars: List[VarInfo] = if fullTupleOptimizable || partialTupleOptimizable then // include `_` pat match - case Tuple(pats) => pats.map { - case id: Ident => (id, TypeTree()) - case Typed(id: Ident, tpt) => (id, tpt) - } + case Tuple(pats) => pats.map { case id: Ident => (id, TypeTree()) } else getVariables( tree = pat, @@ -1578,10 +1568,7 @@ object desugar { // Replace all variables with wildcards in the pattern val pat1 = pat match case Tuple(pats) => - val wildcardPats = pats.map { - case id: Ident => Ident(nme.WILDCARD).withSpan(id.span) - case p @ Typed(_: Ident, tpt) => Typed(Ident(nme.WILDCARD), tpt).withSpan(p.span) - } + val wildcardPats = pats.map(p => Ident(nme.WILDCARD).withSpan(p.span)) Tuple(wildcardPats).withSpan(pat.span) CaseDef( Bind(tmpTuple, pat1), @@ -1591,8 +1578,6 @@ object desugar { else CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ())) Match(makeSelector(rhs, MatchCheck.IrrefutablePatDef), caseDef :: Nil) - // println(i"matchExpr = $matchExpr") - vars match { case Nil if !mods.is(Lazy) => matchExpr diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 69c45416e4ff..0a2c0c850e5d 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -207,17 +207,6 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => case _ => false } - /** Is tree a wildcard pattern? Not including `x @ _` */ - def isWildcardPattern(pat: Tree): Boolean = unsplice(pat) match { - case x: Ident => x.name == nme.WILDCARD && !isBackquoted(x) - case _ => false - } - - def isTypedVarPattern(pat: Tree): Boolean = unsplice(pat) match { - case Typed(id: Ident, _) if id.name.isVarPattern && !isBackquoted(id) => true - case _ => false - } - /** The first constructor definition in `stats` */ def firstConstructor(stats: List[Tree]): Tree = stats match { case (meth: DefDef) :: _ if meth.name.isConstructorName => meth @@ -417,8 +406,6 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => tree.tpe.isInstanceOf[ThisType] } - - /** Under x.modularity: Extractor for `annotation.internal.WitnessNames(name_1, ..., name_n)` * represented as an untyped or typed tree. */ diff --git a/tests/pos/simple-tuple-extract.scala b/tests/pos/simple-tuple-extract.scala index 1325cd0ecfc2..736deb7ada3a 100644 --- a/tests/pos/simple-tuple-extract.scala +++ b/tests/pos/simple-tuple-extract.scala @@ -17,6 +17,9 @@ class Test: // val c: AnyRef = $2$._3 a + b.length() + c.toString.length() + // This pattern will not be optimized: + // val (a1, b1, c1: String) = f1 + def test2 = val (_, b, c) = f1 b.length() + c.toString.length() From 310e20b0837c828baa1126177088a4a498f31bfc Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 16 Jun 2025 21:50:33 +0200 Subject: [PATCH 5/9] Enhance forallResults to handle Try --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 2 -- compiler/src/dotty/tools/dotc/ast/TreeInfo.scala | 10 ++++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 9eee43959a75..4aec8fc87234 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1557,8 +1557,6 @@ object desugar { val ids = for ((named, tpt) <- vars) yield Ident(named.name) - // println(s"fullTupleOptimizable = $fullTupleOptimizable, partialTupleOptimizable = $partialTupleOptimizable, ids = $ids") - val matchExpr = if fullTupleOptimizable then rhs else diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 0a2c0c850e5d..f8a5e0dbaf76 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -350,14 +350,16 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => } /** Checks whether predicate `p` is true for all result parts of this expression, - * where we zoom into Ifs, Matches, and Blocks. + * where we zoom into Ifs, Matches, Tries, and Blocks. */ - def forallResults(tree: Tree, p: Tree => Boolean): Boolean = tree match { + def forallResults(tree: Tree, p: Tree => Boolean): Boolean = tree match case If(_, thenp, elsep) => forallResults(thenp, p) && forallResults(elsep, p) - case Match(_, cases) => cases forall (c => forallResults(c.body, p)) + case Match(_, cases) => cases.forall(c => forallResults(c.body, p)) + case Try(_, cases, finalizer) => + cases.forall(c => forallResults(c.body, p)) + && (finalizer.isEmpty || forallResults(finalizer, p)) case Block(_, expr) => forallResults(expr, p) case _ => p(tree) - } /** The tree stripped of the possibly nested applications (term and type). * The original tree if it's not an application. From 5f79048db1562f877bec0f3f7a28508922c769ef Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 18 Jun 2025 01:34:16 +0200 Subject: [PATCH 6/9] Add special handling when the selector of a pattern matching has Nothing type --- .../src/dotty/tools/dotc/typer/Applications.scala | 5 +++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 1 + tests/neg/i7294.check | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index d03f7e7f8a56..0051157574de 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1689,7 +1689,8 @@ trait Applications extends Compatibility { if selType <:< unapplyArgType then unapp.println(i"case 1 $unapplyArgType ${ctx.typerState.constraint}") fullyDefinedType(unapplyArgType, "pattern selector", tree.srcPos) - selType.dropAnnot(defn.UncheckedAnnot) // need to drop @unchecked. Just because the selector is @unchecked, the pattern isn't. + if selType.isNothingType then unapplyArgType + else selType.dropAnnot(defn.UncheckedAnnot) // need to drop @unchecked. Just because the selector is @unchecked, the pattern isn't. else if !ctx.mode.is(Mode.InTypeTest) then checkMatchable(selType, tree.srcPos, pattern = true) @@ -1711,7 +1712,7 @@ trait Applications extends Compatibility { val unapplyPatterns = UnapplyArgs(unapplyApp.tpe, unapplyFn, unadaptedArgs, tree.srcPos) .typedPatterns(qual, this) val result = assignType(cpy.UnApply(tree)(newUnapplyFn, unapplyImplicits(dummyArg, unapplyApp), unapplyPatterns), ownType) - if (ownType.stripped eq selType.stripped) || ownType.isError then result + if (ownType.stripped eq selType.stripped) || selType.isNothingType || ownType.isError then result else tryWithTypeTest(Typed(result, TypeTree(ownType)), selType) case tp => val unapplyErr = if (tp.isError) unapplyFn else notAnExtractor(unapplyFn) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3b6e125fbeb4..eaef9dc6f65d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2816,6 +2816,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if isStableIdentifierOrLiteral || isNamedTuplePattern then pt else if isWildcardStarArg(body1) || pt == defn.ImplicitScrutineeTypeRef + || pt.isNothingType || body1.tpe <:< pt // There is some strange interaction with gadt matching. // and implicit scopes. // run/t2755.scala fails to compile if this subtype test is omitted diff --git a/tests/neg/i7294.check b/tests/neg/i7294.check index 30c076470899..1af233b1279f 100644 --- a/tests/neg/i7294.check +++ b/tests/neg/i7294.check @@ -1,9 +1,9 @@ --- [E007] Type Mismatch Error: tests/neg/i7294.scala:7:15 -------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg/i7294.scala:7:18 -------------------------------------------------------------- 7 | case x: T => x.g(10) // error - | ^ - | Found: (x : Nothing) - | Required: ?{ g: ? } - | Note that implicit conversions were not tried because the result of an implicit conversion - | must be more specific than ?{ g: [applied to (10) returning T] } + | ^^^^^^^ + | Found: Any + | Required: T + | + | where: T is a type in given instance f with bounds <: foo.Foo | | longer explanation available when compiling with `-explain` From 568b115bff21a2e8c5c9730f264d0dba0b12c899 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 18 Jun 2025 12:05:00 +0200 Subject: [PATCH 7/9] Apply change to all bottom types --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 0051157574de..336dce12e015 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1689,7 +1689,7 @@ trait Applications extends Compatibility { if selType <:< unapplyArgType then unapp.println(i"case 1 $unapplyArgType ${ctx.typerState.constraint}") fullyDefinedType(unapplyArgType, "pattern selector", tree.srcPos) - if selType.isNothingType then unapplyArgType + if selType.isBottomType then unapplyArgType else selType.dropAnnot(defn.UncheckedAnnot) // need to drop @unchecked. Just because the selector is @unchecked, the pattern isn't. else if !ctx.mode.is(Mode.InTypeTest) then @@ -1712,7 +1712,7 @@ trait Applications extends Compatibility { val unapplyPatterns = UnapplyArgs(unapplyApp.tpe, unapplyFn, unadaptedArgs, tree.srcPos) .typedPatterns(qual, this) val result = assignType(cpy.UnApply(tree)(newUnapplyFn, unapplyImplicits(dummyArg, unapplyApp), unapplyPatterns), ownType) - if (ownType.stripped eq selType.stripped) || selType.isNothingType || ownType.isError then result + if (ownType.stripped eq selType.stripped) || selType.isBottomType || ownType.isError then result else tryWithTypeTest(Typed(result, TypeTree(ownType)), selType) case tp => val unapplyErr = if (tp.isError) unapplyFn else notAnExtractor(unapplyFn) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index eaef9dc6f65d..7c0741629601 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2816,7 +2816,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if isStableIdentifierOrLiteral || isNamedTuplePattern then pt else if isWildcardStarArg(body1) || pt == defn.ImplicitScrutineeTypeRef - || pt.isNothingType + || pt.isBottomType || body1.tpe <:< pt // There is some strange interaction with gadt matching. // and implicit scopes. // run/t2755.scala fails to compile if this subtype test is omitted @@ -3556,7 +3556,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedTuple(tree: untpd.Tuple, pt: Type)(using Context): Tree = val tree1 = desugar.tuple(tree, pt).withAttachmentsFrom(tree) checkDeprecatedAssignmentSyntax(tree) - if tree1 ne tree then typed(tree1, pt) + if tree1 ne tree then + val t = typed(tree1, pt) + // println(i"typedTuple: ${t} , ${t.tpe}") + t else val arity = tree.trees.length val pts = pt.stripNamedTuple.tupleElementTypes match From 545dd690f3573d3f6c1ff946aa153018c8cb0fa5 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 26 Jun 2025 10:27:09 +0200 Subject: [PATCH 8/9] Add bytecode test to verify absence of Tuple2.apply calls --- .../tools/backend/jvm/DottyBytecodeTest.scala | 9 +++++++++ .../tools/backend/jvm/DottyBytecodeTests.scala | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala index 8a9611a9b165..baae40841508 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala @@ -133,6 +133,15 @@ trait DottyBytecodeTest { }, l.stringLines) } + def assertNoInvoke(m: MethodNode, receiver: String, method: String): Unit = + assertNoInvoke(instructionsFromMethod(m), receiver, method) + def assertNoInvoke(l: List[Instruction], receiver: String, method: String): Unit = { + assert(!l.exists { + case Invoke(_, `receiver`, `method`, _, _) => true + case _ => false + }, s"Found unexpected invoke of $receiver.$method in:\n${l.stringLines}") + } + def diffInstructions(isa: List[Instruction], isb: List[Instruction]): String = { val len = Math.max(isa.length, isb.length) val sb = new StringBuilder diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index e92c4c26adb8..22deeae911d4 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -1606,6 +1606,21 @@ class DottyBytecodeTests extends DottyBytecodeTest { } } + @Test + def simpleTupleExtraction(): Unit = { + val code = + """class C { + | def f1(t: (Int, String)) = + | val (i, s) = t + | i + s.length + |} + """.stripMargin + checkBCode(code) { dir => + val c = loadClassNode(dir.lookupName("C.class", directory = false).input) + assertNoInvoke(getMethod(c, "f1"), "scala/Tuple2$", "apply") // no Tuple2.apply call + } + } + @Test def deprecation(): Unit = { val code = From d614d0ba11e559204759abed9ef39914e3969d0e Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 26 Jun 2025 10:30:41 +0200 Subject: [PATCH 9/9] Remove debug comment --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 7c0741629601..cb0e1b5ce362 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3556,10 +3556,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedTuple(tree: untpd.Tuple, pt: Type)(using Context): Tree = val tree1 = desugar.tuple(tree, pt).withAttachmentsFrom(tree) checkDeprecatedAssignmentSyntax(tree) - if tree1 ne tree then - val t = typed(tree1, pt) - // println(i"typedTuple: ${t} , ${t.tpe}") - t + if tree1 ne tree then typed(tree1, pt) else val arity = tree.trees.length val pts = pt.stripNamedTuple.tupleElementTypes match