fix corner cases in `collapse_vars` (#4555)
authorAlex Lam S.L <alexlamsl@gmail.com>
Thu, 14 Jan 2021 16:22:34 +0000 (16:22 +0000)
committerGitHub <noreply@github.com>
Thu, 14 Jan 2021 16:22:34 +0000 (00:22 +0800)
fixes #4554

lib/compress.js
test/compress/destructured.js

index f0ed677..fadd741 100644 (file)
@@ -1715,7 +1715,7 @@ merge(Compressor.prototype, {
                     var funarg = candidate.name instanceof AST_SymbolFunarg;
                     var may_throw = return_false;
                     if (candidate.may_throw(compressor)) {
-                        if (funarg && scope instanceof AST_AsyncFunction) continue;
+                        if (funarg && is_async(scope)) continue;
                         may_throw = in_try ? function(node) {
                             return node.has_side_effects(compressor);
                         } : side_effects_external;
@@ -1727,6 +1727,9 @@ merge(Compressor.prototype, {
                     var lhs_local = is_lhs_local(lhs);
                     var rvalue = get_rvalue(candidate);
                     if (!side_effects) side_effects = value_has_side_effects();
+                    var check_destructured = in_try || !lhs_local ? function(node) {
+                        return node instanceof AST_Destructured;
+                    } : return_false;
                     var replace_all = replace_all_symbols(candidate);
                     var hit = funarg;
                     var abort = false;
@@ -1833,7 +1836,6 @@ merge(Compressor.prototype, {
                 }
                 if (node instanceof AST_Debugger) return true;
                 if (node instanceof AST_Defun) return funarg && lhs.name === node.name.name;
-                if (node instanceof AST_Destructured) return (in_try || !lhs_local) && parent instanceof AST_Assign;
                 if (node instanceof AST_DestructuredKeyVal) return node.key instanceof AST_Node;
                 if (node instanceof AST_DWLoop) return true;
                 if (node instanceof AST_LoopControl) return true;
@@ -1931,7 +1933,7 @@ merge(Compressor.prototype, {
                 }
                 if (node instanceof AST_This) return symbol_in_lvalues(node, parent);
                 if (node instanceof AST_VarDef) {
-                    if ((in_try || !lhs_local) && node.name instanceof AST_Destructured) return true;
+                    if (check_destructured(node.name)) return true;
                     return (node.value || parent instanceof AST_Let) && node.name.match_symbol(function(node) {
                         return node instanceof AST_SymbolDeclaration
                             && (lvalues.has(node.name) || side_effects && may_modify(node));
@@ -1940,13 +1942,38 @@ merge(Compressor.prototype, {
                 var sym = is_lhs(node.left, node);
                 if (!sym) return false;
                 if (sym instanceof AST_PropAccess) return true;
-                if ((in_try || !lhs_local) && sym instanceof AST_Destructured) return true;
+                if (check_destructured(sym)) return true;
                 return sym.match_symbol(function(node) {
                     return node instanceof AST_SymbolRef
                         && (lvalues.has(node.name) || read_toplevel && compressor.exposed(node.definition()));
                 }, true);
             }
 
+            function may_throw_destructured(node, value) {
+                if (!value) return !(node instanceof AST_Symbol);
+                if (node instanceof AST_DefaultValue) {
+                    return value.has_side_effects(compressor)
+                        || node.value.has_side_effects(compressor)
+                        || may_throw_destructured(node.name, is_undefined(value) && node.value);
+                }
+                if (node instanceof AST_Destructured) {
+                    if (node.rest && may_throw_destructured(node.rest)) return true;
+                    if (node instanceof AST_DestructuredArray) {
+                        if (!(value instanceof AST_Array || value.is_string(compressor))) return true;
+                        return !all(node.elements, function(element) {
+                            return !may_throw_destructured(element);
+                        });
+                    }
+                    if (node instanceof AST_DestructuredObject) {
+                        if (!value.is_defined(compressor)) return true;
+                        return !all(node.properties, function(prop) {
+                            if (prop instanceof AST_Node && prop.has_side_effects(compressor)) return false;
+                            return !may_throw_destructured(prop.value);
+                        });
+                    }
+                }
+            }
+
             function extract_args() {
                 var iife, fn = compressor.self();
                 if (is_function(fn)
@@ -1960,7 +1987,7 @@ merge(Compressor.prototype, {
                     })) {
                     var fn_strict = compressor.has_directive("use strict");
                     if (fn_strict && !member(fn_strict, fn.body)) fn_strict = false;
-                    var has_await = fn instanceof AST_AsyncFunction ? function(node) {
+                    var has_await = is_async(fn) ? function(node) {
                         return node instanceof AST_Symbol && node.name == "await";
                     } : function(node) {
                         return node instanceof AST_Await && !tw.find_parent(AST_Scope);
@@ -1984,18 +2011,19 @@ merge(Compressor.prototype, {
                         }
                     });
                     args = iife.args.slice();
+                    var len = args.length;
                     var names = Object.create(null);
                     for (var i = fn.argnames.length; --i >= 0;) {
                         var sym = fn.argnames[i];
-                        var arg = iife.args[i];
+                        var arg = args[i];
                         var value;
                         if (sym instanceof AST_DefaultValue) {
                             value = sym.value;
                             sym = sym.name;
-                            args[iife.args.length + i] = value;
+                            args[len + i] = value;
                         }
                         if (sym instanceof AST_Destructured) {
-                            if (!sym.match_symbol(return_false)) continue;
+                            if (!may_throw_destructured(sym, arg)) continue;
                             candidates.length = 0;
                             break;
                         }
@@ -2015,7 +2043,7 @@ merge(Compressor.prototype, {
                             value: arg
                         });
                         candidate.name_index = i;
-                        candidate.arg_index = value ? iife.args.length + i : i;
+                        candidate.arg_index = value ? len + i : i;
                         candidates.unshift([ candidate ]);
                     }
                 }
@@ -2273,7 +2301,10 @@ merge(Compressor.prototype, {
                         can_replace = false;
                         var after = stop_after;
                         var if_hit = stop_if_hit;
+                        var stack = scanner.stack;
+                        scanner.stack = [ parent ];
                         lhs.transform(scanner);
+                        scanner.stack = stack;
                         stop_if_hit = if_hit;
                         stop_after = after;
                         can_replace = replace;
index 029b442..7cc6f46 100644 (file)
@@ -2456,3 +2456,34 @@ issue_4519_2: {
     expect_stdout: "PASS"
     node_version: ">=6"
 }
+
+issue_4554: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        A = "PASS";
+        var a = "FAIL";
+        try {
+            (function({}, b) {
+                return b;
+            })(void 0, a = A);
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect: {
+        A = "PASS";
+        var a = "FAIL";
+        try {
+            (function({}, b) {
+                return b;
+            })(void 0, a = A);
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}