support destructured literals (#4278)
authorAlex Lam S.L <alexlamsl@gmail.com>
Tue, 17 Nov 2020 00:01:24 +0000 (00:01 +0000)
committerGitHub <noreply@github.com>
Tue, 17 Nov 2020 00:01:24 +0000 (08:01 +0800)
README.md
lib/ast.js
lib/compress.js
lib/output.js
lib/parse.js
lib/scope.js
lib/transform.js
test/compress/destructured.js [new file with mode: 0644]
test/reduce.js
test/ufuzz/index.js

index ecc844b..c0b1e8b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -906,6 +906,8 @@ can pass additional arguments that control the code output:
 
 - `shebang` (default `true`) -- preserve shebang `#!` in preamble (bash scripts)
 
+- `v8` (default `false`) -- enable workarounds for Chrome & Node.js bugs
+
 - `webkit` (default `false`) -- enable workarounds for WebKit bugs.
   PhantomJS users should set this option to `true`.
 
@@ -1138,7 +1140,7 @@ To enable fast minify mode with the API use:
 UglifyJS.minify(code, { compress: false, mangle: true });
 ```
 
-#### Source maps and debugging
+### Source maps and debugging
 
 Various `compress` transforms that simplify, rearrange, inline and remove code
 are known to have an adverse effect on debugging with source maps. This is
@@ -1150,6 +1152,10 @@ disable the Uglify `compress` option and just use `mangle`.
 
 To allow for better optimizations, the compiler makes various assumptions:
 
+- The code does not rely on preserving its runtime performance characteristics.
+  Typically uglified code will run faster due to less instructions and easier
+  inlining, but may be slower on rare occasions for a specific platform, e.g.
+  see [`reduce_funcs`](#compress-options).
 - `.toString()` and `.valueOf()` don't have side effects, and for built-in
   objects they have not been overridden.
 - `undefined`, `NaN` and `Infinity` have not been externally redefined.
@@ -1177,3 +1183,7 @@ To allow for better optimizations, the compiler makes various assumptions:
   top.B = "PASS";
   console.log(B);
   ```
+- Use of `arguments` alongside destructuring as function parameters, e.g.
+  `function({}, arguments) {}` will result in `SyntaxError` in earlier versions
+  of Chrome and Node.js - UglifyJS may modify the input which in turn may
+  suppress those errors.
index 02a38b9..dc12a3f 100644 (file)
@@ -414,8 +414,12 @@ var AST_ForIn = DEFNODE("ForIn", "init object", {
     _validate: function() {
         if (this.init instanceof AST_Definitions) {
             if (this.init.definitions.length != 1) throw new Error("init must have single declaration");
-        } else if (!(this.init instanceof AST_PropAccess || this.init instanceof AST_SymbolRef)) {
-            throw new Error("init must be assignable");
+        } else {
+            validate_destructured(this.init, function(node) {
+                if (!(node instanceof AST_PropAccess || node instanceof AST_SymbolRef)) {
+                    throw new Error("init must be assignable: " + node.TYPE);
+                }
+            });
         }
         must_be_expression(this, "object");
     },
@@ -496,12 +500,26 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
     }
 }, AST_Scope);
 
-var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments length_read", {
+var AST_Lambda = DEFNODE("Lambda", "name argnames length_read uses_arguments", {
     $documentation: "Base class for functions",
     $propdoc: {
         name: "[AST_SymbolDeclaration?] the name of this function",
-        argnames: "[AST_SymbolFunarg*] array of function arguments",
-        uses_arguments: "[boolean/S] tells whether this function accesses the arguments array"
+        argnames: "[(AST_Destructured|AST_SymbolFunarg)*] array of function arguments and/or destructured literals",
+        uses_arguments: "[boolean/S] tells whether this function accesses the arguments array",
+    },
+    each_argname: function(visit) {
+        var tw = new TreeWalker(function(node) {
+            if (node instanceof AST_DestructuredObject) {
+                node.properties.forEach(function(prop) {
+                    prop.value.walk(tw);
+                });
+                return true;
+            }
+            if (node instanceof AST_SymbolFunarg) visit(node);
+        });
+        this.argnames.forEach(function(argname) {
+            argname.walk(tw);
+        });
     },
     walk: function(visitor) {
         var node = this;
@@ -515,7 +533,9 @@ var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments length_read", {
     },
     _validate: function() {
         this.argnames.forEach(function(node) {
-            if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
+            validate_destructured(node, function(node) {
+                if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
+            });
         });
     },
 }, AST_Scope);
