support destructuring of `catch` variable (#4412)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sat, 19 Dec 2020 04:28:38 +0000 (12:28 +0800)
committerGitHub <noreply@github.com>
Sat, 19 Dec 2020 04:28:38 +0000 (12:28 +0800)
lib/ast.js
lib/compress.js
lib/parse.js
test/compress/destructured.js
test/ufuzz/index.js

index fcbfb71..e966cef 100644 (file)
@@ -786,7 +786,7 @@ var AST_Try = DEFNODE("Try", "bcatch bfinally", {
 var AST_Catch = DEFNODE("Catch", "argname", {
     $documentation: "A `catch` node; only makes sense as part of a `try` statement",
     $propdoc: {
-        argname: "[AST_SymbolCatch?] symbol for the exception, or null if not present",
+        argname: "[(AST_Destructured|AST_SymbolCatch)?] symbol for the exception, or null if not present",
     },
     walk: function(visitor) {
         var node = this;
@@ -796,9 +796,9 @@ var AST_Catch = DEFNODE("Catch", "argname", {
         });
     },
     _validate: function() {
-        if (this.argname != null) {
-            if (!(this.argname instanceof AST_SymbolCatch)) throw new Error("argname must be AST_SymbolCatch");
-        }
+        if (this.argname != null) validate_destructured(this.argname, function(node) {
+            if (!(node instanceof AST_SymbolCatch)) throw new Error("argname must be AST_SymbolCatch");
+        });
     },
 }, AST_Block);
 
@@ -868,7 +868,7 @@ var AST_Var = DEFNODE("Var", null, {
 var AST_VarDef = DEFNODE("VarDef", "name value", {
     $documentation: "A variable declaration; only appears in a AST_Definitions node",
     $propdoc: {
-        name: "[AST_SymbolVar] name of the variable",
+        name: "[AST_Destructured|AST_SymbolVar] name of the variable",
         value: "[AST_Node?] initializer, or null of there's no initializer"
     },
     walk: function(visitor) {
index f33b97b..1aab368 100644 (file)
@@ -4949,11 +4949,13 @@ merge(Compressor.prototype, {
                 walk_body(node, tw);
                 pop();
                 if (node.bcatch) {
-                    if (node.bcatch.argname) {
-                        var def = node.bcatch.argname.definition();
-                        references[def.id] = false;
-                        if (def = def.redefined()) references[def.id] = false;
-                    }
+                    if (node.bcatch.argname) node.bcatch.argname.mark_symbol(function(node) {
+                        if (node instanceof AST_SymbolCatch) {
+                            var def = node.definition();
+                            references[def.id] = false;
+                            if (def = def.redefined()) references[def.id] = false;
+                        }
+                    }, tw);
                     push();
                     if (node.bfinally) segment.block = node.bcatch;
                     walk_body(node.bcatch, tw);
@@ -5317,6 +5319,34 @@ merge(Compressor.prototype, {
         var unused_fn_names = [];
         var calls_to_drop_args = [];
         var fns_with_marked_args = [];
+        var trimmer = new TreeTransformer(function(node) {
+            if (node instanceof AST_DestructuredArray) {
+                var trim = true;
+                for (var i = node.elements.length; --i >= 0;) {
+                    var sym = node.elements[i];
+                    if (!(sym instanceof AST_SymbolDeclaration)) {
+                        node.elements[i] = sym.transform(trimmer);
+                        trim = false;
+                    } else if (sym.definition().id in in_use_ids) {
+                        trim = false;
+                    } else if (trim) {
+                        node.elements.pop();
+                    } else {
+                        node.elements[i] = make_node(AST_Hole, sym);
+                    }
+                }
+                return node;
+            }
+            if (node instanceof AST_DestructuredKeyVal) {
+                if (!(node.value instanceof AST_SymbolDeclaration)) {
+                    node.value = node.value.transform(trimmer);
+                    return node;
+                }
+                if (typeof node.key != "string") return node;
+                if (node.value.definition().id in in_use_ids) return node;
+                return List.skip;
+            }
+        });
         var tt = new TreeTransformer(function(node, descend, in_list) {
             var parent = tt.parent();
             if (drop_vars) {
@@ -5383,34 +5413,7 @@ merge(Compressor.prototype, {
                     for (var a = node.argnames, i = a.length; --i >= 0;) {
                         var sym = a[i];
                         if (sym instanceof AST_Destructured) {
-                            sym.transform(new TreeTransformer(function(node) {
-                                if (node instanceof AST_DestructuredArray) {
-                                    var trim = true;
-                                    for (var i = node.elements.length; --i >= 0;) {
-                                        var sym = node.elements[i];
-                                        if (!(sym instanceof AST_SymbolFunarg)) {
-                                            node.elements[i] = sym.transform(this);
-                                            trim = false;
-                                        } else if (sym.definition().id in in_use_ids) {
-                                            trim = false;
-                                        } else if (trim) {
-                                            node.elements.pop();
-                                        } else {
-                                            node.elements[i] = make_node(AST_Hole, sym);
-                                        }
-                                    }
-                                    return node;
-                                }
-                                if (node instanceof AST_DestructuredKeyVal) {
-                                    if (!(node.value instanceof AST_SymbolFunarg)) {
-                                        node.value = node.value.transform(this);
-                                        return node;
-                                    }
-                                    if (typeof node.key != "string") return node;
-                                    if (node.value.definition().id in in_use_ids) return node;
-                                    return List.skip;
-                                }
-                            }));
+                            sym.transform(trimmer);
                             trim = false;
                             continue;
                         }
@@ -5429,6 +5432,9 @@ merge(Compressor.prototype, {
                     fns_with_marked_args.push(node);
                 }
             }
+            if (node instanceof AST_Catch && node.argname instanceof AST_Destructured) {
+                node.argname.transform(trimmer);
+            }
             if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) {
                 // place uninitialized names at the start
                 var body = [], head = [], tail = [];
@@ -7263,7 +7269,9 @@ merge(Compressor.prototype, {
         self.body = tighten_body(self.body, compressor);
         if (compressor.option("dead_code")) {
             if (has_declarations_only(self)
-                && !(self.bcatch && self.bcatch.argname && !can_drop_symbol(self.bcatch.argname))) {
+                && !(self.bcatch && self.bcatch.argname && self.bcatch.argname.match_symbol(function(node) {
+                    return node instanceof AST_SymbolCatch && !can_drop_symbol(node);
+                }, true))) {
                 var body = [];
                 if (self.bcatch) {
                     extract_declarations_from_unreachable_code(compressor, self.bcatch, body);
index f760d0d..3b7d15c 100644 (file)
@@ -1216,7 +1216,7 @@ function parse($TEXT, options) {
             var name = null;
             if (is("punc", "(")) {
                 next();
-                name = as_symbol(AST_SymbolCatch);
+                name = maybe_destructured(AST_SymbolCatch);
                 expect(")");
             }
             bcatch = new AST_Catch({
index 8e1cf68..4c36e45 100644 (file)
@@ -750,6 +750,46 @@ simple_var: {
     node_version: ">=6"
 }
 
+drop_catch: {
+    options = {
+        dead_code: true,
+    }
+    input: {
+        try {} catch ({
+            [console.log("FAIL")]: e,
+        }) {} finally {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+drop_catch_var: {
+    options = {
+        unused: true,
+    }
+    input: {
+        try {
+            throw new Error("PASS");
+        } catch ({ name, message }) {
+            console.log(message);
+        }
+    }
+    expect: {
+        try {
+            throw new Error("PASS");
+        } catch ({ message }) {
+            console.log(message);
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
 collapse_vars_1: {
     options = {
         collapse_vars: true,
index eb38a75..848b22b 100644 (file)
@@ -858,22 +858,44 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
         if (n !== 1) {
             // the catch var should only be accessible in the catch clause...
             // we have to do go through some trouble here to prevent leaking it
-            var nameLenBefore = VAR_NAMES.length;
             mayCreateBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
-                if (SUPPORT.catch_omit_var && rng(20) == 0) {
+                var block_len = block_vars.length;
+                var nameLenBefore = VAR_NAMES.length;
+                var unique_len = unique_vars.length;
+                var offset = SUPPORT.catch_omit_var ? 0 : SUPPORT.destructuring ? 1 : 2;
+                switch (offset + rng(20 - offset)) {
+                  case 0:
                     s += " catch { ";
-                } else {
-                    var catchName = createVarName(MANDATORY);
-                    if (!catch_redef) unique_vars.push(catchName);
-                    s += " catch (" + catchName + ") { ";
+                    break;
+                  case 1:
+                    var name = createVarName(MANDATORY);
+                    block_vars.push(name);
+                    var message = createVarName(MANDATORY);
+                    block_vars.push(message);
+                    if (SUPPORT.computed_key && rng(10) == 0) {
+                        s += " catch ({  message: " + message + ", ";
+                        addAvoidVars([ name ]);
+                        s += "[" + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + "]: " + name;
+                        removeAvoidVars([ name ]);
+                        s += " }) { ";
+                    } else {
+                        s += " catch ({ name: " + name + ", message: " + message + " }) { ";
+                    }
+                    break;
+                  default:
+                    var name = createVarName(MANDATORY);
+                    if (!catch_redef) unique_vars.push(name);
+                    s += " catch (" + name + ") { ";
+                    break;
                 }
-                var freshCatchName = VAR_NAMES.length !== nameLenBefore;
+                var catches = VAR_NAMES.length - nameLenBefore;
                 s += defns() + "\n";
                 s += _createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth);
                 s += " }";
-                // remove catch name
-                if (!catch_redef) unique_vars.pop();
-                if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1);
+                // remove catch variables
+                block_vars.length = block_len;
+                if (catches > 0) VAR_NAMES.splice(nameLenBefore, catches);
+                unique_vars.length = unique_len;
             });
         }
         if (n !== 0) s += " finally { " + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + " }";