From 6e466de5cf44146c84057b34091c0aa6fae9aed6 Mon Sep 17 00:00:00 2001
From: noti0na1 <noti0na1@users.noreply.github.com>
Date: Mon, 16 Jun 2025 02:19:58 +0200
Subject: [PATCH 01/10] 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 <noti0na1@users.noreply.github.com>
Date: Mon, 16 Jun 2025 03:14:13 +0200
Subject: [PATCH 02/10] 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 <noti0na1@users.noreply.github.com>
Date: Mon, 16 Jun 2025 21:25:32 +0200
Subject: [PATCH 03/10] 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 <noti0na1@users.noreply.github.com>
Date: Mon, 16 Jun 2025 21:39:16 +0200
Subject: [PATCH 04/10] 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 <noti0na1@users.noreply.github.com>
Date: Mon, 16 Jun 2025 21:50:33 +0200
Subject: [PATCH 05/10] 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 <noti0na1@users.noreply.github.com>
Date: Wed, 18 Jun 2025 01:34:16 +0200
Subject: [PATCH 06/10] 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 <noti0na1@users.noreply.github.com>
Date: Wed, 18 Jun 2025 12:05:00 +0200
Subject: [PATCH 07/10] 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 <noti0na1@users.noreply.github.com>
Date: Thu, 26 Jun 2025 10:27:09 +0200
Subject: [PATCH 08/10] 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 <noti0na1@users.noreply.github.com>
Date: Thu, 26 Jun 2025 10:30:41 +0200
Subject: [PATCH 09/10] 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

From 82c068c356e20c324b6b3a0308e463de364e65e8 Mon Sep 17 00:00:00 2001
From: noti0na1 <noti0na1@users.noreply.github.com>
Date: Wed, 9 Jul 2025 18:04:58 +0200
Subject: [PATCH 10/10] Enhance bytecode test to verify absence of NEW
 instruction in method f1

---
 .../dotty/tools/backend/jvm/DottyBytecodeTests.scala     | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala
index 22deeae911d4..2e48b33ec624 100644
--- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala
+++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala
@@ -1617,7 +1617,14 @@ class DottyBytecodeTests extends DottyBytecodeTest {
       """.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
+      val f1 = getMethod(c, "f1")
+      assertNoInvoke(f1, "scala/Tuple2$", "apply") // no Tuple2.apply call
+      // no `new` instruction
+      val hasNew = instructionsFromMethod(f1).exists {
+        case Op(Opcodes.NEW) => true
+        case _ => false
+      }
+      assertFalse("f1 should not have NEW instruction", hasNew)
     }
   }