@@ -748,8 +768,10 @@ var AST_Const = DEFNODE("Const", null, {
     _validate: function() {
         this.definitions.forEach(function(node) {
             if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
-            if (!(node.name instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
-            must_be_expression(node, "value");
+            validate_destructured(node.name, function(node) {
+                if (!(node instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
+            });
+            if (node.value != null) must_be_expression(node, "value");
         });
     },
 }, AST_Definitions);
@@ -759,7 +781,9 @@ var AST_Let = DEFNODE("Let", null, {
     _validate: function() {
         this.definitions.forEach(function(node) {
             if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
-            if (!(node.name instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet");
+            validate_destructured(node.name, function(node) {
+                if (!(node instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet");
+            });
             if (node.value != null) must_be_expression(node, "value");
         });
     },
@@ -770,7 +794,9 @@ var AST_Var = DEFNODE("Var", null, {
     _validate: function() {
         this.definitions.forEach(function(node) {
             if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
-            if (!(node.name instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
+            validate_destructured(node.name, function(node) {
+                if (!(node instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
+            });
             if (node.value != null) must_be_expression(node, "value");
         });
     },
@@ -969,6 +995,14 @@ var AST_Assign = DEFNODE("Assign", null, {
     $documentation: "An assignment expression — `a = b + 5`",
     _validate: function() {
         if (this.operator.indexOf("=") < 0) throw new Error('operator must contain "="');
+        if (this.left instanceof AST_Destructured) {
+            if (this.operator != "=") throw new Error("invalid destructuring operator: " + this.operator);
+            validate_destructured(this.left, function(node) {
+                if (!(node instanceof AST_PropAccess || node instanceof AST_SymbolRef)) {
+                    throw new Error("left must be assignable: " + node.TYPE);
+                }
+            });
+        }
     },
 }, AST_Binary);
 
@@ -992,6 +1026,77 @@ var AST_Array = DEFNODE("Array", "elements", {
     },
 });
 
+var AST_Destructured = DEFNODE("Destructured", null, {
+    $documentation: "Base class for destructured literal",
+});
+
+function validate_destructured(node, check) {
+    if (node instanceof AST_DestructuredArray) return node.elements.forEach(function(node) {
+        if (!(node instanceof AST_Hole)) validate_destructured(node, check);
+    });
+    if (node instanceof AST_DestructuredObject) return node.properties.forEach(function(prop) {
+        validate_destructured(prop.value, check);
+    });
+    check(node);
+}
+
+var AST_DestructuredArray = DEFNODE("DestructuredArray", "elements", {
+    $documentation: "A destructured array literal",
+    $propdoc: {
+        elements: "[AST_Node*] array of elements",
+    },
+    walk: function(visitor) {
+        var node = this;
+        visitor.visit(node, function() {
+            node.elements.forEach(function(element) {
+                element.walk(visitor);
+            });
+        });
+    },
+}, AST_Destructured);
+
+var AST_DestructuredKeyVal = DEFNODE("DestructuredKeyVal", "key value", {
+    $documentation: "A key: value destructured property",
+    $propdoc: {
+        key: "[string|AST_Node] property name.  For computed property this is an AST_Node.",
+        value: "[AST_Node] property value",
+    },
+    walk: function(visitor) {
+        var node = this;
+        visitor.visit(node, function() {
+            if (node.key instanceof AST_Node) node.key.walk(visitor);
+            node.value.walk(visitor);
+        });
+    },
+    _validate: function() {
+        if (typeof this.key != "string") {
+            if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node");
+            must_be_expression(this, "key");
+        }
+        must_be_expression(this, "value");
+    },
+});
+
+var AST_DestructuredObject = DEFNODE("DestructuredObject", "properties", {
+    $documentation: "A destructured object literal",
+    $propdoc: {
+        properties: "[AST_DestructuredKeyVal*] array of properties",
+    },
+    walk: function(visitor) {
+        var node = this;
+        visitor.visit(node, function() {
+            node.properties.forEach(function(prop) {
+                prop.walk(visitor);
+            });
+        });
+    },
+    _validate: function() {
+        this.properties.forEach(function(node) {
+            if (!(node instanceof AST_DestructuredKeyVal)) throw new Error("properties must be AST_DestructuredKeyVal[]");
+        });
+    },
+}, AST_Destructured);
+
 var AST_Object = DEFNODE("Object", "properties", {
     $documentation: "An object literal",
     $propdoc: {
index 9eb4a9e..d9446e8 100644 (file)
@@ -368,6 +368,15 @@ merge(Compressor.prototype, {
         } while (sym = sym.parent_scope);
     }
 
+    function can_drop_symbol(tw, ref, keep_lambda) {
+        var orig = ref.definition().orig;
+        if (ref.in_arg && (orig[0] instanceof AST_SymbolFunarg || orig[1] instanceof AST_SymbolFunarg)) return false;
+        return all(orig, function(sym) {
+            return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet
+                || keep_lambda && sym instanceof AST_SymbolLambda);
+        });
+    }
+
     (function(def) {
         def(AST_Node, noop);
 
@@ -585,6 +594,47 @@ merge(Compressor.prototype, {
             if (is_arguments(def) && node.property instanceof AST_Number) def.reassigned = true;
         }
 
+        function scan_declaration(tw, lhs, fixed, visit) {
+            var scanner = new TreeWalker(function(node) {
+                if (node instanceof AST_DestructuredArray) {
+                    var save = fixed;
+                    node.elements.forEach(function(node, index) {
+                        if (node instanceof AST_Hole) return;
+                        fixed = function() {
+                            return make_node(AST_Sub, node, {
+                                expression: save(),
+                                property: make_node(AST_Number, node, {
+                                    value: index
+                                })
+                            });
+                        };
+                        node.walk(scanner);
+                    });
+                    fixed = save;
+                    return true;
+                }
+                if (node instanceof AST_DestructuredObject) {
+                    var save = fixed;
+                    node.properties.forEach(function(node) {
+                        if (node.key instanceof AST_Node) node.key.walk(tw);
+                        fixed = function() {
+                            var key = node.key;
+                            return make_node(typeof key == "string" ? AST_Dot : AST_Sub, node, {
+                                expression: save(),
+                                property: key
+                            });
+                        };
+                        node.value.walk(scanner);
+                    });
+                    fixed = save;
+                    return true;
+                }
+                visit(node, fixed);
+                return true;
+            });
+            lhs.walk(scanner);
+        }
+
         def(AST_Accessor, function(tw, descend, compressor) {
             push(tw);
             reset_variables(tw, compressor, this);
@@ -595,52 +645,66 @@ merge(Compressor.prototype, {
         });
         def(AST_Assign, function(tw, descend, compressor) {
             var node = this;
-            var eq = node.operator == "=";
-            var sym = node.left;
-            if (eq && sym.equivalent_to(node.right) && !sym.has_side_effects(compressor)) {
+            var left = node.left;
+            if (node.operator == "=" && left.equivalent_to(node.right) && !left.has_side_effects(compressor)) {
                 node.right.walk(tw);
-                walk_prop(sym);
+                walk_prop(left);
                 node.__drop = true;
-                return true;
-            }
-            if (!(sym instanceof AST_SymbolRef)) {
-                mark_assignment_to_arguments(sym);
-                return;
-            }
-            var d = sym.definition();
-            d.assignments++;
-            var fixed = d.fixed;
-            var value = eq ? node.right : node;
-            if (is_modified(compressor, tw, node, value, 0)) {
-                d.fixed = false;
+            } else if (!(left instanceof AST_Destructured || left instanceof AST_SymbolRef)) {
+                mark_assignment_to_arguments(left);
                 return;
-            }
-            var safe = eq || safe_to_read(tw, d);
-            node.right.walk(tw);
-            if (safe && safe_to_assign(tw, d)) {
-                push_ref(d, sym);
-                mark(tw, d);
-                if (eq) {
-                    tw.loop_ids[d.id] = tw.in_loop;
-                    mark_escaped(tw, d, sym.scope, node, value, 0, 1);
-                    sym.fixed = d.fixed = function() {
-                        return node.right;
-                    };
-                } else {
+            } else if (node.operator == "=") {
+                node.right.walk(tw);
+                scan_declaration(tw, left, function() {
+                    return node.right;
+                }, function(sym, fixed) {
+                    if (!(sym instanceof AST_SymbolRef)) {
+                        mark_assignment_to_arguments(sym);
+                        sym.walk(tw);
+                        return;
+                    }
+                    var d = sym.definition();
+                    d.assignments++;
+                    if (!is_modified(compressor, tw, node, node.right, 0)
+                        && can_drop_symbol(tw, sym) && safe_to_assign(tw, d)) {
+                        push_ref(d, sym);
+                        mark(tw, d);
+                        tw.loop_ids[d.id] = tw.in_loop;
+                        mark_escaped(tw, d, sym.scope, node, node.right, 0, 1);
+                        sym.fixed = d.fixed = fixed;
+                        sym.fixed.assigns = [ node ];
+                    } else {
+                        sym.walk(tw);
+                        d.fixed = false;
+                    }
+                });
+            } else {
+                var d = left.definition();
+                d.assignments++;
+                var fixed = d.fixed;
+                if (is_modified(compressor, tw, node, node, 0)) {
+                    d.fixed = false;
+                    return;
+                }
+                var safe = safe_to_read(tw, d);
+                node.right.walk(tw);
+                if (safe && safe_to_assign(tw, d)) {
+                    push_ref(d, left);
+                    mark(tw, d);
                     if (d.single_use) d.single_use = false;
-                    sym.fixed = d.fixed = function() {
+                    left.fixed = d.fixed = function() {
                         return make_node(AST_Binary, node, {
                             operator: node.operator.slice(0, -1),
-                            left: make_ref(sym, fixed),
+                            left: make_ref(left, fixed),
                             right: node.right
                         });
                     };
+                    left.fixed.assigns = !fixed || !fixed.assigns ? [] : fixed.assigns.slice();
+                    left.fixed.assigns.push(node);
+                } else {
+                    left.walk(tw);
+                    d.fixed = false;
                 }
-                sym.fixed.assigns = eq || !fixed || !fixed.assigns ? [] : fixed.assigns.slice();
-                sym.fixed.assigns.push(node);
-            } else {
-                sym.walk(tw);
-                d.fixed = false;
             }
             return true;
 
@@ -766,10 +830,12 @@ merge(Compressor.prototype, {
             push(tw);
             var init = this.init;
             init.walk(tw);
-            if (init instanceof AST_SymbolRef) {
+            if (init instanceof AST_Definitions) {
+                init.definitions[0].name.match_symbol(function(node) {
+                    if (node instanceof AST_SymbolDeclaration) node.definition().fixed = false;
+                });
+            } else if (init instanceof AST_SymbolRef) {
                 init.definition().fixed = false;
-            } else if (init instanceof AST_Var) {
-                init.definitions[0].name.definition().fixed = false;
             }
             this.body.walk(tw);
             pop(tw);
@@ -793,22 +859,26 @@ merge(Compressor.prototype, {
                 // Virtually turn IIFE parameters into variable definitions:
                 //   (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
                 // So existing transformation rules can work on them.
+                var safe = !fn.uses_arguments || tw.has_directive("use strict");
                 fn.argnames.forEach(function(arg, i) {
-                    var d = arg.definition();
-                    if (d.fixed === undefined && (!fn.uses_arguments || tw.has_directive("use strict"))) {
-                        mark(tw, d);
-                        tw.loop_ids[d.id] = tw.in_loop;
-                        var value = iife.args[i];
-                        d.fixed = function() {
-                            var j = fn.argnames.indexOf(arg);
-                            return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife);
-                        };
-                        d.fixed.assigns = [ arg ];
-                    } else {
-                        d.fixed = false;
-                    }
+                    var value = iife.args[i];
+                    scan_declaration(tw, arg, function() {
+                        var j = fn.argnames.indexOf(arg);
+                        return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife);
+                    }, function(node, fixed) {
+                        var d = node.definition();
+                        if (safe && d.fixed === undefined) {
+                            mark(tw, d);
+                            tw.loop_ids[d.id] = tw.in_loop;
+                            var value = iife.args[i];
+                            d.fixed = fixed;
+                            d.fixed.assigns = [ arg ];
+                        } else {
+                            d.fixed = false;
+                        }
+                    });
                 });
-                descend();
+                walk_body(fn, tw);
                 var safe_ids = tw.safe_ids;
                 pop(tw);
                 walk_defuns(tw, fn);
@@ -881,6 +951,7 @@ merge(Compressor.prototype, {
             } else if (d.fixed === undefined || !safe_to_read(tw, d)) {
                 d.fixed = false;
             } else if (d.fixed) {
+                if (this.in_arg && d.orig[0] instanceof AST_SymbolLambda) this.fixed = d.scope;
                 var value = this.fixed_value();
                 var recursive = recursive_ref(tw, d);
                 if (recursive) {
@@ -908,7 +979,7 @@ merge(Compressor.prototype, {
                 }
                 mark_escaped(tw, d, this.scope, this, value, 0, 1);
             }
-            this.fixed = d.fixed;
+            if (!this.fixed) this.fixed = d.fixed;
             var parent;
             if (d.fixed instanceof AST_Defun
                 && !((parent = tw.parent()) instanceof AST_Call && parent.expression === this)) {
@@ -988,22 +1059,24 @@ merge(Compressor.prototype, {
             }
             return true;
         });
-        def(AST_VarDef, function(tw, descend) {
+        def(AST_VarDef, function(tw) {
             var node = this;
             if (!node.value) return;
-            descend();
-            var d = node.name.definition();
-            if (safe_to_assign(tw, d, true)) {
-                mark(tw, d);
-                tw.loop_ids[d.id] = tw.in_loop;
-                d.fixed = function() {
-                    return node.value;
-                };
-                d.fixed.assigns = [ node ];
-                if (node.name instanceof AST_SymbolConst && d.redefined()) d.single_use = false;
-            } else {
-                d.fixed = false;
-            }
+            node.value.walk(tw);
+            scan_declaration(tw, node.name, function() {
+                return node.value;
+            }, function(name, fixed) {
+                var d = name.definition();
+                if (safe_to_assign(tw, d, true)) {
+                    mark(tw, d);
+                    tw.loop_ids[d.id] = tw.in_loop;
+                    d.fixed = fixed;
+                    d.fixed.assigns = [ node ];
+                    if (name instanceof AST_SymbolConst && d.redefined()) d.single_use = false;
+                } else {
+                    d.fixed = false;
+                }
+            });
             return true;
         });
         def(AST_While, function(tw, descend) {
@@ -1059,6 +1132,64 @@ merge(Compressor.prototype, {
         return sym instanceof AST_SymbolLambda && def.scope.name === sym;
     });
 
+    AST_Node.DEFMETHOD("convert_symbol", noop);
+    AST_Destructured.DEFMETHOD("convert_symbol", function(type, process) {
+        return this.transform(new TreeTransformer(function(node, descend) {
+            if (node instanceof AST_Destructured) {
+                node = node.clone();
+                descend(node, this);
+                return node;
+            }
+            if (node instanceof AST_DestructuredKeyVal) {
+                node = node.clone();
+                node.value = node.value.transform(this);
+                return node;
+            }
+            return node.convert_symbol(type, process);
+        }));
+    });
+    function convert_symbol(type, process) {
+        var node = make_node(type, this, this);
+        process(node, this);
+        return node;
+    }
+    AST_SymbolDeclaration.DEFMETHOD("convert_symbol", convert_symbol);
+    AST_SymbolRef.DEFMETHOD("convert_symbol", convert_symbol);
+
+    AST_Destructured.DEFMETHOD("mark_symbol", function(process, tw) {
+        var marker = new TreeWalker(function(node) {
+            if (node instanceof AST_DestructuredKeyVal) {
+                if (node.key instanceof AST_Node) node.key.walk(tw);
+                node.value.walk(marker);
+                return true;
+            }
+            return process(node);
+        });
+        this.walk(marker);
+    });
+    function mark_symbol(process) {
+        return process(this);
+    }
+    AST_SymbolDeclaration.DEFMETHOD("mark_symbol", mark_symbol);
+    AST_SymbolRef.DEFMETHOD("mark_symbol", mark_symbol);
+
+    AST_Node.DEFMETHOD("match_symbol", function(predicate) {
+        return predicate(this);
+    });
+    AST_Destructured.DEFMETHOD("match_symbol", function(predicate) {
+        var found = false;
+        var tw = new TreeWalker(function(node) {
+            if (found) return true;
+            if (node instanceof AST_DestructuredKeyVal) {
+                node.value.walk(tw);
+                return true;
+            }
+            if (predicate(node)) return found = true;
+        });
+        this.walk(tw);
+        return found;
+    });
+
     function find_scope(compressor) {
         var level = 0, node;
         while (node = compressor.parent(level++)) {
@@ -1557,6 +1688,8 @@ 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 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;
                 if (node instanceof AST_SymbolRef) {
@@ -1589,6 +1722,9 @@ merge(Compressor.prototype, {
                     }
                     if (!(fn instanceof AST_Lambda)) return true;
                     if (def && recursive_ref(compressor, def)) return true;
+                    if (!all(fn.argnames, function(argname) {
+                        return !(argname instanceof AST_Destructured);
+                    })) return true;
                     if (fn.collapse_scanning) return false;
                     fn.collapse_scanning = true;
                     var replace = can_replace;
@@ -1636,13 +1772,20 @@ merge(Compressor.prototype, {
                 }
                 if (node instanceof AST_This) return symbol_in_lvalues(node, parent);
                 if (node instanceof AST_VarDef) {
-                    if (!node.value) return false;
-                    return lvalues.has(node.name.name) || side_effects && may_modify(node.name);
+                    if ((in_try || !lhs_local) && node.name instanceof AST_Destructured) return true;
+                    return node.value && node.name.match_symbol(function(node) {
+                        return node instanceof AST_SymbolDeclaration
+                            && (lvalues.has(node.name) || side_effects && may_modify(node));
+                    });
                 }
                 var sym = is_lhs(node.left, node);
+                if (!sym) return false;
                 if (sym instanceof AST_PropAccess) return true;
-                if (!(sym instanceof AST_SymbolRef)) return false;
-                return lvalues.has(sym.name) || read_toplevel && compressor.exposed(sym.definition());
+                if ((in_try || !lhs_local) && sym instanceof AST_Destructured) return true;
+                return sym.match_symbol(function(node) {
+                    return node instanceof AST_SymbolRef
+                        && (lvalues.has(node.name) || read_toplevel && compressor.exposed(node.definition()));
+                });
             }
 
             function extract_args() {
@@ -1665,6 +1808,7 @@ merge(Compressor.prototype, {
                             name: sym,
                             value: arg
                         }));
+                        if (sym instanceof AST_Destructured) continue;
                         if (sym.name in names) continue;
                         names[sym.name] = true;
                         if (!arg) {
@@ -1700,7 +1844,7 @@ merge(Compressor.prototype, {
                 if (expr instanceof AST_Array) {
                     expr.elements.forEach(extract_candidates);
                 } else if (expr instanceof AST_Assign) {
-                    candidates.push(hit_stack.slice());
+                    if (!(expr.left instanceof AST_Destructured)) candidates.push(hit_stack.slice());
                     extract_candidates(expr.left);
                     extract_candidates(expr.right);
                     if (expr.left instanceof AST_SymbolRef) {
@@ -2057,7 +2201,7 @@ merge(Compressor.prototype, {
                     }
                     if (value) lvalues.add(node.name, is_modified(compressor, tw, node, value, 0));
                     if (find_arguments && node instanceof AST_Sub) {
-                        scope.argnames.forEach(function(argname) {
+                        scope.each_argname(function(argname) {
                             if (!compressor.option("reduce_vars") || argname.definition().assignments) {
                                 lvalues.add(argname.name, true);
                             }
@@ -2642,6 +2786,7 @@ merge(Compressor.prototype, {
 
         function merge_conditional_assignments(var_def, exprs, keep) {
             if (!compressor.option("conditionals")) return;
+            if (var_def.name instanceof AST_Destructured) return;
             var trimmed = false;
             var def = var_def.name.definition();
             while (exprs.length > keep) {
@@ -2759,11 +2904,15 @@ merge(Compressor.prototype, {
                     }
                 } else if (stat instanceof AST_ForIn) {
                     if (defs && defs.TYPE == stat.init.TYPE) {
-                        defs.definitions = defs.definitions.concat(stat.init.definitions);
-                        var name = stat.init.definitions[0].name;
-                        var ref = make_node(AST_SymbolRef, name, name);
-                        name.definition().references.push(ref);
-                        stat.init = ref;
+                        var defns = defs.definitions.slice();
+                        stat.init = stat.init.definitions[0].name.convert_symbol(AST_SymbolRef, function(ref, name) {
+                            defns.push(make_node(AST_VarDef, name, {
+                                name: name,
+                                value: null
+                            }));
+                            name.definition().references.push(ref);
+                        });
+                        defs.definitions = defns;
                         CHANGED = true;
                     }
                     stat.object = join_assigns_expr(stat.object);
@@ -2821,10 +2970,14 @@ merge(Compressor.prototype, {
         var block;
         stat.walk(new TreeWalker(function(node, descend) {
             if (node instanceof AST_Definitions) {
-                if (node.remove_initializers(compressor)) {
+                var defns = [];
+                if (node.remove_initializers(compressor, defns)) {
                     AST_Node.warn("Dropping initialization in unreachable code [{file}:{line},{col}]", node.start);
                 }
-                push(node);
+                if (defns.length > 0) {
+                    node.definitions = defns;
+                    push(node);
+                }
                 return true;
             }
             if (node instanceof AST_Defun) {
@@ -3261,8 +3414,10 @@ merge(Compressor.prototype, {
     var unary_side_effects = makePredicate("delete ++ --");
 
     function is_lhs(node, parent) {
-        if (parent instanceof AST_Unary && unary_side_effects[parent.operator]) return parent.expression;
-        if (parent instanceof AST_Assign && parent.left === node) return node;
+        if (parent instanceof AST_Assign) return parent.left === node && node;
+        if (parent instanceof AST_Destructured) return node;
+        if (parent instanceof AST_DestructuredKeyVal) return node;
+        if (parent instanceof AST_Unary) return unary_side_effects[parent.operator] && parent.expression;
     }
 
     (function(def) {
@@ -3831,6 +3986,9 @@ merge(Compressor.prototype, {
                 if (fn.evaluating) return this;
                 if (fn.name && fn.name.definition().recursive_refs > 0) return this;
                 if (this.is_expr_pure(compressor)) return this;
+                if (!all(fn.argnames, function(sym) {
+                    return !(sym instanceof AST_Destructured);
+                })) return this;
                 var args = eval_args(this.args);
                 if (!args && !ignore_side_effects) return this;
                 var stat = fn.first_statement();
@@ -4094,6 +4252,16 @@ merge(Compressor.prototype, {
         def(AST_Definitions, function(compressor) {
             return any(this.definitions, compressor);
         });
+        def(AST_DestructuredArray, function(compressor) {
+            return any(this.elements, compressor);
+        });
+        def(AST_DestructuredKeyVal, function(compressor) {
+            return this.key instanceof AST_Node && this.key.has_side_effects(compressor)
+                || this.value.has_side_effects(compressor);
+        });
+        def(AST_DestructuredObject, function(compressor) {
+            return any(this.properties, compressor);
+        });
         def(AST_Dot, function(compressor) {
             return this.expression.may_throw_on_access(compressor)
                 || this.expression.has_side_effects(compressor);
@@ -4132,9 +4300,7 @@ merge(Compressor.prototype, {
         });
         def(AST_SymbolDeclaration, return_false);
         def(AST_SymbolRef, function(compressor) {
-            return !(this.is_declared(compressor) && all(this.definition().orig, function(sym) {
-                return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet);
-            }));
+            return !this.is_declared(compressor) || !can_drop_symbol(compressor, this);
         });
         def(AST_This, return_false);
         def(AST_Try, function(compressor) {
@@ -4461,12 +4627,26 @@ merge(Compressor.prototype, {
         var prev = Object.create(null);
         var tw = new TreeWalker(function(node, descend) {
             if (node instanceof AST_Assign) {
-                var sym = node.left;
-                if (!(sym instanceof AST_SymbolRef)) return;
-                if (node.operator != "=") mark(sym, true, false);
-                node.right.walk(tw);
-                mark(sym, false, true);
-                return true;
+                var lhs = node.left;
+                if (lhs instanceof AST_Destructured) {
+                    node.right.walk(tw);
+                    lhs.mark_symbol(function(node) {
+                        if (node instanceof AST_SymbolRef) {
+                            mark(node, false, true);
+                        } else {
+                            node.walk(tw);
+                        }
+                        return true;
+                    }, tw);
+                    return true;
+                }
+                if (lhs instanceof AST_SymbolRef) {
+                    if (node.operator != "=") mark(lhs, true, false);
+                    node.right.walk(tw);
+                    mark(lhs, false, true);
+                    return true;
+                }
+                return;
             }
             if (node instanceof AST_Binary) {
                 if (!lazy_op[node.operator]) return;
@@ -4491,13 +4671,6 @@ merge(Compressor.prototype, {
                 pop();
                 return true;
             }
-            if (node instanceof AST_Const) {
-                node.definitions.forEach(function(defn) {
-                    references[defn.name.definition().id] = false;
-                    defn.value.walk(tw);
-                });
-                return true;
-            }
             if (node instanceof AST_Continue) {
                 var target = tw.loopcontrol_target(node);
                 if (target instanceof AST_Do) insert(target);
@@ -4556,21 +4729,16 @@ merge(Compressor.prototype, {
                 pop();
                 return true;
             }
-            if (node instanceof AST_Let) {
-                node.definitions.forEach(function(defn) {
-                    references[defn.name.definition().id] = false;
-                    if (defn.value) defn.value.walk(tw);
-                });
-                return true;
-            }
             if (node instanceof AST_Scope) {
                 push();
                 segment.block = node;
                 if (node === self) root = segment;
                 if (node instanceof AST_Lambda) {
                     if (node.name) references[node.name.definition().id] = false;
-                    if (node.uses_arguments && !tw.has_directive("use strict")) node.argnames.forEach(function(node) {
+                    node.each_argname(node.uses_arguments && !tw.has_directive("use strict") ? function(node) {
                         references[node.definition().id] = false;
+                    } : function(node) {
+                        mark(node, false, true);
                     });
                 }
                 descend();
@@ -4596,10 +4764,6 @@ merge(Compressor.prototype, {
                 });
                 return true;
             }
-            if (node instanceof AST_SymbolFunarg) {
-                if (!node.__unused) mark(node, false, true);
-                return true;
-            }
             if (node instanceof AST_SymbolRef) {
                 mark(node, true, false);
                 return true;
@@ -4631,17 +4795,27 @@ merge(Compressor.prototype, {
                 return true;
             }
             if (node instanceof AST_VarDef) {
-                if (node.value) {
-                    node.value.walk(tw);
-                    mark(node.name, false, true);
-                } else {
-                    var id = node.name.definition().id;
-                    if (!(id in references)) {
-                        declarations.add(id, node.name);
+                if (node.value) node.value.walk(tw);
+                node.name.mark_symbol(node.value ? function(node) {
+                    if (!(node instanceof AST_SymbolDeclaration)) return;
+                    if (node instanceof AST_SymbolVar) {
+                        mark(node, false, true);
+                    } else {
+                        references[node.definition().id] = false;
+                    }
+                    return true;
+                } : function(node) {
+                    if (!(node instanceof AST_SymbolDeclaration)) return;
+                    var id = node.definition().id;
+                    if (!(node instanceof AST_SymbolVar)) {
+                        references[id] = false;
+                    } else if (!(id in references)) {
+                        declarations.add(id, node);
                     } else if (references[id]) {
-                        references[id].push(node.name);
+                        references[id].push(node);
                     }
-                }
+                    return true;
+                }, tw);
                 return true;
             }
             if (node instanceof AST_While) {
@@ -4711,7 +4885,7 @@ merge(Compressor.prototype, {
         }
 
         function mark(sym, read, write) {
-            var def = sym.definition();
+            var def = sym.definition(), ldef;
             if (def.id in references) {
                 var refs = references[def.id];
                 if (!refs) return;
@@ -4723,7 +4897,10 @@ merge(Compressor.prototype, {
                 } else if (!read) {
                     return;
                 }
-            } else if (self.variables.get(def.name) !== def || compressor.exposed(def) || sym.name == "arguments") {
+            } else if ((ldef = self.variables.get(def.name)) !== def) {
+                if (ldef && root === segment) references[ldef.id] = false;
+                return references[def.id] = false;
+            } else if (compressor.exposed(def) || sym.name == "arguments") {
                 return references[def.id] = false;
             } else {
                 var refs = declarations.get(def.id) || [];
@@ -4781,8 +4958,8 @@ merge(Compressor.prototype, {
         var self = this;
         var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
         var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
-        var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node, props) {
-            var sym;
+        var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(tw, node, props) {
+            var sym, nested = false;
             if (node instanceof AST_Assign) {
                 if (node.write_only || node.operator == "=") sym = node.left;
             } else if (node instanceof AST_Unary) {
@@ -4792,13 +4969,12 @@ merge(Compressor.prototype, {
                 while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) {
                     if (sym instanceof AST_Sub) props.unshift(sym.property);
                     sym = sym.expression;
+                    nested = true;
                 }
             }
             if (!(sym instanceof AST_SymbolRef)) return;
             if (compressor.exposed(sym.definition())) return;
-            if (!all(sym.definition().orig, function(sym) {
-                return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLambda || sym instanceof AST_SymbolLet);
-            })) return;
+            if (!can_drop_symbol(tw, sym, nested)) return;
             return sym;
         };
         var assign_in_use = Object.create(null);
@@ -4823,7 +4999,7 @@ merge(Compressor.prototype, {
         var scope = this;
         var tw = new TreeWalker(function(node, descend) {
             if (node instanceof AST_Lambda && node.uses_arguments && !tw.has_directive("use strict")) {
-                node.argnames.forEach(function(argname) {
+                node.each_argname(function(argname) {
                     var def = argname.definition();
                     if (!(def.id in in_use_ids)) {
                         in_use_ids[def.id] = true;
@@ -4844,24 +5020,28 @@ merge(Compressor.prototype, {
                 }
                 if (node instanceof AST_Definitions) {
                     node.definitions.forEach(function(defn) {
-                        var def = defn.name.definition();
-                        var_defs_by_id.add(def.id, defn);
-                        if (node instanceof AST_Var && def.orig[0] instanceof AST_SymbolCatch) {
-                            var redef = def.redefined();
-                            if (redef) var_defs_by_id.add(redef.id, defn);
-                        }
-                        if ((!drop_vars || (node instanceof AST_Const ? def.redefined() : def.const_redefs))
-                            && !(def.id in in_use_ids)) {
-                            in_use_ids[def.id] = true;
-                            in_use.push(def);
-                        }
-                        if (!defn.value) return;
-                        if (defn.value.has_side_effects(compressor)) {
-                            defn.value.walk(tw);
-                        } else {
-                            initializations.add(def.id, defn.value);
-                        }
-                        assignments.add(def.id, defn);
+                        var side_effects = defn.value
+                            && (defn.name instanceof AST_Destructured || defn.value.has_side_effects(compressor));
+                        defn.name.mark_symbol(function(name) {
+                            if (!(name instanceof AST_SymbolDeclaration)) return;
+                            var def = name.definition();
+                            var_defs_by_id.add(def.id, defn);
+                            if (node instanceof AST_Var && def.orig[0] instanceof AST_SymbolCatch) {
+                                var redef = def.redefined();
+                                if (redef) var_defs_by_id.add(redef.id, defn);
+                            }
+                            if ((!drop_vars || (node instanceof AST_Const ? def.redefined() : def.const_redefs))
+                                && !(def.id in in_use_ids)) {
+                                in_use_ids[def.id] = true;
+                                in_use.push(def);
+                            }
+                            if (defn.value) {
+                                if (!side_effects) initializations.add(def.id, defn.value);
+                                assignments.add(def.id, defn);
+                            }
+                            return true;
+                        }, tw);
+                        if (side_effects) defn.value.walk(tw);
                     });
                     return true;
                 }
@@ -4916,10 +5096,14 @@ merge(Compressor.prototype, {
         var drop_fn_name = compressor.option("keep_fnames") ? return_false : compressor.option("ie8") ? function(def) {
             return !compressor.exposed(def) && def.references.length == def.replaced;
         } : function(def) {
-            // any declarations with same name will overshadow
-            // name of this anonymous function and can therefore
-            // never be used anywhere
-            return !(def.id in in_use_ids) || def.orig.length > 1;
+            if (!(def.id in in_use_ids)) return true;
+            if (def.orig.length < 2) return false;
+            // function argument will always overshadow its name
+            if (def.orig[1] instanceof AST_SymbolFunarg) return true;
+            // retain if referenced within destructured object of argument
+            return all(def.references, function(ref) {
+                return !ref.in_arg;
+            });
         };
         // pass 3: we should drop declarations not in_use
         var unused_fn_names = [];
@@ -4928,7 +5112,7 @@ merge(Compressor.prototype, {
         var tt = new TreeTransformer(function(node, descend, in_list) {
             var parent = tt.parent();
             if (drop_vars) {
-                var props = [], sym = assign_as_unused(node, props);
+                var props = [], sym = assign_as_unused(tt, node, props);
                 if (sym) {
                     var def = sym.definition();
                     var in_use = def.id in in_use_ids;
@@ -4990,6 +5174,38 @@ merge(Compressor.prototype, {
                     var trim = compressor.drop_fargs(node, parent);
                     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;
+                                }
+                            }));
+                            trim = false;
+                            continue;
+                        }
                         var def = sym.definition();
                         if (def.id in in_use_ids) {
                             trim = false;
@@ -5015,6 +5231,84 @@ merge(Compressor.prototype, {
                 var duplicated = 0;
                 node.definitions.forEach(function(def) {
                     if (def.value) def.value = def.value.transform(tt);
+                    if (def.name instanceof AST_Destructured) {
+                        var value = def.value;
+                        var trimmer = new TreeTransformer(function(node) {
+                            if (node instanceof AST_DestructuredArray) {
+                                var save = value;
+                                if (value instanceof AST_SymbolRef) value = value.fixed_value();
+                                var values = value instanceof AST_Array && value.elements;
+                                var elements = [];
+                                node.elements.forEach(function(element, index) {
+                                    if (element instanceof AST_Hole) return;
+                                    value = values && values[index];
+                                    element = element.transform(trimmer);
+                                    if (element) elements[index] = element;
+                                });
+                                value = save;
+                                if (values && elements.length == 0) return null;
+                                for (var i = elements.length; --i >= 0;) {
+                                    if (!elements[i]) elements[i] = make_node(AST_Hole, node.elements[i] || node);
+                                }
+                                node.elements = elements;
+                                return node;
+                            }
+                            if (node instanceof AST_DestructuredObject) {
+                                var save = value;
+                                if (value instanceof AST_SymbolRef) value = value.fixed_value();
+                                var values;
+                                if (value instanceof AST_Object) {
+                                    values = Object.create(null);
+                                    for (var i = 0; i < value.properties.length; i++) {
+                                        var prop = value.properties[i];
+                                        if (typeof prop.key != "string") {
+                                            values = null;
+                                            break;
+                                        }
+                                        values[prop.key] = prop.value;
+                                    }
+                                }
+                                var properties = [];
+                                node.properties.forEach(function(prop) {
+                                    var retain;
+                                    if (prop.key instanceof AST_Node) {
+                                        prop.key = prop.key.transform(tt);
+                                        value = null;
+                                        retain = prop.key.has_side_effects(compressor);
+                                    } else {
+                                        value = values && values[prop.key];
+                                        retain = false;
+                                    }
+                                    if (retain && prop.value instanceof AST_SymbolDeclaration) {
+                                        properties.push(prop);
+                                    } else {
+                                        var newValue = prop.value.transform(trimmer);
+                                        if (newValue) {
+                                            prop.value = newValue;
+                                            properties.push(prop);
+                                        }
+                                    }
+                                });
+                                value = save;
+                                if (properties.length == 0 && value && !value.may_throw_on_access(compressor)) {
+                                    return null;
+                                }
+                                node.properties = properties;
+                                return node;
+                            }
+                            if (node instanceof AST_SymbolDeclaration) {
+                                return !drop_vars || node.definition().id in in_use_ids || is_catch(node) ? node : null;
+                            }
+                        });
+                        var name = def.name.transform(trimmer);
+                        if (name) {
+                            flush();
+                        } else {
+                            value = value.drop_side_effect_free(compressor);
+                            if (value) side_effects.push(value);
+                        }
+                        return;
+                    }
                     var sym = def.name.definition();
                     if (!drop_vars || sym.id in in_use_ids) {
                         if (def.value && indexOf_assign(sym, def) < 0) {
@@ -5070,26 +5364,9 @@ merge(Compressor.prototype, {
                                 remove(var_defs, def);
                                 duplicated++;
                             }
-                            if (side_effects.length > 0) {
-                                if (tail.length == 0) {
-                                    body.push(make_node(AST_SimpleStatement, node, {
-                                        body: make_sequence(node, side_effects)
-                                    }));
-                                } else if (def.value) {
-                                    side_effects.push(def.value);
-                                    def.value = make_sequence(def.value, side_effects);
-                                } else {
-                                    def.value = make_node(AST_UnaryPrefix, def, {
-                                        operator: "void",
-                                        expression: make_sequence(def, side_effects)
-                                    });
-                                }
-                                side_effects = [];
-                            }
-                            tail.push(def);
+                            flush();
                         }
-                    } else if (sym.orig[0] instanceof AST_SymbolCatch
-                        && sym.scope.resolve() === def.name.scope.resolve()) {
+                    } else if (is_catch(def.name)) {
                         var value = def.value && def.value.drop_side_effect_free(compressor);
                         if (value) side_effects.push(value);
                         var var_defs = var_defs_by_id.get(sym.id);
@@ -5112,6 +5389,11 @@ merge(Compressor.prototype, {
                         sym.eliminated++;
                     }
 
+                    function is_catch(node) {
+                        var sym = node.definition();
+                        return sym.orig[0] instanceof AST_SymbolCatch && sym.scope.resolve() === node.scope.resolve();
+                    }
+
                     function can_rename(fn, name) {
                         var def = fn.variables.get(name);
                         return !def || fn.name && def === fn.name.definition();
@@ -5123,6 +5405,26 @@ merge(Compressor.prototype, {
                             || parent instanceof AST_For && parent.init === node
                             || parent instanceof AST_If;
                     }
+
+                    function flush() {
+                        if (side_effects.length > 0) {
+                            if (tail.length == 0) {
+                                body.push(make_node(AST_SimpleStatement, node, {
+                                    body: make_sequence(node, side_effects)
+                                }));
+                            } else if (def.value) {
+                                side_effects.push(def.value);
+                                def.value = make_sequence(def.value, side_effects);
+                            } else {
+                                def.value = make_node(AST_UnaryPrefix, def, {
+                                    operator: "void",
+                                    expression: make_sequence(def, side_effects)
+                                });
+                            }
+                            side_effects = [];
+                        }
+                        tail.push(def);
+                    }
                 });
                 switch (head.length) {
                   case 0:
@@ -5227,6 +5529,7 @@ merge(Compressor.prototype, {
                 } else while (sym instanceof AST_PropAccess) {
                     sym = sym.expression.tail_node();
                 }
+                if (sym instanceof AST_Destructured) return;
                 var def = sym.definition();
                 if (!def) return;
                 if (def.id in in_use_ids) return;
@@ -5342,7 +5645,7 @@ merge(Compressor.prototype, {
                 var def = node.expression.definition();
                 if (def.scope === self) assignments.add(def.id, node);
             }
-            var node_def, props = [], sym = assign_as_unused(node, props);
+            var node_def, props = [], sym = assign_as_unused(tw, node, props);
             if (sym && self.variables.get(sym.name) === (node_def = sym.definition())) {
                 props.forEach(function(prop) {
                     prop.walk(tw);
@@ -5376,6 +5679,7 @@ merge(Compressor.prototype, {
                 }
                 if (!drop_vars || !compressor.option("loops")) return;
                 if (!is_empty(node.body)) return;
+                if (node.init instanceof AST_Destructured) return;
                 if (node.init.has_side_effects(compressor)) return;
                 node.object.walk(tw);
                 return true;
@@ -5742,6 +6046,7 @@ merge(Compressor.prototype, {
         }));
 
         function can_hoist(sym, right, count) {
+            if (!(sym instanceof AST_Symbol)) return;
             var def = sym.definition();
             if (def.assignments != count) return;
             if (def.direct_access) return;
@@ -5764,7 +6069,7 @@ merge(Compressor.prototype, {
         }
     });
 
-    function safe_to_drop(fn, compressor) {
+    function fn_name_unused(fn, compressor) {
         if (!fn.name || !compressor.option("ie8")) return true;
         var def = fn.name.definition();
         if (compressor.exposed(def)) return false;
@@ -5940,7 +6245,7 @@ merge(Compressor.prototype, {
             return expr.drop_side_effect_free(compressor, first_in_statement);
         });
         def(AST_Function, function(compressor) {
-            return safe_to_drop(this, compressor) ? null : this;
+            return fn_name_unused(this, compressor) ? null : this;
         });
         def(AST_Object, function(compressor, first_in_statement) {
             var exprs = [];
@@ -5977,13 +6282,8 @@ merge(Compressor.prototype, {
             if (!property) return expression;
             return make_sequence(this, [ expression, property ]);
         });
-        function drop_symbol(ref) {
-            return all(ref.definition().orig, function(sym) {
-                return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet);
-            });
-        }
         def(AST_SymbolRef, function(compressor) {
-            return this.is_declared(compressor) && drop_symbol(this) ? null : this;
+            return this.is_declared(compressor) && can_drop_symbol(compressor, this) ? null : this;
         });
         def(AST_This, return_null);
         def(AST_Unary, function(compressor, first_in_statement) {
@@ -5992,7 +6292,9 @@ merge(Compressor.prototype, {
                 this.write_only = !exp.has_side_effects(compressor);
                 return this;
             }
-            if (this.operator == "typeof" && exp instanceof AST_SymbolRef && drop_symbol(exp)) return null;
+            if (this.operator == "typeof" && exp instanceof AST_SymbolRef && can_drop_symbol(compressor, exp)) {
+                return null;
+            }
             var node = exp.drop_side_effect_free(compressor, first_in_statement);
             if (first_in_statement && node && is_iife_call(node)) {
                 if (node === exp && this.operator == "!") return this;
@@ -6477,6 +6779,7 @@ merge(Compressor.prototype, {
                         exprs.push(line.body);
                     } else if (line instanceof AST_Var) {
                         if (!compressor.option("sequences") && exprs.length > 0) return;
+                        line.remove_initializers(compressor, var_defs);
                         line.definitions.forEach(process_var_def);
                     } else {
                         return;
@@ -6492,23 +6795,20 @@ merge(Compressor.prototype, {
             if (stat instanceof AST_SimpleStatement) return [ stat.body ];
             if (stat instanceof AST_Var) {
                 var exprs = [];
+                stat.remove_initializers(compressor, var_defs);
                 stat.definitions.forEach(process_var_def);
                 return exprs;
             }
 
             function process_var_def(var_def) {
-                var_defs.push(make_node(AST_VarDef, var_def, {
-                    name: var_def.name,
-                    value: null
-                }));
                 if (!var_def.value) return;
-                var ref = make_node(AST_SymbolRef, var_def.name, var_def.name);
                 exprs.push(make_node(AST_Assign, var_def, {
                     operator: "=",
-                    left: ref,
+                    left: var_def.name.convert_symbol(AST_SymbolRef, function(ref) {
+                        refs.push(ref);
+                    }),
                     right: var_def.value
                 }));
-                refs.push(ref);
             }
         }
     });
@@ -6710,25 +7010,27 @@ merge(Compressor.prototype, {
         return self;
     });
 
-    AST_Const.DEFMETHOD("remove_initializers", function(compressor) {
-        this.definitions.forEach(function(def) {
-            def.value = make_node(AST_Undefined, def).optimize(compressor);
-        });
-        return true;
-    });
-
-    function remove_initializers() {
-        var CHANGED = false;
-        this.definitions.forEach(function(def) {
-            if (!def.value) return;
-            def.value = null;
-            CHANGED = true;
-        });
-        return CHANGED;
+    function remove_initializers(make_value) {
+        return function(compressor, defns) {
+            var dropped = false;
+            this.definitions.forEach(function(defn) {
+                if (defn.value) dropped = true;
+                defn.name.match_symbol(function(node) {
+                    if (node instanceof AST_SymbolDeclaration) defns.push(make_node(AST_VarDef, node, {
+                        name: node,
+                        value: make_value(compressor, node)
+                    }));
+                });
+            });
+            return dropped;
+        };
     }
 
-    AST_Let.DEFMETHOD("remove_initializers", remove_initializers);
-    AST_Var.DEFMETHOD("remove_initializers", remove_initializers);
+    AST_Const.DEFMETHOD("remove_initializers", remove_initializers(function(compressor, node) {
+        return make_node(AST_Undefined, node).optimize(compressor);
+    }));
+    AST_Let.DEFMETHOD("remove_initializers", remove_initializers(return_null));
+    AST_Var.DEFMETHOD("remove_initializers", remove_initializers(return_null));
 
     AST_Definitions.DEFMETHOD("to_assignments", function() {
         var assignments = this.definitions.reduce(function(a, defn) {
@@ -6751,25 +7053,26 @@ merge(Compressor.prototype, {
 
     function varify(self, compressor) {
         return compressor.option("varify") && all(self.definitions, function(defn) {
-            var node = defn.name;
-            if (!node.fixed_value()) return false;
-            var def = node.definition();
-            if (compressor.exposed(def)) return false;
-            var scope = def.scope.resolve();
-            for (var s = def.scope; s !== scope;) {
-                s = s.parent_scope;
-                if (s.var_names()[node.name]) return false;
-            }
-            return true;
+            return !defn.name.match_symbol(function(node) {
+                if (!(node instanceof AST_SymbolDeclaration)) return;
+                if (!node.fixed_value()) return true;
+                var def = node.definition();
+                if (compressor.exposed(def)) return true;
+                var scope = def.scope.resolve();
+                for (var s = def.scope; s !== scope;) {
+                    s = s.parent_scope;
+                    if (s.var_names()[node.name]) return true;
+                }
+            });
         }) ? make_node(AST_Var, self, {
             definitions: self.definitions.map(function(defn) {
-                var name = make_node(AST_SymbolVar, defn.name, defn.name);
-                var def = name.definition();
-                def.orig[def.orig.indexOf(defn.name)] = name;
-                var scope = def.scope.resolve();
-                if (def.scope !== scope) scope.variables.set(def.name, def);
                 return make_node(AST_VarDef, defn, {
-                    name: name,
+                    name: defn.name.convert_symbol(AST_SymbolVar, function(name, node) {
+                        var def = name.definition();
+                        def.orig[def.orig.indexOf(node)] = name;
+                        var scope = def.scope.resolve();
+                        if (def.scope !== scope) scope.variables.set(def.name, def);
+                    }),
                     value: defn.value
                 });
             })
@@ -6798,14 +7101,23 @@ merge(Compressor.prototype, {
         if (fns_with_marked_args && fns_with_marked_args.indexOf(fn) < 0) return;
         var args = call.args;
         var pos = 0, last = 0;
-        var drop_fargs = fn === exp && !fn.name && compressor.drop_fargs(fn, call);
+        var drop_fargs = fn === exp && !fn.name && compressor.drop_fargs(fn, call) ? function(argname, arg) {
+            if (!argname) return true;
+            if (argname instanceof AST_DestructuredArray) {
+                return argname.elements.length == 0 && arg instanceof AST_Array;
+            }
+            if (argname instanceof AST_DestructuredObject) {
+                return argname.properties.length == 0 && arg && !arg.may_throw_on_access(compressor);
+            }
+            return argname.__unused;
+        } : return_false;
         var side_effects = [];
         for (var i = 0; i < args.length; i++) {
-            var trim = i >= fn.argnames.length;
-            if (trim || "__unused" in fn.argnames[i]) {
+            var argname = fn.argnames[i];
+            if (!argname || "__unused" in argname) {
                 var node = args[i].drop_side_effect_free(compressor);
-                if (drop_fargs && (trim || fn.argnames[i].__unused)) {
-                    if (!trim) fn.argnames.splice(i, 1);
+                if (drop_fargs(argname)) {
+                    if (argname) fn.argnames.splice(i, 1);
                     args.splice(i, 1);
                     if (node) side_effects.push(node);
                     i--;
@@ -6814,7 +7126,7 @@ merge(Compressor.prototype, {
                     side_effects.push(node);
                     args[pos++] = make_sequence(call, side_effects);
                     side_effects = [];
-                } else if (!trim) {
+                } else if (argname) {
                     if (side_effects.length) {
                         args[pos++] = make_sequence(call, side_effects);
                         side_effects = [];
@@ -6825,6 +7137,13 @@ merge(Compressor.prototype, {
                         continue;
                     }
                 }
+            } else if (argname && drop_fargs(argname, args[i])) {
+                var node = args[i].drop_side_effect_free(compressor);
+                fn.argnames.splice(i, 1);
+                args.splice(i, 1);
+                if (node) side_effects.push(node);
+                i--;
+                continue;
             } else {
                 side_effects.push(args[i]);
                 args[pos++] = make_sequence(call, side_effects);
@@ -6832,8 +7151,8 @@ merge(Compressor.prototype, {
             }
             last = pos;
         }
-        if (drop_fargs) for (; i < fn.argnames.length; i++) {
-            if (fn.argnames[i].__unused) fn.argnames.splice(i--, 1);
+        for (; i < fn.argnames.length; i++) {
+            if (drop_fargs(fn.argnames[i])) fn.argnames.splice(i--, 1);
         }
         args.length = last;
         if (!side_effects.length) return;
@@ -7129,7 +7448,12 @@ merge(Compressor.prototype, {
         var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp;
         var is_func = fn instanceof AST_Lambda;
         var stat = is_func && fn.first_statement();
-        var can_inline = compressor.option("inline") && !self.is_expr_pure(compressor);
+        var can_inline = is_func
+            && compressor.option("inline")
+            && !self.is_expr_pure(compressor)
+            && all(fn.argnames, function(argname) {
+                return !(argname instanceof AST_Destructured);
+            });
         if (can_inline && stat instanceof AST_Return) {
             var value = stat.value;
             if (exp === fn && (!value || value.is_constant_expression())) {
@@ -7189,7 +7513,10 @@ merge(Compressor.prototype, {
             }
             if (compressor.option("side_effects")
                 && all(fn.body, is_empty)
-                && (fn !== exp || safe_to_drop(fn, compressor))) {
+                && all(fn.argnames, function(argname) {
+                    return !(argname instanceof AST_Destructured);
+                })
+                && (fn !== exp || fn_name_unused(fn, compressor))) {
                 var args = self.args.concat(make_node(AST_Undefined, self));
                 return make_sequence(self, args).optimize(compressor);
             }
@@ -9409,6 +9736,17 @@ merge(Compressor.prototype, {
         return try_evaluate(compressor, self);
     });
 
+    OPT(AST_DestructuredKeyVal, function(self, compressor) {
+        if (compressor.option("objects")) {
+            var key = self.key;
+            if (key instanceof AST_Node) {
+                key = key.evaluate(compressor);
+                if (key !== self.key) self.key = "" + key;
+            }
+        }
+        return self;
+    });
+
     OPT(AST_Object, function(self, compressor) {
         if (!compressor.option("objects") || compressor.has_directive("use strict")) return self;
         for (var i = self.properties.length; --i >= 0;) {
index 884cafc..12358ff 100644 (file)
@@ -69,6 +69,7 @@ function OutputStream(options) {
         semicolons       : true,
         shebang          : true,
         source_map       : null,
+        v8               : false,
         webkit           : false,
         width            : 80,
         wrap_iife        : false,
@@ -499,11 +500,11 @@ function OutputStream(options) {
                 }
             }
             if (/comment[134]/.test(c.type)) {
-                print("//" + c.value.replace(/[@#]__PURE__/g, ' ') + "\n");
+                print("//" + c.value.replace(/[@#]__PURE__/g, " ") + "\n");
                 indent();
                 last_nlb = true;
             } else if (c.type == "comment2") {
-                print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/");
+                print("/*" + c.value.replace(/[@#]__PURE__/g, " ") + "*/");
                 last_nlb = false;
             }
         });
@@ -557,10 +558,10 @@ function OutputStream(options) {
                 space();
             }
             if (/comment[134]/.test(c.type)) {
-                print("//" + c.value.replace(/[@#]__PURE__/g, ' '));
+                print("//" + c.value.replace(/[@#]__PURE__/g, " "));
                 need_newline_indented = true;
             } else if (c.type == "comment2") {
-                print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/");
+                print("/*" + c.value.replace(/[@#]__PURE__/g, " ") + "*/");
                 need_space = true;
             }
         });
@@ -610,7 +611,7 @@ function OutputStream(options) {
         },
         parent          : function(n) {
             return stack[stack.length - 2 - (n || 0)];
-        }
+        },
     };
 }
 
@@ -652,13 +653,7 @@ function OutputStream(options) {
     /* -----[ PARENTHESES ]----- */
 
     function PARENS(nodetype, func) {
-        if (Array.isArray(nodetype)) {
-            nodetype.forEach(function(nodetype) {
-                PARENS(nodetype, func);
-            });
-        } else {
-            nodetype.DEFMETHOD("needs_parens", func);
-        }
+        nodetype.DEFMETHOD("needs_parens", func);
     }
 
     PARENS(AST_Node, return_false);
@@ -667,11 +662,11 @@ function OutputStream(options) {
     // the first token to appear in a statement.
     PARENS(AST_Function, function(output) {
         if (!output.has_parens() && first_in_statement(output)) return true;
-        if (output.option('webkit')) {
+        if (output.option("webkit")) {
             var p = output.parent();
             if (p instanceof AST_PropAccess && p.expression === this) return true;
         }
-        if (output.option('wrap_iife')) {
+        if (output.option("wrap_iife")) {
             var p = output.parent();
             if (p instanceof AST_Call && p.expression === this) return true;
         }
@@ -679,9 +674,10 @@ function OutputStream(options) {
 
     // same goes for an object literal, because otherwise it would be
     // interpreted as a block of code.
-    PARENS(AST_Object, function(output) {
+    function needs_parens_obj(output) {
         return !output.has_parens() && first_in_statement(output);
-    });
+    }
+    PARENS(AST_Object, needs_parens_obj);
 
     PARENS(AST_Unary, function(output) {
         var p = output.parent();
@@ -701,6 +697,7 @@ function OutputStream(options) {
             || p instanceof AST_Conditional
             // { [(1, 2)]: 3 }[2] ==> 3
             // { foo: (1, 2) }.foo ==> 2
+            || p instanceof AST_DestructuredKeyVal
             || p instanceof AST_ObjectProperty
             // (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
             || p instanceof AST_PropAccess && p.expression === this
@@ -747,7 +744,7 @@ function OutputStream(options) {
         var p = output.parent();
         if (p instanceof AST_New) return p.expression === this;
         // https://bugs.webkit.org/show_bug.cgi?id=123506
-        if (output.option('webkit')) {
+        if (output.option("webkit")) {
             var g = output.parent(1);
             return this.expression instanceof AST_Function
                 && p instanceof AST_PropAccess
@@ -776,18 +773,29 @@ function OutputStream(options) {
         }
     });
 
-    PARENS([ AST_Assign, AST_Conditional ], function(output) {
+    function needs_parens_assign_cond(self, output) {
         var p = output.parent();
         // 1 + (a = 2) + 3 → 6, side effect setting a = 2
         if (p instanceof AST_Binary) return !(p instanceof AST_Assign);
         // (a = func)() —or— new (a = Object)()
-        if (p instanceof AST_Call) return p.expression === this;
+        if (p instanceof AST_Call) return p.expression === self;
         // (a = foo) ? bar : baz
-        if (p instanceof AST_Conditional) return p.condition === this;
+        if (p instanceof AST_Conditional) return p.condition === self;
         // (a = foo)["prop"] —or— (a = foo).prop
-        if (p instanceof AST_PropAccess) return p.expression === this;
+        if (p instanceof AST_PropAccess) return p.expression === self;
         // !(a = false) → true
         if (p instanceof AST_Unary) return true;
+    }
+    PARENS(AST_Assign, function(output) {
+        if (needs_parens_assign_cond(this, output)) return true;
+        //  v8 parser bug   =>     workaround
+        // f([1], [a] = []) => f([1], ([a] = []))
+        if (output.option("v8")) return this.left instanceof AST_Destructured;
+        // ({ p: a } = o);
+        if (this.left instanceof AST_DestructuredObject) return needs_parens_obj(output);
+    });
+    PARENS(AST_Conditional, function(output) {
+        return needs_parens_assign_cond(this, output);
     });
 
     /* -----[ PRINTERS ]----- */
@@ -1274,6 +1282,38 @@ function OutputStream(options) {
             output.space();
         } : noop);
     });
+    DEFPRINT(AST_DestructuredArray, function(output) {
+        var a = this.elements, len = a.length;
+        output.with_square(len > 0 ? function() {
+            output.space();
+            a.forEach(function(exp, i) {
+                if (i) output.comma();
+                exp.print(output);
+                // If the final element is a hole, we need to make sure it
+                // doesn't look like a trailing comma, by inserting an actual
+                // trailing comma.
+                if (i === len - 1 && exp instanceof AST_Hole)
+                  output.comma();
+            });
+            output.space();
+        } : noop);
+    });
+    DEFPRINT(AST_DestructuredKeyVal, print_key_value);
+    DEFPRINT(AST_DestructuredObject, function(output) {
+        var props = this.properties;
+        if (props.length > 0) output.with_block(function() {
+            props.forEach(function(prop, i) {
+                if (i) {
+                    output.print(",");
+                    output.newline();
+                }
+                output.indent();
+                prop.print(output);
+            });
+            output.newline();
+        });
+        else print_braced_empty(this, output);
+    });
     DEFPRINT(AST_Object, function(output) {
         var props = this.properties;
         if (props.length > 0) output.with_block(function() {
@@ -1314,12 +1354,13 @@ function OutputStream(options) {
         }
     }
 
-    DEFPRINT(AST_ObjectKeyVal, function(output) {
+    function print_key_value(output) {
         var self = this;
         print_property_key(self, output);
         output.colon();
         self.value.print(output);
-    });
+    }
+    DEFPRINT(AST_ObjectKeyVal, print_key_value);
     function print_accessor(type) {
         return function(output) {
             var self = this;
@@ -1483,6 +1524,7 @@ function OutputStream(options) {
         AST_Constant,
         AST_Debugger,
         AST_Definitions,
+        AST_Destructured,
         AST_Finally,
         AST_Jump,
         AST_Lambda,
@@ -1497,7 +1539,7 @@ function OutputStream(options) {
         output.add_mapping(this.start);
     });
 
-    DEFMAP([ AST_ObjectProperty ], function(output) {
+    DEFMAP([ AST_DestructuredKeyVal, AST_ObjectProperty ], function(output) {
         if (typeof this.key == "string") output.add_mapping(this.start, this.key);
     });
 })();
index c91fb73..a422a5a 100644 (file)
@@ -973,14 +973,16 @@ function parse($TEXT, options) {
         if (!is("punc", ";")) {
             init = is("keyword", "const")
                 ? (next(), const_(true))
+                : is("keyword", "let")
+                ? (next(), let_(true))
                 : is("keyword", "var")
                 ? (next(), var_(true))
                 : expression(true, true);
             if (is("operator", "in")) {
-                if (init instanceof AST_Var) {
+                if (init instanceof AST_Definitions) {
                     if (init.definitions.length > 1)
                         croak("Only one variable declaration allowed in for..in loop", init.start.line, init.start.col, init.start.pos);
-                } else if (!is_assignable(init)) {
+                } else if (!(is_assignable(init) || (init = to_destructured(init)) instanceof AST_Destructured)) {
                     croak("Invalid left-hand side in for..in loop", init.start.line, init.start.col, init.start.pos);
                 }
                 next();
@@ -1025,7 +1027,7 @@ function parse($TEXT, options) {
         var argnames = [];
         for (var first = true; !is("punc", ")");) {
             if (first) first = false; else expect(",");
-            argnames.push(as_symbol(AST_SymbolFunarg));
+            argnames.push(maybe_destructured(AST_SymbolFunarg));
         }
         next();
         var loop = S.in_loop;
@@ -1146,16 +1148,16 @@ function parse($TEXT, options) {
         });
     }
 
-    function vardefs(type, no_in, must_init) {
+    function vardefs(type, no_in) {
         var a = [];
         for (;;) {
             var start = S.token;
-            var name = as_symbol(type);
+            var name = maybe_destructured(type);
             var value = null;
             if (is("operator", "=")) {
                 next();
                 value = expression(false, no_in);
-            } else if (must_init) {
+            } else if (!no_in && (type === AST_SymbolConst || name instanceof AST_Destructured)) {
                 croak("Missing initializer in declaration");
             }
             a.push(new AST_VarDef({
@@ -1174,7 +1176,7 @@ function parse($TEXT, options) {
     var const_ = function(no_in) {
         return new AST_Const({
             start       : prev(),
-            definitions : vardefs(AST_SymbolConst, no_in, true),
+            definitions : vardefs(AST_SymbolConst, no_in),
             end         : prev()
         });
     };
@@ -1306,7 +1308,8 @@ function parse($TEXT, options) {
         unexpected();
     };
 
-    function expr_list(closing, allow_trailing_comma, allow_empty) {
+    function expr_list(closing, allow_trailing_comma, allow_empty, parser) {
+        if (!parser) parser = expression;
         var first = true, a = [];
         while (!is("punc", closing)) {
             if (first) first = false; else expect(",");
@@ -1314,7 +1317,7 @@ function parse($TEXT, options) {
             if (is("punc", ",") && allow_empty) {
                 a.push(new AST_Hole({ start: S.token, end: S.token }));
             } else {
-                a.push(expression(false));
+                a.push(parser());
             }
         }
         next();
@@ -1449,6 +1452,54 @@ function parse($TEXT, options) {
         return sym;
     }
 
+    function maybe_destructured(type) {
+        var start = S.token;
+        if (is("punc", "[")) {
+            next();
+            return new AST_DestructuredArray({
+                start: start,
+                elements: expr_list("]", !options.strict, true, function() {
+                    return maybe_destructured(type);
+                }),
+                end: prev(),
+            });
+        }
+        if (is("punc", "{")) {
+            next();
+            var first = true, a = [];
+            while (!is("punc", "}")) {
+                if (first) first = false; else expect(",");
+                // allow trailing comma
+                if (!options.strict && is("punc", "}")) break;
+                var key_start = S.token;
+                var key = as_property_key();
+                if (!is("punc", ":") && key_start.type == "name") {
+                    a.push(new AST_DestructuredKeyVal({
+                        start: key_start,
+                        key: key,
+                        value: _make_symbol(type, key_start),
+                        end: prev(),
+                    }));
+                    continue;
+                }
+                expect(":");
+                a.push(new AST_DestructuredKeyVal({
+                    start: key_start,
+                    key: key,
+                    value: maybe_destructured(type),
+                    end: prev(),
+                }));
+            }
+            next();
+            return new AST_DestructuredObject({
+                start: start,
+                properties: a,
+                end: prev(),
+            });
+        }
+        return as_symbol(type);
+    }
+
     function mark_pure(call) {
         var start = call.start;
         var comments = start.comments_before;
@@ -1578,11 +1629,43 @@ function parse($TEXT, options) {
         return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef;
     }
 
+    function to_destructured(node) {
+        if (node instanceof AST_Array) {
+            var elements = node.elements.map(to_destructured);
+            return all(elements, function(node) {
+                return node instanceof AST_Destructured || node instanceof AST_Hole || is_assignable(node);
+            }) ? new AST_DestructuredArray({
+                start: node.start,
+                elements: elements,
+                end: node.end,
+            }) : node;
+        }
+        if (!(node instanceof AST_Object)) return node;
+        var props = [];
+        for (var i = 0; i < node.properties.length; i++) {
+            var prop = node.properties[i];
+            if (!(prop instanceof AST_ObjectKeyVal)) return node;
+            var value = to_destructured(prop.value);
+            if (!(value instanceof AST_Destructured || is_assignable(value))) return node;
+            props.push(new AST_DestructuredKeyVal({
+                start: prop.start,
+                key: prop.key,
+                value: value,
+                end: prop.end,
+            }));
+        }
+        return new AST_DestructuredObject({
+            start: node.start,
+            properties: props,
+            end: node.end,
+        });
+    }
+
     var maybe_assign = function(no_in) {
         var start = S.token;
         var left = maybe_conditional(no_in), val = S.token.value;
         if (is("operator") && ASSIGNMENT[val]) {
-            if (is_assignable(left)) {
+            if (is_assignable(left) || val == "=" && (left = to_destructured(left)) instanceof AST_Destructured) {
                 next();
                 return new AST_Assign({
                     start    : start,
index 80034a5..bffd493 100644 (file)
@@ -204,7 +204,17 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
 
     // pass 2: find back references and eval
     self.globals = new Dictionary();
+    var in_arg = [];
     var tw = new TreeWalker(function(node) {
+        if (node instanceof AST_Lambda) {
+            in_arg.push(node);
+            node.argnames.forEach(function(argname) {
+                argname.walk(tw);
+            });
+            in_arg.pop();
+            walk_body(node, tw);
+            return true;
+        }
         if (node instanceof AST_LoopControl) {
             if (node.label) node.label.thedef.references.push(node);
             return true;
@@ -212,6 +222,16 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
         if (node instanceof AST_SymbolRef) {
             var name = node.name;
             var sym = node.scope.find_variable(name);
+            for (var i = in_arg.length; i > 0 && sym;) {
+                i = in_arg.lastIndexOf(sym.scope, i - 1);
+                if (i < 0) break;
+                var decl = sym.orig[0];
+                if (decl instanceof AST_SymbolFunarg || decl instanceof AST_SymbolLambda) {
+                    node.in_arg = true;
+                    break;
+                }
+                sym = sym.scope.parent_scope.find_variable(name);
+            }
             if (!sym) {
                 sym = self.def_global(node);
             } else if (name == "arguments" && sym.scope instanceof AST_Lambda) {
index 4aeb0f0..3831504 100644 (file)
@@ -160,6 +160,16 @@ TreeTransformer.prototype = new TreeWalker;
     DEF(AST_Array, function(self, tw) {
         self.elements = do_list(self.elements, tw);
     });
+    DEF(AST_DestructuredArray, function(self, tw) {
+        self.elements = do_list(self.elements, tw);
+    });
+    DEF(AST_DestructuredKeyVal, function(self, tw) {
+        if (self.key instanceof AST_Node) self.key = self.key.transform(tw);
+        self.value = self.value.transform(tw);
+    });
+    DEF(AST_DestructuredObject, function(self, tw) {
+        self.properties = do_list(self.properties, tw);
+    });
     DEF(AST_Object, function(self, tw) {
         self.properties = do_list(self.properties, tw);
     });
diff --git a/test/compress/destructured.js b/test/compress/destructured.js
new file mode 100644 (file)
index 0000000..01b2456
--- /dev/null
@@ -0,0 +1,1297 @@
+redefine_arguments_1: {
+    options = {
+        toplevel: false,
+        unused: true,
+    }
+    input: {
+        function f([ arguments ]) {}
+    }
+    expect: {
+        function f([]) {}
+    }
+    expect_stdout: true
+    node_version: ">=8"
+}
+
+redefine_arguments_1_toplevel: {
+    options = {
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        function f([ arguments ]) {}
+    }
+    expect: {}
+    expect_stdout: true
+    node_version: ">=8"
+}
+
+redefine_arguments_2: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        (function([], arguments) {});
+    }
+    expect: {}
+    expect_stdout: true
+    node_version: ">=8"
+}
+
+redefine_arguments_3: {
+    options = {
+        keep_fargs: "strict",
+        unused: true,
+    }
+    input: {
+        (function([], arguments) {})([]);
+    }
+    expect: {
+        (function() {})();
+    }
+    expect_stdout: true
+    node_version: ">=8"
+}
+
+redefine_arguments_4: {
+    options = {
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        function f() {
+            (function({}, arguments) {});
+        }
+    }
+    expect: {}
+    expect_stdout: true
+    node_version: ">=8"
+}
+
+uses_arguments_1_merge_vars: {
+    options = {
+        merge_vars: true,
+    }
+    input: {
+        console.log(typeof function({}) {
+            return arguments;
+        }(42));
+    }
+    expect: {
+        console.log(typeof function({}) {
+            return arguments;
+        }(42));
+    }
+    expect_stdout: "object"
+    node_version: ">=6"
+}
+
+uses_arguments_1_unused: {
+    options = {
+        unused: true,
+    }
+    input: {
+        console.log(typeof function({}) {
+            return arguments;
+        }(42));
+    }
+    expect: {
+        console.log(typeof function({}) {
+            return arguments;
+        }(42));
+    }
+    expect_stdout: "object"
+    node_version: ">=6"
+}
+
+uses_arguments_2: {
+    options = {
+        collapse_vars: true,
+        reduce_vars: true,
+    }
+    input: {
+        console.log(typeof function({ a }) {
+            a[1] = 2;
+            return arguments;
+        }({ a: 42 }));
+    }
+    expect: {
+        console.log(typeof function({ a }) {
+            a[1] = 2;
+            return arguments;
+        }({ a: 42 }));
+    }
+    expect_stdout: "object"
+    node_version: ">=6"
+}
+
+funarg_merge_vars_1: {
+    options = {
+        merge_vars: true,
+    }
+    input: {
+        function f(a, {
+            [a]: b
+        }) {
+            console.log(b);
+        }
+        f(0, [ "PASS" ]);
+    }
+    expect: {
+        function f(a, {
+            [a]: b
+        }) {
+            console.log(b);
+        }
+        f(0, [ "PASS" ]);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_merge_vars_2: {
+    options = {
+        merge_vars: true,
+    }
+    input: {
+        var a = 0;
+        (function f({
+            [a]: b,
+        }) {
+            var a = typeof b;
+            console.log(a);
+        })([ 42 ]);
+    }
+    expect: {
+        var a = 0;
+        (function f({
+            [a]: b,
+        }) {
+            var a = typeof b;
+            console.log(a);
+        })([ 42 ]);
+    }
+    expect_stdout: "number"
+    node_version: ">=6"
+}
+
+funarg_side_effects_1: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        try {
+            (function({}) {})();
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        try {
+            (function({}) {})();
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_side_effects_2: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        try {
+            (function({
+                [(a, 0)]: a,
+            }) {})(1);
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        try {
+            (function({
+                [(a, 0)]: a,
+            }) {})(1);
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_side_effects_3: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        try {
+            (function({
+                p: {
+                    [(a, 0)]: a,
+                },
+            }) {})({
+                p: 1,
+            });
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        try {
+            (function({
+                p: {
+                    [(a, 0)]: a,
+                },
+            }) {})({
+                p: 1,
+            });
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_unused_1: {
+    options = {
+        keep_fargs: "strict",
+        unused: true,
+    }
+    input: {
+        (function([]) {})([ console.log("PASS") ]);
+    }
+    expect: {
+        (function() {})(console.log("PASS"));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_unused_2: {
+    options = {
+        unused: true,
+    }
+    input: {
+        function f([ a, b, c ]) {
+            console.log(b);
+        }
+        f([ "FAIL", "PASS" ]);
+    }
+    expect: {
+        function f([ , b ]) {
+            console.log(b);
+        }
+        f([ "FAIL", "PASS" ]);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_unused_3: {
+    options = {
+        objects: true,
+        evaluate: true,
+        unused: true,
+    }
+    input: {
+        function f({
+            [0]: a,
+        }) {
+            return "PASS";
+        }
+        console.log(f([ "FAIL" ]));
+    }
+    expect: {
+        function f({}) {
+            return "PASS";
+        }
+        console.log(f([ "FAIL" ]));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_unused_4: {
+    options = {
+        keep_fargs: "strict",
+        pure_getters: "strict",
+        unused: true,
+    }
+    input: {
+        console.log(function([ a ], { b }, c) {
+            return "PASS";
+        }([ 1 ], { b: 2 }, 3));
+    }
+    expect: {
+        console.log(function() {
+            return "PASS";
+        }());
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_unused_5: {
+    options = {
+        unused: true,
+    }
+    input: {
+        try {
+            (function({
+                [c = 0]: c
+            }) {})(1);
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        try {
+            (function({
+                [c = 0]: c
+            }) {})(1);
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_unused_6_inline: {
+    options = {
+        inline: true,
+        pure_getters: "strict",
+        unused: true,
+    }
+    input: {
+        (function(a) {
+            var {} = (a = console, 42);
+        })();
+        console.log(typeof a);
+    }
+    expect: {
+        void console;
+        console.log(typeof a);
+    }
+    expect_stdout: "undefined"
+    node_version: ">=6"
+}
+
+funarg_unused_6_keep_fargs: {
+    options = {
+        keep_fargs: "strict",
+        unused: true,
+    }
+    input: {
+        (function(a) {
+            var {} = (a = console, 42);
+        })();
+        console.log(typeof a);
+    }
+    expect: {
+        (function() {
+            var {} = (console, 42);
+        })();
+        console.log(typeof a);
+    }
+    expect_stdout: "undefined"
+    node_version: ">=6"
+}
+
+funarg_collapse_vars_1: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        console.log(function(a, {}) {
+            return typeof a;
+            var b;
+        }(console, {}));
+    }
+    expect: {
+        console.log(function(a, {}) {
+            return typeof (0, console);
+        }(0, {}));
+    }
+    expect_stdout: "object"
+    node_version: ">=6"
+}
+
+funarg_collapse_vars_2: {
+    options = {
+        collapse_vars: true,
+        keep_fargs: "strict",
+        unused: true,
+    }
+    input: {
+        console.log(function([ a ], { b }, c) {
+            return a + b + c;
+        }([ "P" ], { b: "A" }, "SS"));
+    }
+    expect: {
+        console.log(function([ a ], { b }) {
+            return a + b + "SS";
+        }([ "P" ], { b: "A" }));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_collapse_vars_3: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = "FAIL";
+        try {
+            a = "PASS";
+            (function({}) {})();
+            throw "PASS";
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect: {
+        var a = "FAIL";
+        try {
+            a = "PASS";
+            (function({}) {})();
+            throw "PASS";
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_reduce_vars_1: {
+    options = {
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        try {
+            (function({
+                [a]: b,
+            }, a) {
+                console.log("FAIL");
+            })({});
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        try {
+            (function({
+                [a]: b,
+            }, a) {
+                console.log("FAIL");
+            })({});
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_reduce_vars_2: {
+    options = {
+        evaluate: true,
+        keep_fargs: "strict",
+        pure_getters: "strict",
+        reduce_vars: true,
+        unsafe: true,
+        unused: true,
+    }
+    input: {
+        console.log(function([ a ], { b }, c) {
+            return a + b + c;
+        }([ "P" ], { b: "A" }, "SS"));
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_reduce_vars_3: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+    }
+    input: {
+        var a = 0;
+        (function({
+            [a++]: b
+        }) {})(0);
+        console.log(a);
+    }
+    expect: {
+        var a = 0;
+        (function({
+            [a++]: b
+        }) {})(0);
+        console.log(1);
+    }
+    expect_stdout: "1"
+    node_version: ">=6"
+}
+
+funarg_reduce_vars_4: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+    }
+    input: {
+        try {
+            (function f({
+                [a = 1]: a,
+            }) {})(2);
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        try {
+            (function f({
+                [a = 1]: a,
+            }) {})(2);
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+funarg_computed_key_scope_1: {
+    rename = true
+    input: {
+        var b = 0;
+        function f({
+            [b]: a
+        }) {
+            var b = 42;
+            console.log(a, b);
+        }
+        f([ "PASS" ]);
+    }
+    expect: {
+        var b = 0;
+        function f({
+            [b]: a
+        }) {
+            var c = 42;
+            console.log(a, c);
+        }
+        f([ "PASS" ]);
+    }
+    expect_stdout: "PASS 42"
+    node_version: ">=6"
+}
+
+funarg_computed_key_scope_2: {
+    options = {
+        reduce_funcs: true,
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        (function({
+            [function() {
+                console.log(typeof f);
+            }()]: a
+        }) {
+            function f() {}
+        })(0);
+    }
+    expect: {
+        (function({
+            [function() {
+                console.log(typeof f);
+            }()]: a
+        }) {})(0);
+    }
+    expect_stdout: "undefined"
+    node_version: ">=6"
+}
+
+funarg_computed_key_scope_3: {
+    options = {
+        reduce_funcs: true,
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        (function({
+            [function() {
+                (function({
+                    [function() {
+                        console.log(typeof f, typeof g, typeof h);
+                    }()]: a
+                }) {
+                    function f() {}
+                })(1);
+                function g() {}
+            }()]: b
+        }) {
+            function h() {}
+        })(2);
+    }
+    expect: {
+        (function({
+            [function() {
+                (function({
+                    [function() {
+                        console.log(typeof f, typeof function() {}, typeof h);
+                    }()]: a
+                }) {})(1);
+            }()]: b
+        }) {})(2);
+    }
+    expect_stdout: "undefined function undefined"
+    node_version: ">=6"
+}
+
+funarg_inline: {
+    options = {
+        inline: true,
+        reduce_vars: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        try {
+            function f({}) {
+                return 42;
+            }
+            var a = f();
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        try {
+            (function({}) {})();
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+simple_const: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+        unsafe: true,
+        unused: true,
+        varify: true,
+    }
+    input: {
+        const [ a ] = [ "PASS" ];
+        console.log(a);
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+simple_let: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+        unsafe: true,
+        unused: true,
+        varify: true,
+    }
+    input: {
+        let [ a ] = [ "PASS" ];
+        console.log(a);
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+simple_var: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+        unsafe: true,
+        unused: true,
+    }
+    input: {
+        var [ a ] = [ "PASS" ];
+        console.log(a);
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+collapse_vars_1: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = "PASS";
+        var {
+            [a.p]: a,
+        } = !console.log(a);
+    }
+    expect: {
+        var a = "PASS";
+        var {
+            [a.p]: a,
+        } = !console.log(a);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+collapse_vars_2: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        console.log(function() {
+            [] = [];
+            return "PASS";
+        }());
+    }
+    expect: {
+        console.log(function() {
+            [] = [];
+            return "PASS";
+        }());
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+collapse_vars_3: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        console.log(function(a) {
+            [ a ] = (a = "FAIL", [ "PASS" ]);
+            return a;
+        }());
+    }
+    expect: {
+        console.log(function(a) {
+            [ a ] = (a = "FAIL", [ "PASS" ]);
+            return a;
+        }());
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+collapse_vars_4: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a;
+        try {
+            a = 42;
+            [ 42..p ] = null;
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect: {
+        var a;
+        try {
+            a = 42;
+            [ 42..p ] = null;
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect_stdout: "42"
+    node_version: ">=6"
+}
+
+collapse_vars_5: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a;
+        try {
+            [] = (a = 42, null);
+            a = 42;
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect: {
+        var a;
+        try {
+            [] = (a = 42, null);
+            a = 42;
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect_stdout: "42"
+    node_version: ">=6"
+}
+
+collapse_vars_6: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a;
+        try {
+            var [] = (a = 42, null);
+            a = 42;
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect: {
+        var a;
+        try {
+            var [] = (a = 42, null);
+            a = 42;
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect_stdout: "42"
+    node_version: ">=6"
+}
+
+collapse_vars_7: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = "FAIL";
+        try {
+            (function() {
+                [] = (a = "PASS", null);
+                return "PASS";
+            })();
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect: {
+        var a = "FAIL";
+        try {
+            (function() {
+                [] = (a = "PASS", null);
+                return "PASS";
+            })();
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+collapse_vars_8: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = "FAIL";
+        try {
+            (function() {
+                var {} = (a = "PASS", null);
+                return "PASS";
+            })();
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect: {
+        var a = "FAIL";
+        try {
+            (function() {
+                var {} = (a = "PASS", null);
+                return "PASS";
+            })();
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+conditionals: {
+    options = {
+        conditionals: true,
+    }
+    input: {
+        if (console.log("PASS")) {
+            var [] = 0;
+        }
+    }
+    expect: {
+        console.log("PASS") && ([] = 0);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+dead_code: {
+    options = {
+        conditionals: true,
+        dead_code: true,
+        evaluate: true,
+    }
+    input: {
+        if (0) {
+            let [] = 42;
+            var { a, b: [ c ] } = null;
+        }
+        console.log("PASS");
+    }
+    expect: {
+        0;
+        var a, c;
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+drop_unused_1: {
+    options = {
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        switch (0) {
+          case console.log(a, a):
+            try {
+                throw 42;
+            } catch (a) {
+                var [ a ] = [];
+            }
+        }
+    }
+    expect: {
+        switch (0) {
+          case console.log(a, a):
+            try {
+                throw 42;
+            } catch (a) {
+                var [ a ] = [];
+            }
+        }
+    }
+    expect_stdout: "undefined undefined"
+    node_version: ">=6"
+}
+
+drop_unused_2: {
+    options = {
+        merge_vars: true,
+        pure_getters: "strict",
+        reduce_vars: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        function f(a) {
+            var b = [ console.log("PASS"), a ], {
+                p: a,
+            } = 0;
+        }
+        f();
+    }
+    expect:{
+        (function(a) {
+            console.log("PASS");
+        })();
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+join_vars: {
+    options = {
+        conditionals: true,
+        join_vars: true,
+    }
+    input: {
+        const [ a ] = [ "PASS" ];
+        a,
+        console.log(a);
+    }
+    expect: {
+        const [ a ] = [ "PASS" ];
+        a,
+        console.log(a);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+keep_fargs: {
+    options = {
+        keep_fargs: "strict",
+        unused: true,
+    }
+    input: {
+        console.log(function f(a) {
+            var {} = a;
+        }(0));
+    }
+    expect: {
+        console.log(function(a) {
+            var {} = a;
+        }(0));
+    }
+    expect_stdout: "undefined"
+    node_version: ">=6"
+}
+
+reduce_vars_1: {
+    options = {
+        reduce_vars: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var a;
+        console.log("PASS") && ([ a ] = 0);
+    }
+    expect: {
+        var a;
+        console.log("PASS") && ([ a ] = 0);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+reduce_vars_2: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var a = "FAIL", b = 42;
+        ({
+            [console.log(a, b)]: b.p
+        } = a = "PASS");
+    }
+    expect: {
+        ({
+            [console.log("PASS", 42)]: 42..p
+        } = "PASS");
+    }
+    expect_stdout: "PASS 42"
+    node_version: ">=6"
+}
+
+computed_key_evaluate: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+    }
+    input: {
+        var a = 0, {
+            [++a]: b,
+        } = [ "FAIL 1", a ? "FAIL 2" : "PASS" ];
+        console.log(b);
+    }
+    expect: {
+        var a = 0, {
+            [1]: b,
+        } = [ "FAIL 1", 0 ? "FAIL 2" : "PASS" ];
+        console.log(b);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+computed_key_unused: {
+    options = {
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var {
+            [console.log("bar")]: a,
+            [console.log("baz")]: { b },
+            [console.log("moo")]: [
+                c,
+                {
+                    [console.log("moz")]: d,
+                    e,
+                },
+            ],
+        } = {
+            [console.log("foo")]: [ null, 42 ],
+        };
+    }
+    expect: {
+        var {
+            [console.log("bar")]: a,
+            [console.log("baz")]: {},
+            [console.log("moo")]: [
+                ,
+                {
+                    [console.log("moz")]: d,
+                },
+            ],
+        } = {
+            [console.log("foo")]: [ null, 42 ],
+        };
+    }
+    expect_stdout: [
+        "foo",
+        "bar",
+        "baz",
+        "moo",
+        "moz",
+    ]
+    node_version: ">=6"
+}
+
+for_in_1: {
+    options = {
+        loops: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        for (var { a } in console.log("PASS"));
+    }
+    expect: {
+        for (var { a } in console.log("PASS"));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+for_in_2: {
+    options = {
+        join_vars: true,
+    }
+    input: {
+        var a;
+        for (var { b } in console.log("PASS"));
+    }
+    expect: {
+        var a, b;
+        for ({ b } in console.log("PASS"));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+for_in_3: {
+    options = {
+        merge_vars: true,
+        reduce_vars: true,
+    }
+    input: {
+        for (var { length: a } in [ 42 ])
+            console.log(a);
+    }
+    expect: {
+        for (var { length: a } in [ 42 ])
+            console.log(a);
+    }
+    expect_stdout: "1"
+    node_version: ">=6"
+}
+
+fn_name_evaluate: {
+    options = {
+        evaluate: true,
+        objects: true,
+        reduce_vars: true,
+        typeofs: true,
+    }
+    input: {
+        console.log(function f({
+            [typeof f]: a,
+        }) {
+            var f;
+            return a;
+        }({
+            function: "PASS",
+            undefined: "FAIL",
+        }));
+    }
+    expect: {
+        console.log(function f({
+            function: a,
+        }) {
+            var f;
+            return a;
+        }({
+            function: "PASS",
+            undefined: "FAIL",
+        }));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+fn_name_unused: {
+    options = {
+        unused: true,
+    }
+    input: {
+        console.log(function f({
+            [typeof f]: a,
+        }) {
+            var f;
+            return a;
+        }({
+            function: "PASS",
+            undefined: "FAIL",
+        }));
+    }
+    expect: {
+        console.log(function f({
+            [typeof f]: a,
+        }) {
+            var f;
+            return a;
+        }({
+            function: "PASS",
+            undefined: "FAIL",
+        }));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
index ab742af..f126573 100644 (file)
@@ -95,6 +95,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
 
             // quick ignores
             if (node instanceof U.AST_Accessor) return;
+            if (node instanceof U.AST_Destructured) return;
             if (node instanceof U.AST_Directive) return;
             if (!in_list && node instanceof U.AST_EmptyStatement) return;
             if (node instanceof U.AST_Label) return;
@@ -114,6 +115,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
 
             // ignore lvalues
             if (parent instanceof U.AST_Assign && parent.left === node) return;
+            if (parent instanceof U.AST_Destructured) return;
+            if (parent instanceof U.AST_DestructuredKeyVal && parent.value === node) return;
             if (parent instanceof U.AST_Unary && parent.expression === node) switch (parent.operator) {
               case "++":
               case "--":
@@ -250,13 +253,23 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
                 }
             }
             else if (node instanceof U.AST_ForIn) {
-                var expr = [
-                    node.init,
-                    node.object,
-                    node.body,
-                ][ (node.start._permute * steps | 0) % 3 ];
+                var expr;
+                switch ((node.start._permute * steps | 0) % 3) {
+                  case 0:
+                    if (!(node.init instanceof U.AST_Definitions
+                        && node.init.definitions[0].name instanceof U.AST_Destructured)) {
+                        expr = node.init;
+                    }
+                    break;
+                  case 1:
+                    expr = node.object;
+                    break;
+                  case 2:
+                    if (!has_loopcontrol(node.body, node, parent)) expr = node.body;
+                    break;
+                }
                 node.start._permute += step;
-                if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
+                if (expr) {
                     CHANGED = true;
                     return to_statement(expr);
                 }
@@ -389,6 +402,13 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
                     CHANGED = true;
                     return List.skip;
                 }
+
+                // skip element/property from (destructured) array/object
+                if (parent instanceof U.AST_Array || parent instanceof U.AST_Destructured || parent instanceof AST_Object) {
+                    node.start._permute++;
+                    CHANGED = true;
+                    return List.skip;
+                }
             }
 
             // replace this node
index 82fdc37..1c4af3c 100644 (file)
@@ -377,6 +377,118 @@ function createArgs(recurmax, stmtDepth, canThrow) {
     return args.join(", ");
 }
 
+function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames, maybe, dontStore) {
+    var avoid = [];
+    var len = unique_vars.length;
+    var pairs = createPairs(recurmax);
+    unique_vars.length = len;
+    return pairs;
+
+    function createAssignmentValue(recurmax) {
+        var current = VAR_NAMES;
+        VAR_NAMES = (varNames || VAR_NAMES).slice();
+        var value = varNames && rng(2) ? createValue() : createExpression(recurmax, noComma, stmtDepth, canThrow);
+        VAR_NAMES = current;
+        return value;
+    }
+
+    function createKey(recurmax, keys) {
+        var save = VAR_NAMES;
+        VAR_NAMES = VAR_NAMES.filter(function(name) {
+            return avoid.indexOf(name) < 0;
+        });
+        var len = VAR_NAMES.length;
+        var key;
+        do {
+            key = createObjectKey(recurmax, stmtDepth, canThrow);
+        } while (keys.indexOf(key) >= 0);
+        VAR_NAMES = save.concat(VAR_NAMES.slice(len));
+        return key;
+    }
+
+    function createPairs(recurmax) {
+        var names = [], values = [];
+        var m = rng(4), n = rng(4);
+        if (!varNames) m = Math.max(m, n, 1);
+        for (var i = Math.max(m, n); --i >= 0;) {
+            if (i < m && i < n) {
+                createDestructured(recurmax, names, values);
+                continue;
+            }
+            if (i < m) {
+                unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity");
+                var name = createVarName(maybe, dontStore);
+                unique_vars.length -= 6;
+                avoid.push(name);
+                unique_vars.push(name);
+                names.unshift(name);
+            }
+            if (i < n) {
+                values.unshift(createAssignmentValue(recurmax));
+            }
+        }
+        return {
+            names: names,
+            values: values,
+        };
+    }
+
+    function createDestructured(recurmax, names, values) {
+        switch (rng(20)) {
+          case 0:
+            if (--recurmax < 0) {
+                names.unshift("[]");
+                values.unshift('""');
+            } else {
+                var pairs = createPairs(recurmax);
+                while (!rng(10)) {
+                    var index = rng(pairs.names.length + 1);
+                    pairs.names.splice(index, 0, "");
+                    pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : "");
+                }
+                names.unshift("[ " + pairs.names.join(", ") + " ]");
+                values.unshift("[ " + pairs.values.join(", ") + " ]");
+            }
+            break;
+          case 1:
+            if (--recurmax < 0) {
+                names.unshift("{}");
+                values.unshift('""');
+            } else {
+                var pairs = createPairs(recurmax);
+                var keys = [];
+                pairs.names.forEach(function(name, index) {
+                    if (/^[[{]/.test(name)) {
+                        var key;
+                        do {
+                            key = KEYS[rng(KEYS.length)];
+                        } while (keys.indexOf(key) >= 0);
+                        keys[index] = key;
+                    }
+                });
+                names.unshift("{ " + pairs.names.map(function(name, index) {
+                    var key = index in keys ? keys[index] : rng(10) && createKey(recurmax, keys);
+                    return key ? key + ": " + name : name;
+                }).join(", ") + " }");
+                values.unshift("{ " + pairs.values.map(function(value, index) {
+                    var key = index in keys ? keys[index] : createKey(recurmax, keys);
+                    return key + ": " + value;
+                }).join(", ") + " }");
+            }
+            break;
+          default:
+            unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity");
+            var name = createVarName(maybe, dontStore);
+            unique_vars.length -= 6;
+            avoid.push(name);
+            unique_vars.push(name);
+            names.unshift(name);
+            values.unshift(createAssignmentValue(recurmax));
+            break;
+        }
+    }
+}
+
 function filterDirective(s) {
     if (!generate_directive && !s[1] && /\("/.test(s[2])) s[2] = ";" + s[2];
     return s;
@@ -415,11 +527,37 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
             return names.indexOf(name) < 0;
         });
         var len = VAR_NAMES.length;
-        var s = type + " " + names.map(function(name) {
-            var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
-            VAR_NAMES.push(name);
-            return name + " = " + value;
-        }).join(", ") + ";";
+        var s = type + " ";
+        switch (rng(10)) {
+          case 0:
+            while (!rng(10)) names.splice(rng(names.length + 1), 0, "");
+            s += "[ " + names.join(", ") + " ] = [ " + names.map(function() {
+                return rng(10) ? createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) : "";
+            }).join(", ") + " ];";
+            break;
+          case 1:
+            var keys = [];
+            s += "{ " + names.map(function(name, i) {
+                var key = createObjectKey(recurmax, stmtDepth, canThrow);
+                if (!/\[/.test(key)) keys[i] = key;
+                return key + ": " + name;
+            }).join(", ") + "} = { " + names.map(function(name, i) {
+                var key = i in keys ? keys[i] : createObjectKey(recurmax, stmtDepth, canThrow);
+                return key + ": " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
+            }).join(", ") + "};";
+            break;
+          default:
+            s += names.map(function(name, i) {
+                if (type == "let" && !rng(10)) {
+                    VAR_NAMES.push(name);
+                    return name;
+                }
+                var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
+                VAR_NAMES.push(name);
+                return name + " = " + value;
+            }).join(", ") + ";";
+            break;
+        }
         VAR_NAMES = save.concat(VAR_NAMES.slice(len));
         return s;
     }
@@ -429,9 +567,9 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
     if (--recurmax < 0) { return ";"; }
     if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
     var s = [];
-    var name;
+    var name, args;
+    var varNames = VAR_NAMES.slice();
     createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
-        var namesLenBefore = VAR_NAMES.length;
         if (allowDefun || rng(5) > 0) {
             name = "f" + funcs++;
         } else {
@@ -439,7 +577,16 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
             name = createVarName(MANDATORY, !allowDefun);
             unique_vars.length -= 3;
         }
-        s.push("function " + name + "(" + createParams() + "){", strictMode());
+        var params;
+        if ((!allowDefun || !(name in called)) && rng(2)) {
+            called[name] = false;
+            var pairs = createAssignmentPairs(recurmax, COMMA_OK, stmtDepth, canThrow, varNames, MANDATORY);
+            params = pairs.names.join(", ");
+            args = pairs.values.join(", ");
+        } else {
+            params = createParams();
+        }
+        s.push("function " + name + "(" + params + "){", strictMode());
         s.push(defns());
         if (rng(5) === 0) {
             // functions with functions. lower the recursion to prevent a mess.
@@ -450,17 +597,16 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
         }
         s.push("}", "");
         s = filterDirective(s).join("\n");
-
-        VAR_NAMES.length = namesLenBefore;
     });
+    VAR_NAMES = varNames;
 
     if (!allowDefun) {
         // avoid "function statements" (decl inside statements)
         s = "var " + createVarName(MANDATORY) + " = " + s;
-        s += "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
-    } else if (!(name in called) || rng(3) > 0) {
+        s += "(" + (args || createArgs(recurmax, stmtDepth, canThrow)) + ")";
+    } else if (!(name in called) || args || rng(3)) {
         s += "var " + createVarName(MANDATORY) + " = " + name;
-        s += "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
+        s += "(" + (args || createArgs(recurmax, stmtDepth, canThrow)) + ")";
     }
 
     return s + ";";
@@ -561,8 +707,9 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
         return [
             "{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ",
             label.target + " for (",
-            /^key/.test(key) ? "var " : "",
-            key + " in expr" + loop + ") {",
+            !/^key/.test(key) ? rng(10) ? "" : "var " : rng(10) ? "var " : rng(2) ? "let " : "const ",
+            rng(20) ? key : "{ length: " + key + " }",
+            " in expr" + loop + ") {",
             rng(5) > 1 ? "c = 1 + c; var " + createVarName(MANDATORY) + " = expr" + loop + "[" + key + "]; " : "",
             createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
             "}}",
@@ -576,7 +723,12 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
         // note: default does not _need_ to be last
         return "switch (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") { " + createSwitchParts(recurmax, 4, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "}";
       case STMT_VAR:
-        switch (rng(3)) {
+        if (!rng(20)) {
+            var pairs = createAssignmentPairs(recurmax, NO_COMMA, stmtDepth, canThrow, null, MANDATORY);
+            return "var " + pairs.names.map(function(name, index) {
+                return index in pairs.values ? name + " = " + pairs.values[index] : name;
+            }).join(", ") + ";";
+        } else switch (rng(3)) {
           case 0:
             unique_vars.push("c");
             var name = createVarName(MANDATORY);
@@ -719,7 +871,28 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
       case p++:
         return getVarName();
       case p++:
-        return getVarName(NO_CONST) + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
+        switch (rng(20)) {
+          case 0:
+            return [
+                "[ ",
+                new Array(rng(3)).join(","),
+                getVarName(NO_CONST),
+                new Array(rng(3)).join(","),
+                " ] = ",
+                createArrayLiteral(recurmax, stmtDepth, canThrow),
+            ].join("");
+          case 1:
+            return [
+                "{ ",
+                rng(2) ? "" : createObjectKey(recurmax, stmtDepth, canThrow) + ": ",
+                getVarName(NO_CONST),
+                " } = ",
+                createExpression(recurmax, COMMA_OK, stmtDepth, canThrow),
+                " || {}",
+            ].join("");
+          default:
+            return getVarName(NO_CONST) + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
+        }
       case p++:
         return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
       case p++:
@@ -864,7 +1037,10 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
       case p++:
       case p++:
       case p++:
-        var name = rng(3) == 0 ? getVarName() : "f" + rng(funcs + 2);
+        var name;
+        do {
+            name = rng(3) == 0 ? getVarName() : "f" + rng(funcs + 2);
+        } while (name in called && !called[name]);
         called[name] = true;
         return "typeof " + name + ' == "function" && --_calls_ >= 0 && ' + name + "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
     }
@@ -1002,13 +1178,77 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
         return "(" + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
       case 3:
         assignee = getVarName();
-        expr = "(" + assignee + "[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow)
-            + "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
+        switch (rng(20)) {
+          case 0:
+            expr = [
+                "([ ",
+                assignee,
+                "[", createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), "]",
+                " ] = [ ",
+                _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+                " ])",
+            ].join("");
+            break;
+          case 1:
+            var key1 = createObjectKey(recurmax, stmtDepth, canThrow);
+            var key2 = /^\[/.test(key1) ? createObjectKey(recurmax, stmtDepth, canThrow) : key1;
+            expr = [
+                "({ ",
+                key1, ": ", assignee,
+                "[", createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), "]",
+                " } = { ",
+                key2, ": ", _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+                " })",
+            ].join("");
+            break;
+          default:
+            expr = [
+                "(",
+                assignee,
+                "[", createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), "]",
+                createAssignment(),
+                _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+                ")",
+            ].join("");
+            break;
+        }
         return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")";
       case 4:
         assignee = getVarName();
-        expr = "(" + assignee + "." + getDotKey(true) + createAssignment()
-            + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
+        switch (rng(20)) {
+          case 0:
+            expr = [
+                "([ ",
+                assignee,
+                ".", getDotKey(true),
+                " ] = [ ",
+                _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+                " ])",
+            ].join("");
+            break;
+          case 1:
+            var key1 = createObjectKey(recurmax, stmtDepth, canThrow);
+            var key2 = /^\[/.test(key1) ? createObjectKey(recurmax, stmtDepth, canThrow) : key1;
+            expr = [
+                "({ ",
+                key1, ": ", assignee,
+                ".", getDotKey(true),
+                " } = { ",
+                key2, ": ", _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+                " })",
+            ].join("");
+            break;
+          default:
+            expr = [
+                "(",
+                assignee,
+                ".", getDotKey(true),
+                createAssignment(),
+                _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+                ")",
+            ].join("");
+            break;
+        }
         return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")";
       default:
         return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
@@ -1351,7 +1591,14 @@ function patch_try_catch(orig, toplevel) {
     }
 }
 
-var minify_options = require("./options.json").map(JSON.stringify);
+var minify_options = require("./options.json");
+if (typeof sandbox.run_code("console.log([ 1 ], {} = 2);") != "string") {
+    minify_options.forEach(function(o) {
+        if (!("output" in o)) o.output = {};
+        o.output.v8 = true;
+    });
+}
+minify_options = minify_options.map(JSON.stringify);
 var original_code, original_result, errored;
 var uglify_code, uglify_result, ok;
 for (var round = 1; round <= num_iterations; round++) {