Repository: swc-project/swc (rust)

Issue:
Minifier produces incorrect code when template literals are used
### Describe the bug

The minifier is reusing a variable defined inside of a function when it's not safe to do so. This changes the runtime behavior as demonstrated in this repro.

### Input code

```typescript
let a = 0;
function f(arr) {
  let b = a;
  return `${arr.map((child) => {
  a = b + "str";
})}`;
}

function g(arr) {
    return f(arr);
}
globalThis.g = g;
g([1,2,3])
console.log(a);
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es2015",
    "loose": false,
    "minify": {
      "compress": {
        "arguments": false,
        "arrows": true,
        "booleans": true,
        "booleans_as_integers": false,
        "collapse_vars": true,
        "comparisons": true,
        "computed_props": true,
        "conditionals": true,
        "dead_code": true,
        "directives": true,
        "drop_console": false,
        "drop_debugger": true,
        "evaluate": true,
        "expression": false,
        "hoist_funs": false,
        "hoist_props": true,
        "hoist_vars": false,
        "if_return": true,
        "join_vars": true,
        "keep_classnames": false,
        "keep_fargs": true,
        "keep_fnames": false,
        "keep_infinity": false,
        "loops": true,
        "negate_iife": true,
        "properties": true,
        "reduce_funcs": false,
        "reduce_vars": false,
        "side_effects": true,
        "switches": true,
        "typeofs": true,
        "unsafe": false,
        "unsafe_arrows": false,
        "unsafe_comps": false,
        "unsafe_Function": false,
        "unsafe_math": false,
        "unsafe_symbols": false,
        "unsafe_methods": false,
        "unsafe_proto": false,
        "unsafe_regexp": false,
        "unsafe_undefined": false,
        "unused": true,
        "const_to_let": true,
        "pristine_globals": true
      },
      "mangle": {
        "toplevel": false,
        "keep_classnames": false,
        "keep_fnames": false,
        "keep_private_props": false,
        "ie8": false,
        "safari10": false
      }
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": true,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.3.84&code=H4sIAAAAAAAAA0XNTQrDIBAF4L2nGEIXSoP0ZynJKborhag1RphqUbMKuXsnLTSbgcd7fIOugoYOToqNc7Q1pAgj1zkLWBgAUm2o1opCdnXOEYbDQr186Tfndgr4FND13%2FUGGThCU2puFFvFOtBlu%2Bx3%2Bc%2F9vm07j8lovE2hSE%2BSV8zz%2B7m9tNeHYDbFktBJTIQI9QGn86FuuAAAAA%3D%3D&config=H4sIAAAAAAAAA41VwW7bMAy99ysCn3doB3QY9gG77RsExaIcZbJoiJSboMi%2Fj1bsNG1oY5cg5tMjRfKRen%2Fa7Zojtc2v3bv8lY%2FBZoJ8%2BxYLnRPbk1gaaHtLbQ4DN98W9EgT5G0kqKbLFWnY5g64suj788vrzGgiIsHCmG19SMGf72O22A8ZiO5sYhWXpYfE9Jk%2FYxnfJoBzubfvESPYtIEYSyYkhg6y5rjFGO1AYEabFS%2FTTW0OhFqICSwMzgwZBxVPLnDAJDEfUQfWmRYdKFDI0HIYQaNJLKElkvSUfCrsYF%2B6rvb5CxtGG4tlJSacakvktorXAwZi40vSSngFV2pwBefifmUGbzJwyemRd8SQVnryF0AqEC1Rsj1ofusJL3paY%2FtNZkheJMtnBRd9a1km6KSoJgSvVHaqDGQOWjczuNLCVNlWu84Mr5SPggMD3otWFNf0Frg9aEH5PAB6BZD%2BWq%2Bp6gqY2xSu4NNAbMC%2FJUvWBTaf6C0f1lE693uMGwF64AO6jQPSCsZ1OMuWOA3reEkORBrg1COFKvC4BGQAGE2s%2B%2FJBGzIe4tF0Efcfa2I%2BcLnt4d6mrs773b5kHCKMENdk%2FB8jsonK3cZJ1ctkP0wv%2FNREaWXwwsvzp2dDUnlafmtSTY%2Bu3CVUNXl9Tn40H4eWl2OpWhPoz0Ksdbr8A8XlsejjBgAA

### SWC Info output

_No response_

### Expected behavior

Unminified code prints `0str` while minified code prints `0strstrstr`.

### Actual behavior

_No response_

### Version

1.3.85

### Additional context

_No response_

Pull request description:
fix(es/minifier): Do not inline into a template literal with sequential inliner
**Description:**


**Related issue:**

 - Closes #7969.

Patch:
diff --git a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs
index aa2d29ec36..18bc9afd40 100644
--- a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs
+++ b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs
@@ -1207,6 +1207,13 @@ impl Optimizer<'_> {
 
             Expr::Yield(..) | Expr::Await(..) => false,
 
+            Expr::Tpl(t) => t.exprs.iter().all(|e| self.is_skippable_for_seq(a, e)),
+
+            Expr::TaggedTpl(t) => {
+                self.is_skippable_for_seq(a, &t.tag)
+                    && t.tpl.exprs.iter().all(|e| self.is_skippable_for_seq(a, e))
+            }
+
             Expr::Unary(UnaryExpr {
                 op: op!("!") | op!("void") | op!("typeof") | op!(unary, "-") | op!(unary, "+"),
                 arg,
@@ -1378,14 +1385,12 @@ impl Optimizer<'_> {
                 exprs.iter().all(|e| self.is_skippable_for_seq(a, e))
             }
 
-            Expr::TaggedTpl(..) | Expr::New(..) => {
+            Expr::New(..) => {
                 // TODO(kdy1): We can optimize some known calls.
 
                 false
             }
 
-            Expr::Tpl(Tpl { exprs, .. }) => exprs.iter().all(|e| self.is_skippable_for_seq(a, e)),
-
             // Expressions without any effects
             Expr::This(_)
             | Expr::Fn(_)
@@ -1523,7 +1528,9 @@ impl Optimizer<'_> {
             | Expr::Class(..)
             | Expr::Lit(..)
             | Expr::Await(..)
-            | Expr::Yield(..) => true,
+            | Expr::Yield(..)
+            | Expr::Tpl(..)
+            | Expr::TaggedTpl(..) => true,
             Expr::Unary(UnaryExpr {
                 op: op!("delete"), ..
             }) => true,


Test patch:
diff --git a/crates/swc/tests/fixture/issues-7xxx/7969/input/.swcrc b/crates/swc/tests/fixture/issues-7xxx/7969/input/.swcrc
new file mode 100644
index 0000000000..63a5ce65cc
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7969/input/.swcrc
@@ -0,0 +1,72 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "ecmascript",
+            "jsx": false
+        },
+        "target": "es2015",
+        "loose": false,
+        "minify": {
+            "compress": {
+                "arguments": false,
+                "arrows": true,
+                "booleans": true,
+                "booleans_as_integers": false,
+                "collapse_vars": true,
+                "comparisons": true,
+                "computed_props": true,
+                "conditionals": true,
+                "dead_code": true,
+                "directives": true,
+                "drop_console": false,
+                "drop_debugger": true,
+                "evaluate": true,
+                "expression": false,
+                "hoist_funs": false,
+                "hoist_props": true,
+                "hoist_vars": false,
+                "if_return": true,
+                "join_vars": true,
+                "keep_classnames": false,
+                "keep_fargs": true,
+                "keep_fnames": false,
+                "keep_infinity": false,
+                "loops": true,
+                "negate_iife": true,
+                "properties": true,
+                "reduce_funcs": false,
+                "reduce_vars": false,
+                "side_effects": true,
+                "switches": true,
+                "typeofs": true,
+                "unsafe": false,
+                "unsafe_arrows": false,
+                "unsafe_comps": false,
+                "unsafe_Function": false,
+                "unsafe_math": false,
+                "unsafe_symbols": false,
+                "unsafe_methods": false,
+                "unsafe_proto": false,
+                "unsafe_regexp": false,
+                "unsafe_undefined": false,
+                "unused": true,
+                "const_to_let": true,
+                "pristine_globals": true,
+                "passes": 1
+            },
+            "mangle": {
+                "toplevel": false,
+                "keep_classnames": false,
+                "keep_fnames": false,
+                "keep_private_props": false,
+                "ie8": false,
+                "safari10": false
+            }
+        }
+    },
+    "module": {
+        "type": "es6"
+    },
+    "minify": true,
+    "isModule": true
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-7xxx/7969/input/1.js b/crates/swc/tests/fixture/issues-7xxx/7969/input/1.js
new file mode 100644
index 0000000000..68f9ed2ced
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7969/input/1.js
@@ -0,0 +1,14 @@
+let a = 0;
+function f(arr) {
+    let b = a;
+    return `${arr.map((child) => {
+        a = b + "str";
+    })}`;
+}
+
+function g(arr) {
+    return f(arr);
+}
+globalThis.g = g;
+g([1, 2, 3])
+console.log(a);
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-7xxx/7969/output/1.js b/crates/swc/tests/fixture/issues-7xxx/7969/output/1.js
new file mode 100644
index 0000000000..f782350382
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7969/output/1.js
@@ -0,0 +1,1 @@
+let a=0;function g(l){var o;let t;return o=l,t=a,`${o.map(l=>{a=t+"str"})}`}globalThis.g=g,g([1,2,3]),console.log(a);
diff --git a/crates/swc/tests/tsc-references/taggedTemplateStringsTypeArgumentInferenceES6.2.minified.js b/crates/swc/tests/tsc-references/taggedTemplateStringsTypeArgumentInferenceES6.2.minified.js
index e2984e50e8..68d8549c36 100644
--- a/crates/swc/tests/tsc-references/taggedTemplateStringsTypeArgumentInferenceES6.2.minified.js
+++ b/crates/swc/tests/tsc-references/taggedTemplateStringsTypeArgumentInferenceES6.2.minified.js
@@ -1,6 +1,5 @@
 //// [taggedTemplateStringsTypeArgumentInferenceES6.ts]
 // Generic tag with one parameter
-var anyVar;
 // Generic tag with argument of function type whose parameter is not of type parameter type but body/return type uses type parameter
 function someGenerics3(strs, producer) {}
 // 2 parameter generic tag with argument 1 of type parameter type and argument 2 of function type whose parameter is of type parameter type
@@ -11,14 +10,16 @@ function someGenerics5(strs, n, f) {}
 function someGenerics6(strs, a, b, c) {}
 // Generic tag with multiple arguments of function types that each have parameters of different generic type
 function someGenerics7(strs, a, b, c) {}
+someGenerics3`${()=>''}`, someGenerics3`${()=>void 0}`, someGenerics3`${()=>3}`, someGenerics4`${4}${()=>null}`, someGenerics4`${''}${()=>3}`, someGenerics4`${null}${null}`, someGenerics5`${4} ${()=>null}`, someGenerics5`${''}${()=>3}`, someGenerics5`${null}${null}`, someGenerics6`${(n)=>n}${(n)=>n}${(n)=>n}`, someGenerics6`${(n)=>n}${(n)=>n}${(n)=>n}`, someGenerics6`${(n)=>n}${(n)=>n}${(n)=>n}`, someGenerics7`${(n)=>n}${(n)=>n}${(n)=>n}`, someGenerics7`${(n)=>n}${(n)=>n}${(n)=>n}`, someGenerics7`${(n)=>n}${(n)=>n}${(n)=>n}`;
+var anyVar, x = // Generic tag with argument of generic function type
+(function(strs, n) {
+    return n;
+})`${someGenerics7}`;
 // Generic tag with multiple parameters of generic type passed arguments with no best common type
 function someGenerics9(strs, a, b, c) {
     return null;
 }
-someGenerics3`${()=>''}`, someGenerics3`${()=>void 0}`, someGenerics3`${()=>3}`, someGenerics4`${4}${()=>null}`, someGenerics4`${''}${()=>3}`, someGenerics4`${null}${null}`, someGenerics5`${4} ${()=>null}`, someGenerics5`${''}${()=>3}`, someGenerics5`${null}${null}`, someGenerics6`${(n)=>n}${(n)=>n}${(n)=>n}`, someGenerics6`${(n)=>n}${(n)=>n}${(n)=>n}`, someGenerics6`${(n)=>n}${(n)=>n}${(n)=>n}`, someGenerics7`${(n)=>n}${(n)=>n}${(n)=>n}`, someGenerics7`${(n)=>n}${(n)=>n}${(n)=>n}`, someGenerics7`${(n)=>n}${(n)=>n}${(n)=>n}`, // Generic tag with argument of generic function type
-(function(strs, n) {
-    return n;
-})`${someGenerics7}``${null}${null}${null}`, someGenerics9`${''}${0}${[]}`, someGenerics9`${void 0}${{
+x`${null}${null}${null}`, someGenerics9`${''}${0}${[]}`, someGenerics9`${void 0}${{
     x: 6,
     z: new Date()
 }}${{
diff --git a/crates/swc/tests/tsc-references/taggedTemplateStringsWithManyCallAndMemberExpressionsES6.2.minified.js b/crates/swc/tests/tsc-references/taggedTemplateStringsWithManyCallAndMemberExpressionsES6.2.minified.js
index e7d3e5ef64..6bb58d6ee5 100644
--- a/crates/swc/tests/tsc-references/taggedTemplateStringsWithManyCallAndMemberExpressionsES6.2.minified.js
+++ b/crates/swc/tests/tsc-references/taggedTemplateStringsWithManyCallAndMemberExpressionsES6.2.minified.js
@@ -1,2 +1,3 @@
 //// [taggedTemplateStringsWithManyCallAndMemberExpressionsES6.ts]
-new new new (void 0)`abc${0}def`.member("hello")(42);
+var f;
+new new new f`abc${0}def`.member("hello")(42);
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/7969/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/7969/config.json
new file mode 100644
index 0000000000..e24c2f770e
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/7969/config.json
@@ -0,0 +1,46 @@
+{
+    "arguments": false,
+    "arrows": true,
+    "booleans": true,
+    "booleans_as_integers": false,
+    "collapse_vars": true,
+    "comparisons": true,
+    "computed_props": true,
+    "conditionals": true,
+    "dead_code": true,
+    "directives": true,
+    "drop_console": false,
+    "drop_debugger": true,
+    "evaluate": true,
+    "expression": false,
+    "hoist_funs": false,
+    "hoist_props": true,
+    "hoist_vars": false,
+    "if_return": true,
+    "join_vars": true,
+    "keep_classnames": false,
+    "keep_fargs": true,
+    "keep_fnames": false,
+    "keep_infinity": false,
+    "loops": true,
+    "negate_iife": true,
+    "properties": true,
+    "reduce_funcs": false,
+    "reduce_vars": false,
+    "side_effects": true,
+    "switches": true,
+    "typeofs": true,
+    "unsafe": false,
+    "unsafe_arrows": false,
+    "unsafe_comps": false,
+    "unsafe_Function": false,
+    "unsafe_math": false,
+    "unsafe_symbols": false,
+    "unsafe_methods": false,
+    "unsafe_proto": false,
+    "unsafe_regexp": false,
+    "unsafe_undefined": false,
+    "unused": true,
+    "const_to_let": true,
+    "pristine_globals": true
+}
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/7969/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/7969/input.js
new file mode 100644
index 0000000000..68f9ed2ced
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/7969/input.js
@@ -0,0 +1,14 @@
+let a = 0;
+function f(arr) {
+    let b = a;
+    return `${arr.map((child) => {
+        a = b + "str";
+    })}`;
+}
+
+function g(arr) {
+    return f(arr);
+}
+globalThis.g = g;
+g([1, 2, 3])
+console.log(a);
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/7969/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/7969/output.js
new file mode 100644
index 0000000000..6f92a394b5
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/7969/output.js
@@ -0,0 +1,17 @@
+let a = 0;
+function f(arr) {
+    let b = a;
+    return `${arr.map((child)=>{
+        a = b + "str";
+    })}`;
+}
+function g(arr) {
+    return f(arr);
+}
+globalThis.g = g;
+g([
+    1,
+    2,
+    3
+]);
+console.log(a);
diff --git a/crates/swc_ecma_minifier/tests/fixture/next/wrap-contracts/output.js b/crates/swc_ecma_minifier/tests/fixture/next/wrap-contracts/output.js
index ff1347299d..d7d4ba6cbf 100644
--- a/crates/swc_ecma_minifier/tests/fixture/next/wrap-contracts/output.js
+++ b/crates/swc_ecma_minifier/tests/fixture/next/wrap-contracts/output.js
@@ -19083,8 +19083,8 @@
                                 if (Array.isArray(value1)) {
                                     if (0 === value1.length) return '[]';
                                     if (maximumDepth1 < stack1.length + 1) return '"[Array]"';
-                                    stack1.push(value1);
-                                    let res1 = `\n${indentation1 += spacer1}`;
+                                    stack1.push(value1), indentation1 += spacer1;
+                                    let res1 = `\n${indentation1}`;
                                     const join1 = `,\n${indentation1}`, maximumValuesToStringify1 = Math.min(value1.length, maximumBreadth1);
                                     let i2 = 0;
                                     for(; i2 < maximumValuesToStringify1 - 1; i2++){