support `const` (#4190)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sun, 11 Oct 2020 17:18:57 +0000 (18:18 +0100)
committerGitHub <noreply@github.com>
Sun, 11 Oct 2020 17:18:57 +0000 (01:18 +0800)
15 files changed:
lib/ast.js
lib/compress.js
lib/output.js
lib/parse.js
lib/scope.js
test/compress/asm.js
test/compress/collapse_vars.js
test/compress/conditionals.js
test/compress/const.js [new file with mode: 0644]
test/compress/dead-code.js
test/compress/issue-1034.js
test/compress/loops.js
test/compress/pure_getters.js
test/reduce.js
test/ufuzz/index.js

index ac779cb..5ce591c 100644 (file)
@@ -203,6 +203,10 @@ var AST_Directive = DEFNODE("Directive", "value quote", {
     },
 }, AST_Statement);
 
+var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
+    $documentation: "The empty statement (empty block or simply a semicolon)"
+}, AST_Statement);
+
 function must_be_expression(node, prop) {
     if (!(node[prop] instanceof AST_Node)) throw new Error(prop + " must be AST_Node");
     if (node[prop] instanceof AST_Statement && !(node[prop] instanceof AST_Function)) {
@@ -226,35 +230,9 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
     },
 }, AST_Statement);
 
-function walk_body(node, visitor) {
-    node.body.forEach(function(node) {
-        node.walk(visitor);
-    });
-}
-
-var AST_Block = DEFNODE("Block", "body", {
-    $documentation: "A body of statements (usually braced)",
-    $propdoc: {
-        body: "[AST_Statement*] an array of statements"
-    },
-    walk: function(visitor) {
-        var node = this;
-        visitor.visit(node, function() {
-            walk_body(node, visitor);
-        });
-    },
-    _validate: function() {
-        this.body.forEach(function(node) {
-            if (!(node instanceof AST_Statement)) throw new Error("body must be AST_Statement[]");
-            if (node instanceof AST_Function) throw new Error("body cannot contain AST_Function");
-        });
-    },
-}, AST_Statement);
-
-var AST_BlockScope = DEFNODE("BlockScope", "cname enclosed functions make_def parent_scope variables", {
+var AST_BlockScope = DEFNODE("BlockScope", "enclosed functions make_def parent_scope variables", {
     $documentation: "Base class for all statements introducing a lexical scope",
     $propdoc: {
-        cname: "[integer/S] current index for mangling variables (used internally by the mangler)",
         enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
         functions: "[Object/S] like `variables`, but only lists function declarations",
         parent_scope: "[AST_Scope?/S] link to the parent scope",
@@ -278,15 +256,36 @@ var AST_BlockScope = DEFNODE("BlockScope", "cname enclosed functions make_def pa
         if (!(this.parent_scope instanceof AST_BlockScope)) throw new Error("parent_scope must be AST_BlockScope");
         if (!(this.resolve() instanceof AST_Scope)) throw new Error("must be contained within AST_Scope");
     },
-}, AST_Block);
+}, AST_Statement);
 
-var AST_BlockStatement = DEFNODE("BlockStatement", null, {
-    $documentation: "A block statement",
+function walk_body(node, visitor) {
+    node.body.forEach(function(node) {
+        node.walk(visitor);
+    });
+}
+
+var AST_Block = DEFNODE("Block", "body", {
+    $documentation: "A body of statements (usually braced)",
+    $propdoc: {
+        body: "[AST_Statement*] an array of statements"
+    },
+    walk: function(visitor) {
+        var node = this;
+        visitor.visit(node, function() {
+            walk_body(node, visitor);
+        });
+    },
+    _validate: function() {
+        this.body.forEach(function(node) {
+            if (!(node instanceof AST_Statement)) throw new Error("body must be AST_Statement[]");
+            if (node instanceof AST_Function) throw new Error("body cannot contain AST_Function");
+        });
+    },
 }, AST_BlockScope);
 
-var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
-    $documentation: "The empty statement (empty block or simply a semicolon)"
-}, AST_Statement);
+var AST_BlockStatement = DEFNODE("BlockStatement", null, {
+    $documentation: "A block statement",
+}, AST_Block);
 
 var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
     $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`",
@@ -297,7 +296,7 @@ var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
         if (!(this.body instanceof AST_Statement)) throw new Error("body must be AST_Statement");
         if (this.body instanceof AST_Function) throw new Error("body cannot be AST_Function");
     },
-}, AST_Statement);
+}, AST_BlockScope);
 
 var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
     $documentation: "Statement with a label",
@@ -451,7 +450,7 @@ var AST_Scope = DEFNODE("Scope", "uses_eval uses_with", {
         return this.uses_eval || this.uses_with;
     },
     resolve: return_this,
-}, AST_BlockScope);
+}, AST_Block);
 
 var AST_Toplevel = DEFNODE("Toplevel", "globals", {
     $documentation: "The toplevel scope",
@@ -699,7 +698,7 @@ var AST_Try = DEFNODE("Try", "bcatch bfinally", {
             if (!(this.bfinally instanceof AST_Finally)) throw new Error("bfinally must be AST_Finally");
         }
     },
-}, AST_BlockScope);
+}, AST_Block);
 
 var AST_Catch = DEFNODE("Catch", "argname", {
     $documentation: "A `catch` node; only makes sense as part of a `try` statement",
@@ -718,11 +717,11 @@ var AST_Catch = DEFNODE("Catch", "argname", {
             if (!(this.argname instanceof AST_SymbolCatch)) throw new Error("argname must be AST_SymbolCatch");
         }
     },
-}, AST_BlockScope);
+}, AST_Block);
 
 var AST_Finally = DEFNODE("Finally", null, {
     $documentation: "A `finally` node; only makes sense as part of a `try` statement"
-}, AST_BlockScope);
+}, AST_Block);
 
 /* -----[ VAR ]----- */
 
@@ -738,14 +737,30 @@ var AST_Definitions = DEFNODE("Definitions", "definitions", {
                 defn.walk(visitor);
             });
         });
-    }
+    },
+    _validate: function() {
+        if (this.definitions.length < 1) throw new Error("must have at least one definition");
+    },
 }, AST_Statement);
 
+var AST_Const = DEFNODE("Const", null, {
+    $documentation: "A `const` statement",
+    _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");
+        });
+    },
+}, AST_Definitions);
+
 var AST_Var = DEFNODE("Var", null, {
     $documentation: "A `var` statement",
     _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");
+            if (node.value != null) must_be_expression(node, "value");
         });
     },
 }, AST_Definitions);
@@ -763,10 +778,6 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
             if (node.value) node.value.walk(visitor);
         });
     },
-    _validate: function() {
-        if (!(this.name instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
-        if (this.value != null) must_be_expression(this, "value");
-    },
 });
 
 /* -----[ OTHER ]----- */
@@ -1032,12 +1043,12 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", null, {
 }, AST_ObjectProperty);
 
 var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
+    $documentation: "Base class for all symbols",
     $propdoc: {
         name: "[string] name of this symbol",
         scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)",
         thedef: "[SymbolDef/S] the definition of this symbol"
     },
-    $documentation: "Base class for all symbols",
     _validate: function() {
         if (typeof this.name != "string") throw new Error("name must be string");
     },
@@ -1051,6 +1062,10 @@ var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
     $documentation: "A declaration symbol (symbol in var, function name or argument, symbol in catch)",
 }, AST_Symbol);
 
+var AST_SymbolConst = DEFNODE("SymbolConst", null, {
+    $documentation: "Symbol defining a constant",
+}, AST_SymbolDeclaration);
+
 var AST_SymbolVar = DEFNODE("SymbolVar", null, {
     $documentation: "Symbol defining a variable",
 }, AST_SymbolDeclaration);
index fa1cc38..0493237 100644 (file)
@@ -370,7 +370,8 @@ merge(Compressor.prototype, {
             def.cross_loop = false;
             def.direct_access = false;
             def.escaped = [];
-            def.fixed = !def.scope.pinned()
+            def.fixed = !def.const_redefs
+                && !def.scope.pinned()
                 && !compressor.exposed(def)
                 && !(def.init instanceof AST_Function && def.init !== def.scope)
                 && def.init;
@@ -481,8 +482,10 @@ merge(Compressor.prototype, {
             return def.fixed instanceof AST_Defun;
         }
 
-        function safe_to_assign(tw, def) {
-            if (def.fixed === undefined) return true;
+        function safe_to_assign(tw, def, declare) {
+            if (def.fixed === undefined) return declare || all(def.orig, function(sym) {
+                return !(sym instanceof AST_SymbolConst);
+            });
             if (def.fixed === null && def.safe_ids) {
                 def.safe_ids[def.id] = false;
                 delete def.safe_ids;
@@ -654,6 +657,11 @@ merge(Compressor.prototype, {
             pop(tw);
             return true;
         });
+        def(AST_BlockScope, function(tw, descend, compressor) {
+            this.variables.each(function(def) {
+                reset_def(tw, compressor, def);
+            });
+        });
         def(AST_Call, function(tw, descend) {
             tw.find_parent(AST_Scope).may_call_this();
             var exp = this.expression;
@@ -717,7 +725,10 @@ merge(Compressor.prototype, {
             tw.in_loop = saved_loop;
             return true;
         });
-        def(AST_For, function(tw) {
+        def(AST_For, function(tw, descend, compressor) {
+            this.variables.each(function(def) {
+                reset_def(tw, compressor, def);
+            });
             if (this.init) this.init.walk(tw);
             var saved_loop = tw.in_loop;
             tw.in_loop = this;
@@ -735,7 +746,10 @@ merge(Compressor.prototype, {
             tw.in_loop = saved_loop;
             return true;
         });
-        def(AST_ForIn, function(tw) {
+        def(AST_ForIn, function(tw, descend, compressor) {
+            this.variables.each(function(def) {
+                reset_def(tw, compressor, def);
+            });
             this.object.walk(tw);
             var saved_loop = tw.in_loop;
             tw.in_loop = this;
@@ -816,7 +830,10 @@ merge(Compressor.prototype, {
             pop(tw);
             return true;
         });
-        def(AST_Switch, function(tw) {
+        def(AST_Switch, function(tw, descend, compressor) {
+            this.variables.each(function(def) {
+                reset_def(tw, compressor, def);
+            });
             this.expression.walk(tw);
             var first = true;
             this.body.forEach(function(branch) {
@@ -900,7 +917,10 @@ merge(Compressor.prototype, {
             walk_defuns(tw, this);
             return true;
         });
-        def(AST_Try, function(tw) {
+        def(AST_Try, function(tw, descend, compressor) {
+            this.variables.each(function(def) {
+                reset_def(tw, compressor, def);
+            });
             push(tw);
             walk_body(this, tw);
             pop(tw);
@@ -963,7 +983,7 @@ merge(Compressor.prototype, {
             if (!node.value) return;
             descend();
             var d = node.name.definition();
-            if (safe_to_assign(tw, d)) {
+            if (safe_to_assign(tw, d, true)) {
                 mark(tw, d);
                 tw.loop_ids[d.id] = tw.in_loop;
                 d.fixed = function() {
@@ -1667,8 +1687,6 @@ merge(Compressor.prototype, {
                     extract_candidates(expr.condition);
                     extract_candidates(expr.consequent);
                     extract_candidates(expr.alternative);
-                } else if (expr instanceof AST_Definitions) {
-                    expr.definitions.forEach(extract_candidates);
                 } else if (expr instanceof AST_Dot) {
                     extract_candidates(expr.expression);
                 } else if (expr instanceof AST_DWLoop) {
@@ -1722,6 +1740,8 @@ merge(Compressor.prototype, {
                     } else {
                         extract_candidates(expr.expression);
                     }
+                } else if (expr instanceof AST_Var) {
+                    expr.definitions.forEach(extract_candidates);
                 } else if (expr instanceof AST_VarDef) {
                     if (expr.value) {
                         var def = expr.name.definition();
@@ -1895,6 +1915,7 @@ merge(Compressor.prototype, {
             function get_lhs(expr) {
                 if (expr instanceof AST_VarDef) {
                     var def = expr.name.definition();
+                    if (def.const_redefs) return;
                     if (!member(expr.name, def.orig)) return;
                     var declared = def.orig.length - def.eliminated - (declare_only[def.name] || 0);
                     var referenced = def.references.length - def.replaced - (assignments[def.name] || 0);
@@ -2139,11 +2160,15 @@ merge(Compressor.prototype, {
             for (var i = 0; i < statements.length;) {
                 var stat = statements[i];
                 if (stat instanceof AST_BlockStatement) {
-                    CHANGED = true;
-                    eliminate_spurious_blocks(stat.body);
-                    [].splice.apply(statements, [i, 1].concat(stat.body));
-                    i += stat.body.length;
-                    continue;
+                    if (all(stat.body, function(stat) {
+                        return !(stat instanceof AST_Const);
+                    })) {
+                        CHANGED = true;
+                        eliminate_spurious_blocks(stat.body);
+                        [].splice.apply(statements, [i, 1].concat(stat.body));
+                        i += stat.body.length;
+                        continue;
+                    }
                 }
                 if (stat instanceof AST_Directive) {
                     if (member(stat.value, seen_dirs)) {
@@ -2379,12 +2404,15 @@ merge(Compressor.prototype, {
             }
 
             function as_statement_array_with_return(node, ab) {
-                var body = as_statement_array(node).slice(0, -1);
-                if (ab.value) {
-                    body.push(make_node(AST_SimpleStatement, ab.value, {
-                        body: ab.value.expression
-                    }));
-                }
+                var body = as_statement_array(node);
+                var block = body, last;
+                while ((last = block[block.length - 1]) !== ab) {
+                    block = last.body;
+                }
+                block.pop();
+                if (ab.value) body.push(make_node(AST_SimpleStatement, ab.value, {
+                    body: ab.value.expression
+                }));
                 return body;
             }
 
@@ -2430,7 +2458,7 @@ merge(Compressor.prototype, {
             statements.length = n;
             CHANGED = n != len;
             if (has_quit) has_quit.forEach(function(stat) {
-                extract_declarations_from_unreachable_code(stat, statements);
+                extract_declarations_from_unreachable_code(compressor, stat, statements);
             });
         }
 
@@ -2574,7 +2602,7 @@ merge(Compressor.prototype, {
                     if (merge_conditional_assignments(def, exprs, keep)) trimmed = true;
                     break;
                 }
-                if (join_var_assign(defn.definitions, exprs, keep)) trimmed = true;
+                if (defn instanceof AST_Var && join_var_assign(defn.definitions, exprs, keep)) trimmed = true;
             }
             return trimmed && exprs;
         }
@@ -2668,7 +2696,7 @@ merge(Compressor.prototype, {
                         CHANGED = true;
                     } else {
                         statements[++j] = stat;
-                        defs = stat;
+                        if (stat instanceof AST_Var) defs = stat;
                     }
                     continue;
                 } else if (stat instanceof AST_Exit) {
@@ -2690,7 +2718,7 @@ merge(Compressor.prototype, {
                         defs.definitions = defs.definitions.concat(stat.init.definitions);
                         stat.init = null;
                         CHANGED = true;
-                    } else if (stat.init instanceof AST_Definitions) {
+                    } else if (stat.init instanceof AST_Var) {
                         defs = stat.init;
                     }
                 } else if (stat instanceof AST_ForIn) {
@@ -2750,25 +2778,46 @@ merge(Compressor.prototype, {
         }
     }
 
-    function extract_declarations_from_unreachable_code(stat, target) {
+    function extract_declarations_from_unreachable_code(compressor, stat, target) {
         if (!(stat instanceof AST_Defun)) {
             AST_Node.warn("Dropping unreachable code [{file}:{line},{col}]", stat.start);
         }
-        stat.walk(new TreeWalker(function(node) {
+        var block;
+        stat.walk(new TreeWalker(function(node, descend) {
             if (node instanceof AST_Definitions) {
                 AST_Node.warn("Declarations in unreachable code! [{file}:{line},{col}]", node.start);
-                node.remove_initializers();
-                target.push(node);
+                node.remove_initializers(compressor);
+                push(node);
                 return true;
             }
             if (node instanceof AST_Defun) {
-                target.push(node);
+                push(node);
                 return true;
             }
-            if (node instanceof AST_Scope) {
+            if (node instanceof AST_Scope) return true;
+            if (node instanceof AST_BlockScope) {
+                var save = block;
+                block = [];
+                descend();
+                if (block.required) {
+                    target.push(make_node(AST_BlockStatement, stat, {
+                        body: block
+                    }));
+                } else if (block.length) {
+                    [].push.apply(target, block);
+                }
+                block = save;
                 return true;
             }
         }));
+        function push(node) {
+            if (block) {
+                block.push(node);
+                if (node instanceof AST_Const) block.required = true;
+            } else {
+                target.push(node);
+            }
+        }
     }
 
     function is_undefined(node, compressor) {
@@ -4038,7 +4087,9 @@ merge(Compressor.prototype, {
         });
         def(AST_SymbolDeclaration, return_false);
         def(AST_SymbolRef, function(compressor) {
-            return !this.is_declared(compressor);
+            return !(this.is_declared(compressor) && all(this.definition().orig, function(sym) {
+                return !(sym instanceof AST_SymbolConst);
+            }));
         });
         def(AST_This, return_false);
         def(AST_Try, function(compressor) {
@@ -4285,12 +4336,15 @@ merge(Compressor.prototype, {
         return self;
     });
 
-    function trim_block(stat) {
-        switch (stat.body.length) {
-          case 1: return stat.body[0];
-          case 0: return make_node(AST_EmptyStatement, stat);
+    function trim_block(node) {
+        switch (node.body.length) {
+          case 0:
+            return make_node(AST_EmptyStatement, node);
+          case 1:
+            var stat = node.body[0];
+            if (!(stat instanceof AST_Const)) return stat;
         }
-        return stat;
+        return node;
     }
 
     OPT(AST_BlockStatement, function(self, compressor) {
@@ -4386,6 +4440,16 @@ merge(Compressor.prototype, {
                 pop();
                 return true;
             }
+            if (node instanceof AST_Const) {
+                node.definitions.forEach(function(defn) {
+                    var def = defn.name.definition();
+                    references[def.id] = false;
+                    def = def.redefined();
+                    if (def) references[def.id] = false;
+                    defn.value.walk(tw);
+                });
+                return true;
+            }
             if (node instanceof AST_For) {
                 if (node.init) node.init.walk(tw);
                 push();
@@ -4633,7 +4697,7 @@ merge(Compressor.prototype, {
             if (!(sym instanceof AST_SymbolRef)) return;
             if (compressor.exposed(sym.definition())) return;
             if (!all(sym.definition().orig, function(sym) {
-                return !(sym instanceof AST_SymbolLambda);
+                return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLambda);
             })) return;
             return sym;
         };
@@ -4668,42 +4732,40 @@ merge(Compressor.prototype, {
                 });
             }
             if (node === self) return;
-            if (node instanceof AST_Defun) {
-                var node_def = node.name.definition();
-                if (!drop_funcs && scope === self) {
-                    if (!(node_def.id in in_use_ids)) {
-                        in_use_ids[node_def.id] = true;
-                        in_use.push(node_def);
+            if (scope === self) {
+                if (node instanceof AST_Defun) {
+                    var def = node.name.definition();
+                    if (!drop_funcs && !(def.id in in_use_ids)) {
+                        in_use_ids[def.id] = true;
+                        in_use.push(def);
                     }
+                    initializations.add(def.id, node);
+                    return true; // don't go in nested scopes
                 }
-                initializations.add(node_def.id, node);
-                return true; // don't go in nested scopes
-            }
-            if (node instanceof AST_SymbolFunarg && scope === self) {
-                var node_def = node.definition();
-                var_defs_by_id.add(node_def.id, node);
-                assignments.add(node_def.id, node);
-            }
-            if (node instanceof AST_Definitions && scope === self) {
-                node.definitions.forEach(function(def) {
-                    var node_def = def.name.definition();
-                    var_defs_by_id.add(node_def.id, def);
-                    if (!drop_vars) {
-                        if (!(node_def.id in in_use_ids)) {
-                            in_use_ids[node_def.id] = true;
-                            in_use.push(node_def);
+                if (node instanceof AST_Definitions) {
+                    node.definitions.forEach(function(defn) {
+                        var def = defn.name.definition();
+                        var_defs_by_id.add(def.id, defn);
+                        if (!drop_vars && !(def.id in in_use_ids)) {
+                            in_use_ids[def.id] = true;
+                            in_use.push(def);
                         }
-                    }
-                    if (def.value) {
-                        if (def.value.has_side_effects(compressor)) {
-                            def.value.walk(tw);
+                        if (!defn.value) return;
+                        if (defn.value.has_side_effects(compressor)) {
+                            defn.value.walk(tw);
                         } else {
-                            initializations.add(node_def.id, def.value);
+                            initializations.add(def.id, defn.value);
                         }
-                        assignments.add(node_def.id, def);
-                    }
-                });
-                return true;
+                        assignments.add(def.id, defn);
+                    });
+                    return true;
+                }
+                if (node instanceof AST_SymbolFunarg) {
+                    var def = node.definition();
+                    var_defs_by_id.add(def.id, node);
+                    assignments.add(def.id, node);
+                    return true;
+                }
             }
             return scan_ref_scoped(node, descend, true);
         });
@@ -4714,7 +4776,14 @@ merge(Compressor.prototype, {
         // symbols (that may not be in_use).
         tw = new TreeWalker(scan_ref_scoped);
         for (var i = 0; i < in_use.length; i++) {
-            var init = initializations.get(in_use[i].id);
+            var in_use_def = in_use[i];
+            if (in_use_def.const_redefs) in_use_def.const_redefs.forEach(function(def) {
+                if (!(def.id in in_use_ids)) {
+                    in_use_ids[def.id] = true;
+                    in_use.push(def);
+                }
+            });
+            var init = initializations.get(in_use_def.id);
             if (init) init.forEach(function(init) {
                 init.walk(tw);
             });
@@ -4915,7 +4984,8 @@ merge(Compressor.prototype, {
                             }
                             tail.push(def);
                         }
-                    } else if (sym.orig[0] instanceof AST_SymbolCatch) {
+                    } else if (sym.orig[0] instanceof AST_SymbolCatch
+                        && sym.scope.resolve() === def.name.scope.resolve()) {
                         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);
@@ -5012,7 +5082,13 @@ merge(Compressor.prototype, {
                 return node;
             }
         }, function(node, in_list) {
-            if (node instanceof AST_For) {
+            if (node instanceof AST_BlockStatement) switch (node.body.length) {
+              case 0:
+                return in_list ? List.skip : make_node(AST_EmptyStatement, node);
+              case 1:
+                var stat = node.body[0];
+                if (!(stat instanceof AST_Const)) return stat;
+            } else if (node instanceof AST_For) {
                 // Certain combination of unused name + side effect leads to invalid AST:
                 //    https://github.com/mishoo/UglifyJS/issues/44
                 //    https://github.com/mishoo/UglifyJS/issues/1838
@@ -5574,6 +5650,9 @@ merge(Compressor.prototype, {
                 })
                 && all(def.references, function(ref) {
                     return ref.fixed_value() === right;
+                })
+                && all(def.orig, function(sym) {
+                    return !(sym instanceof AST_SymbolConst);
                 });
         }
     });
@@ -5790,7 +5869,9 @@ merge(Compressor.prototype, {
             return make_sequence(this, [ expression, property ]);
         });
         def(AST_SymbolRef, function(compressor) {
-            return this.is_declared(compressor) ? null : this;
+            return this.is_declared(compressor) && all(this.definition().orig, function(sym) {
+                return !(sym instanceof AST_SymbolConst);
+            }) ? null : this;
         });
         def(AST_This, return_null);
         def(AST_Unary, function(compressor, first_in_statement) {
@@ -5848,26 +5929,24 @@ merge(Compressor.prototype, {
         if (!compressor.option("loops")) return self;
         var cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true);
         if (!(cond instanceof AST_Node)) {
-            if (cond) return make_node(AST_For, self, {
+            if (cond && !has_loop_control(self, compressor.parent(), AST_Continue)) return make_node(AST_For, self, {
                 body: make_node(AST_BlockStatement, self.body, {
                     body: [
                         self.body,
                         make_node(AST_SimpleStatement, self.condition, {
                             body: self.condition
-                        })
+                        }),
                     ]
                 })
             }).optimize(compressor);
-            if (!has_loop_control(self, compressor.parent())) {
-                return make_node(AST_BlockStatement, self.body, {
-                    body: [
-                        self.body,
-                        make_node(AST_SimpleStatement, self.condition, {
-                            body: self.condition
-                        })
-                    ]
-                }).optimize(compressor);
-            }
+            if (!has_loop_control(self, compressor.parent())) return make_node(AST_BlockStatement, self.body, {
+                body: [
+                    self.body,
+                    make_node(AST_SimpleStatement, self.condition, {
+                        body: self.condition
+                    }),
+                ]
+            }).optimize(compressor);
         }
         if (self.body instanceof AST_BlockStatement && !has_loop_control(self, compressor.parent(), AST_Continue)) {
             var body = self.body.body;
@@ -5877,6 +5956,7 @@ merge(Compressor.prototype, {
                     && !stat.alternative
                     && stat.body instanceof AST_Break
                     && compressor.loopcontrol_target(stat.body) === self) {
+                    if (has_block_scope_refs(stat.condition)) break;
                     self.condition = make_node(AST_Binary, self, {
                         operator: "&&",
                         left: stat.condition.negate(compressor),
@@ -5884,6 +5964,7 @@ merge(Compressor.prototype, {
                     });
                     body.splice(i, 1);
                 } else if (stat instanceof AST_SimpleStatement) {
+                    if (has_block_scope_refs(stat.body)) break;
                     self.condition = make_sequence(self, [
                         stat.body,
                         self.condition,
@@ -5904,6 +5985,18 @@ merge(Compressor.prototype, {
             body: make_node(AST_EmptyStatement, self)
         }).optimize(compressor);
         return self;
+
+        function has_block_scope_refs(node) {
+            var found = false;
+            node.walk(new TreeWalker(function(node) {
+                if (found) return true;
+                if (node instanceof AST_SymbolRef) {
+                    if (!member(node.definition(), self.enclosed)) found = true;
+                    return true;
+                }
+            }));
+            return found;
+        }
     });
 
     function if_break_in_loop(self, compressor) {
@@ -5934,7 +6027,7 @@ merge(Compressor.prototype, {
             } else if (retain) {
                 body.push(first);
             }
-            extract_declarations_from_unreachable_code(self.body, body);
+            extract_declarations_from_unreachable_code(compressor, self.body, body);
             return make_node(AST_BlockStatement, self, {
                 body: body
             });
@@ -5952,7 +6045,7 @@ merge(Compressor.prototype, {
                     self.condition = first.condition.negate(compressor);
                 }
                 var body = as_statement_array(first.alternative);
-                extract_declarations_from_unreachable_code(first.body, body);
+                extract_declarations_from_unreachable_code(compressor, first.body, body);
                 return drop_it(body);
             }
             ab = first_statement(first.alternative);
@@ -5967,7 +6060,7 @@ merge(Compressor.prototype, {
                     self.condition = first.condition;
                 }
                 var body = as_statement_array(first.body);
-                extract_declarations_from_unreachable_code(first.alternative, body);
+                extract_declarations_from_unreachable_code(compressor, first.alternative, body);
                 return drop_it(body);
             }
         }
@@ -6015,7 +6108,6 @@ merge(Compressor.prototype, {
             if (!cond) {
                 if (compressor.option("dead_code")) {
                     var body = [];
-                    extract_declarations_from_unreachable_code(self.body, body);
                     if (self.init instanceof AST_Statement) {
                         body.push(self.init);
                     } else if (self.init) {
@@ -6026,6 +6118,7 @@ merge(Compressor.prototype, {
                     body.push(make_node(AST_SimpleStatement, self.condition, {
                         body: self.condition
                     }));
+                    extract_declarations_from_unreachable_code(compressor, self.body, body);
                     return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
                 }
             } else if (self.condition && !(cond instanceof AST_Node)) {
@@ -6121,21 +6214,23 @@ merge(Compressor.prototype, {
             }
             if (!cond) {
                 AST_Node.warn("Condition always false [{file}:{line},{col}]", self.condition.start);
-                var body = [];
-                extract_declarations_from_unreachable_code(self.body, body);
-                body.push(make_node(AST_SimpleStatement, self.condition, {
-                    body: self.condition
-                }));
+                var body = [
+                    make_node(AST_SimpleStatement, self.condition, {
+                        body: self.condition
+                    }),
+                ];
+                extract_declarations_from_unreachable_code(compressor, self.body, body);
                 if (self.alternative) body.push(self.alternative);
                 return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
             } else if (!(cond instanceof AST_Node)) {
                 AST_Node.warn("Condition always true [{file}:{line},{col}]", self.condition.start);
-                var body = [];
-                if (self.alternative) extract_declarations_from_unreachable_code(self.alternative, body);
-                body.push(make_node(AST_SimpleStatement, self.condition, {
-                    body: self.condition
-                }));
-                body.push(self.body);
+                var body = [
+                    make_node(AST_SimpleStatement, self.condition, {
+                        body: self.condition
+                    }),
+                    self.body,
+                ];
+                if (self.alternative) extract_declarations_from_unreachable_code(compressor, self.alternative, body);
                 return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
             }
         }
@@ -6459,7 +6554,7 @@ merge(Compressor.prototype, {
             if (prev && !aborts(prev)) {
                 prev.body = prev.body.concat(branch.body);
             } else {
-                extract_declarations_from_unreachable_code(branch, decl);
+                extract_declarations_from_unreachable_code(compressor, branch, decl);
             }
         }
     });
@@ -6470,9 +6565,9 @@ merge(Compressor.prototype, {
             if (has_declarations_only(self)) {
                 var body = [];
                 if (self.bcatch) {
-                    extract_declarations_from_unreachable_code(self.bcatch, body);
+                    extract_declarations_from_unreachable_code(compressor, self.bcatch, body);
                     body.forEach(function(stat) {
-                        if (!(stat instanceof AST_Definitions)) return;
+                        if (!(stat instanceof AST_Var)) return;
                         stat.definitions.forEach(function(var_def) {
                             var def = var_def.name.definition().redefined();
                             if (!def) return;
@@ -6499,7 +6594,13 @@ merge(Compressor.prototype, {
         return self;
     });
 
-    AST_Definitions.DEFMETHOD("remove_initializers", function() {
+    AST_Const.DEFMETHOD("remove_initializers", function(compressor) {
+        this.definitions.forEach(function(def) {
+            def.value = make_node(AST_Undefined, def).optimize(compressor);
+        });
+    });
+
+    AST_Var.DEFMETHOD("remove_initializers", function() {
         this.definitions.forEach(function(def) {
             def.value = null;
         });
@@ -6526,8 +6627,31 @@ merge(Compressor.prototype, {
         return make_sequence(this, assignments);
     });
 
-    OPT(AST_Definitions, function(self, compressor) {
-        return self.definitions.length ? self : make_node(AST_EmptyStatement, self);
+    OPT(AST_Const, function(self, compressor) {
+        return all(self.definitions, function(defn) {
+            var node = defn.name;
+            if (!node.fixed_value()) return false;
+            var def = node.definition();
+            var scope = def.scope.resolve();
+            if (scope instanceof AST_Toplevel) {
+                if (!compressor.toplevel.vars) return false;
+                if (def.scope === scope) return true;
+                return !scope.variables.has(node.name) && !scope.globals.has(node.name);
+            }
+            return def.scope === scope || !scope.find_variable(node);
+        }) ? 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,
+                    value: defn.value
+                });
+            })
+        }) : self;
     });
 
     function lift_sequence_in_expression(node, compressor) {
index d99c61b..05baa23 100644 (file)
@@ -835,10 +835,6 @@ function OutputStream(options) {
         use_asm = was_asm;
     }
 
-    AST_StatementWithBody.DEFMETHOD("_do_print_body", function(output) {
-        force_statement(this.body, output);
-    });
-
     DEFPRINT(AST_Statement, function(output) {
         this.body.print(output);
         output.semicolon();
@@ -897,7 +893,7 @@ function OutputStream(options) {
             self.condition.print(output);
         });
         output.space();
-        self._do_print_body(output);
+        force_statement(self.body, output);
     });
     DEFPRINT(AST_For, function(output) {
         var self = this;
@@ -927,7 +923,7 @@ function OutputStream(options) {
             }
         });
         output.space();
-        self._do_print_body(output);
+        force_statement(self.body, output);
     });
     DEFPRINT(AST_ForIn, function(output) {
         var self = this;
@@ -941,7 +937,7 @@ function OutputStream(options) {
             self.object.print(output);
         });
         output.space();
-        self._do_print_body(output);
+        force_statement(self.body, output);
     });
     DEFPRINT(AST_With, function(output) {
         var self = this;
@@ -951,7 +947,7 @@ function OutputStream(options) {
             self.expression.print(output);
         });
         output.space();
-        self._do_print_body(output);
+        force_statement(self.body, output);
     });
 
     /* -----[ functions ]----- */
@@ -1036,7 +1032,7 @@ function OutputStream(options) {
             else
                 force_statement(self.alternative, output);
         } else {
-            self._do_print_body(output);
+            force_statement(self.body, output);
         }
     });
 
@@ -1114,17 +1110,21 @@ function OutputStream(options) {
         print_braced(this, output);
     });
 
-    DEFPRINT(AST_Var, function(output) {
-        var self = this;
-        output.print("var");
-        output.space();
-        self.definitions.forEach(function(def, i) {
-            if (i) output.comma();
-            def.print(output);
-        });
-        var p = output.parent();
-        if (p && p.init !== self || !(p instanceof AST_For || p instanceof AST_ForIn)) output.semicolon();
-    });
+    function print_definitinos(type) {
+        return function(output) {
+            var self = this;
+            output.print(type);
+            output.space();
+            self.definitions.forEach(function(def, i) {
+                if (i) output.comma();
+                def.print(output);
+            });
+            var p = output.parent();
+            if (p && p.init !== self || !(p instanceof AST_For || p instanceof AST_ForIn)) output.semicolon();
+        };
+    }
+    DEFPRINT(AST_Const, print_definitinos("const"));
+    DEFPRINT(AST_Var, print_definitinos("var"));
 
     function parenthesize_for_noin(node, output, noin) {
         var parens = false;
@@ -1383,11 +1383,12 @@ function OutputStream(options) {
     function force_statement(stat, output) {
         if (output.option("braces")) {
             make_block(stat, output);
+        } else if (!stat || stat instanceof AST_EmptyStatement) {
+            output.force_semicolon();
+        } else if (stat instanceof AST_Const) {
+            make_block(stat, output);
         } else {
-            if (!stat || stat instanceof AST_EmptyStatement)
-                output.force_semicolon();
-            else
-                stat.print(output);
+            stat.print(output);
         }
     }
 
index f24be3c..42914d7 100644 (file)
@@ -832,6 +832,12 @@ function parse($TEXT, options) {
                 next();
                 return break_cont(AST_Break);
 
+              case "const":
+                next();
+                var node = const_();
+                semicolon();
+                return node;
+
               case "continue":
                 next();
                 return break_cont(AST_Continue);
@@ -988,7 +994,9 @@ function parse($TEXT, options) {
         expect("(");
         var init = null;
         if (!is("punc", ";")) {
-            init = is("keyword", "var")
+            init = is("keyword", "const")
+                ? (next(), const_(true))
+                : is("keyword", "var")
                 ? (next(), var_(true))
                 : expression(true, true);
             if (is("operator", "in")) {
@@ -1161,13 +1169,22 @@ function parse($TEXT, options) {
         });
     }
 
-    function vardefs(no_in) {
+    function vardefs(type, no_in, must_init) {
         var a = [];
         for (;;) {
+            var start = S.token;
+            var name = as_symbol(type);
+            var value = null;
+            if (is("operator", "=")) {
+                next();
+                value = expression(false, no_in);
+            } else if (must_init) {
+                croak("Missing initializer in declaration");
+            }
             a.push(new AST_VarDef({
-                start : S.token,
-                name  : as_symbol(AST_SymbolVar),
-                value : is("operator", "=") ? (next(), expression(false, no_in)) : null,
+                start : start,
+                name  : name,
+                value : value,
                 end   : prev()
             }));
             if (!is("punc", ","))
@@ -1177,10 +1194,18 @@ function parse($TEXT, options) {
         return a;
     }
 
+    var const_ = function(no_in) {
+        return new AST_Const({
+            start       : prev(),
+            definitions : vardefs(AST_SymbolConst, no_in, true),
+            end         : prev()
+        });
+    };
+
     var var_ = function(no_in) {
         return new AST_Var({
             start       : prev(),
-            definitions : vardefs(no_in),
+            definitions : vardefs(AST_SymbolVar, no_in),
             end         : prev()
         });
     };
index ec2c1f6..fa55864 100644 (file)
@@ -80,7 +80,12 @@ SymbolDef.prototype = {
         }
     },
     redefined: function() {
-        return this.defun && this.defun.variables.get(this.name);
+        var scope = this.defun;
+        if (!scope) return;
+        var def = scope.variables.get(this.name);
+        if (!def && scope instanceof AST_Toplevel) def = scope.globals.get(this.name);
+        if (def === this) return;
+        return def;
     },
     unmangleable: function(options) {
         return this.global && !options.toplevel
@@ -114,6 +119,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
             });
             return true;
         }
+        if (node instanceof AST_SwitchBranch) {
+            node.init_vars(scope);
+            descend();
+            return true;
+        }
         if (node instanceof AST_Try) {
             walk_scope(function() {
                 walk_body(node, tw);
@@ -122,10 +132,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
             if (node.bfinally) node.bfinally.walk(tw);
             return true;
         }
-        if (node instanceof AST_BlockScope) {
-            walk_scope(descend);
-            return true;
-        }
         if (node instanceof AST_With) {
             var s = scope;
             do {
@@ -133,7 +139,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
                 if (s.uses_with) break;
                 s.uses_with = true;
             } while (s = s.parent_scope);
-            return;
+            walk_scope(descend);
+            return true;
+        }
+        if (node instanceof AST_BlockScope) {
+            walk_scope(descend);
+            return true;
         }
         if (node instanceof AST_Symbol) {
             node.scope = scope;
@@ -144,6 +155,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
         }
         if (node instanceof AST_SymbolCatch) {
             scope.def_variable(node).defun = defun;
+        } else if (node instanceof AST_SymbolConst) {
+            scope.def_variable(node).defun = defun;
         } else if (node instanceof AST_SymbolDefun) {
             defun.def_function(node, tw.parent());
             entangle(defun, scope);
@@ -216,7 +229,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
             node.reference(options);
             return true;
         }
-        // ensure mangling works if catch reuses a scope variable
+        // ensure mangling works if `catch` reuses a scope variable
         if (node instanceof AST_SymbolCatch) {
             var def = node.definition().redefined();
             if (def) for (var s = node.scope; s; s = s.parent_scope) {
@@ -225,6 +238,16 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
             }
             return true;
         }
+        // ensure compression works if `const` reuses a scope variable
+        if (node instanceof AST_SymbolConst) {
+            var def = node.definition();
+            var redef = def.redefined();
+            if (redef) {
+                if (!redef.const_redefs) redef.const_redefs = [];
+                redef.const_redefs.push(def);
+            }
+            return true;
+        }
     });
     self.walk(tw);
 
@@ -290,7 +313,6 @@ AST_Toplevel.DEFMETHOD("def_global", function(node) {
 });
 
 function init_block_vars(scope, parent) {
-    scope.cname = -1;                               // the current index for mangling functions/variables
     scope.enclosed = [];                            // variables from this or outer scope(s) that are referenced from this or inner scopes
     scope.parent_scope = parent;                    // the parent scope (null if this is the top level)
     scope.functions = new Dictionary();             // map name to AST_SymbolDefun (functions defined in this scope)
@@ -368,8 +390,9 @@ AST_BlockScope.DEFMETHOD("def_variable", function(symbol, init) {
 function names_in_use(scope, options) {
     var names = scope.names_in_use;
     if (!names) {
-        scope.names_in_use = names = Object.create(null);
+        scope.cname = -1;
         scope.cname_holes = [];
+        scope.names_in_use = names = Object.create(null);
         var cache = options.cache && options.cache.props;
         scope.enclosed.forEach(function(def) {
             if (def.unmangleable(options)) names[def.name] = true;
@@ -467,7 +490,11 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
             lname = save_nesting;
             return true;
         }
-        if (node instanceof AST_Scope) {
+        if (node instanceof AST_BlockScope) {
+            var to_mangle = [];
+            node.variables.each(function(def) {
+                if (!defer_redef(def)) to_mangle.push(def);
+            });
             descend();
             if (options.cache && node instanceof AST_Toplevel) {
                 node.globals.each(mangle);
@@ -477,9 +504,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
                 sym.scope = node;
                 sym.reference(options);
             }
-            node.variables.each(function(def) {
-                if (!defer_redef(def)) mangle(def);
-            });
+            to_mangle.forEach(mangle);
             return true;
         }
         if (node instanceof AST_Label) {
@@ -490,13 +515,6 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
             node.mangled_name = name;
             return true;
         }
-        if (!options.ie8 && node instanceof AST_Catch && node.argname) {
-            var def = node.argname.definition();
-            var redef = defer_redef(def, node.argname);
-            descend();
-            if (!redef) mangle(def);
-            return true;
-        }
     });
     this.walk(tw);
     redefined.forEach(mangle);
@@ -511,7 +529,8 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
         if (!redef) return false;
         redefined.push(def);
         def.references.forEach(reference);
-        if (node) reference(node);
+        var node = def.orig[0];
+        if (node instanceof AST_SymbolCatch || node instanceof AST_SymbolConst) reference(node);
         return true;
 
         function reference(sym) {
index 65d20b9..331eb42 100644 (file)
@@ -76,9 +76,8 @@ asm_mixed: {
                 start = start | 0;
                 end = end | 0;
                 var sum = 0.0, p = 0, q = 0;
-                for (p = start << 3, q = end << 3; (p | 0) < (q | 0); p = p + 8 | 0) {
+                for (p = start << 3, q = end << 3; (p | 0) < (q | 0); p = p + 8 | 0)
                     sum = sum + +log(values[p >> 3]);
-                }
                 return +sum;
             }
             function geometricMean(start, end) {
@@ -91,7 +90,8 @@ asm_mixed: {
         function no_asm_GeometricMean(stdlib, foreign, buffer) {
             function logSum(start, end) {
                 start |= 0, end |= 0;
-                for (var sum = 0, p = 0, q = 0, p = start << 3, q = end << 3; (0 | p) < (0 | q); p = p + 8 | 0) sum += +log(values[p >> 3]);
+                for (var sum = 0, p = 0, q = 0, p = start << 3, q = end << 3; (0 | p) < (0 | q); p = p + 8 | 0)
+                    sum += +log(values[p >> 3]);
                 return +sum;
             }
             function geometricMean(start, end) {
index 24fadce..4c3a6ed 100644 (file)
@@ -346,9 +346,8 @@ collapse_vars_if: {
             return "x" != "Bar" + x / 4 ? g9 : g5;
         }
         function f3(x) {
-            if (x) {
+            if (x)
                 return 1;
-            }
             return 2;
         }
     }
@@ -4192,9 +4191,8 @@ issue_2436_11: {
             if (isCollection(arg1)) {
                 var size = arg1, max = arg2, min = 0, res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt);
                 return size && true === size.isMatrix ? matrix(res) : res;
-            } else {
+            } else
                 return _randomInt(min = arg1, max = arg2);
-            }
         }
     }
 }
@@ -4310,9 +4308,8 @@ issue_2497: {
         function sample() {
             if (true)
                 for (var i = 0; i < 1; ++i)
-                    for (var k = 0; k < 1; ++k) {
+                    for (var k = 0; k < 1; ++k)
                         value = (value = 1) ? value + 1 : 0;
-                    }
             else
                 for (i = 0; i < 1; ++i)
                     for (k = 0; k < 1; ++k)
index 5dfd17b..8a4e3aa 100644 (file)
@@ -55,14 +55,15 @@ ifs_3_should_warn: {
     }
     input: {
         var x, y;
-        if (x && !(x + "1") && y) { // 1
+        // 1
+        if (x && !(x + "1") && y) {
             var qq;
             foo();
         } else {
             bar();
         }
-
-        if (x || !!(x + "1") || y) { // 2
+        // 2
+        if (x || !!(x + "1") || y) {
             foo();
         } else {
             var jj;
@@ -71,9 +72,27 @@ ifs_3_should_warn: {
     }
     expect: {
         var x, y;
-        var qq; bar();          // 1
-        var jj; foo();          // 2
-    }
+        // 1
+        var qq; bar();
+        // 2
+        foo(); var jj;
+    }
+    expect_warnings: [
+        "WARN: + in boolean context always true [test/compress/conditionals.js:3,18]",
+        "WARN: Boolean && always false [test/compress/conditionals.js:3,12]",
+        "WARN: Condition left of && always false [test/compress/conditionals.js:3,12]",
+        "WARN: Condition always false [test/compress/conditionals.js:3,12]",
+        "WARN: Dropping unreachable code [test/compress/conditionals.js:3,34]",
+        "WARN: Declarations in unreachable code! [test/compress/conditionals.js:4,12]",
+        "WARN: + in boolean context always true [test/compress/conditionals.js:10,19]",
+        "WARN: Boolean || always true [test/compress/conditionals.js:10,12]",
+        "WARN: Condition left of || always true [test/compress/conditionals.js:10,12]",
+        "WARN: Condition always true [test/compress/conditionals.js:10,12]",
+        "WARN: Dropping unreachable code [test/compress/conditionals.js:12,15]",
+        "WARN: Declarations in unreachable code! [test/compress/conditionals.js:13,12]",
+        "WARN: Dropping side-effect-free statement [test/compress/conditionals.js:3,12]",
+        "WARN: Dropping side-effect-free statement [test/compress/conditionals.js:10,12]",
+    ]
 }
 
 ifs_4: {
diff --git a/test/compress/const.js b/test/compress/const.js
new file mode 100644 (file)
index 0000000..265ca9f
--- /dev/null
@@ -0,0 +1,667 @@
+mangle_catch_1: {
+    mangle = {}
+    input: {
+        try {
+            throw "eeeee";
+        } catch (c) {
+            const e = typeof d;
+        }
+        console.log(typeof a, typeof b);
+    }
+    expect: {
+        try {
+            throw "eeeee";
+        } catch (e) {
+            const o = typeof d;
+        }
+        console.log(typeof a, typeof b);
+    }
+    expect_stdout: "undefined undefined"
+}
+
+mangle_catch_2: {
+    mangle = {}
+    input: {
+        console.log(function f() {
+            try {} catch (e) {
+                const f = 0;
+            }
+        }());
+    }
+    expect: {
+        console.log(function o() {
+            try {} catch (c) {
+                const o = 0;
+            }
+        }());
+    }
+    expect_stdout: "undefined"
+}
+
+retain_block: {
+    options = {}
+    input: {
+        {
+            const a = "FAIL";
+        }
+        var a = "PASS";
+        console.log(a);
+    }
+    expect: {
+        {
+            const a = "FAIL";
+        }
+        var a = "PASS";
+        console.log(a);
+    }
+    expect_stdout: true
+}
+
+if_dead_branch: {
+    options = {
+        conditionals: true,
+        dead_code: true,
+        evaluate: true,
+    }
+    input: {
+        console.log(function() {
+            if (0) {
+                const a = 0;
+            }
+            return typeof a;
+        }());
+    }
+    expect: {
+        console.log(function() {
+            0;
+            {
+                const a = void 0;
+            }
+            return typeof a;
+        }());
+    }
+    expect_stdout: "undefined"
+}
+
+merge_vars_1: {
+    options = {
+        merge_vars: true,
+        toplevel: true,
+    }
+    input: {
+        const a = console;
+        console.log(typeof a);
+        var b = typeof a;
+        console.log(b);
+    }
+    expect: {
+        const a = console;
+        console.log(typeof a);
+        var b = typeof a;
+        console.log(b);
+    }
+    expect_stdout: [
+        "object",
+        "object",
+    ]
+}
+
+merge_vars_2: {
+    options = {
+        inline: true,
+        merge_vars: true,
+        toplevel: true,
+    }
+    input: {
+        var a = 0;
+        (function() {
+            var b = function f() {
+                const c = a && f;
+                c.var += 0;
+            }();
+            console.log(b);
+        })(1 && --a);
+    }
+    expect: {
+        var a = 0;
+        1 && --a,
+        b = function f() {
+            const c = a && f;
+            c.var += 0;
+        }(),
+        void console.log(b);
+        var b;
+    }
+    expect_stdout: "undefined"
+}
+
+merge_vars_3: {
+    options = {
+        merge_vars: true,
+        toplevel: true,
+    }
+    input: {
+        {
+            const a = 0;
+            var b = console;
+            console.log(typeof b);
+        }
+        var a = 1;
+        console.log(typeof a);
+    }
+    expect: {
+        {
+            const a = 0;
+            var b = console;
+            console.log(typeof b);
+        }
+        var a = 1;
+        console.log(typeof a);
+    }
+    expect_stdout: true
+}
+
+reduce_merge_vars: {
+    options = {
+        merge_vars: true,
+        reduce_vars: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        const a = console;
+        console.log(typeof a);
+        var b = typeof a;
+        console.log(b);
+    }
+    expect: {
+        var b = console;
+        console.log(typeof b);
+        b = typeof b;
+        console.log(b);
+    }
+    expect_stdout: [
+        "object",
+        "object",
+    ]
+}
+
+use_before_init_1: {
+    options = {
+        reduce_vars: true,
+        toplevel: true,
+    }
+    input: {
+        a = "foo";
+        const a = "bar";
+    }
+    expect: {
+        a = "foo";
+        const a = "bar";
+    }
+    expect_stdout: true
+}
+
+use_before_init_2: {
+    options = {
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        try {
+            a = "foo";
+        } catch (e) {
+            console.log("PASS");
+        }
+        const a = "bar";
+    }
+    expect: {
+        try {
+            a = "foo";
+        } catch (e) {
+            console.log("PASS");
+        }
+        const a = "bar";
+    }
+    expect_stdout: true
+}
+
+use_before_init_3: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        try {
+            a;
+        } catch (e) {
+            console.log("PASS");
+        }
+        const a = 42;
+    }
+    expect: {
+        try {
+            a;
+        } catch (e) {
+            console.log("PASS");
+        }
+        const a = 42;
+    }
+    expect_stdout: true
+}
+
+use_before_init_4: {
+    options = {
+        reduce_vars: true,
+    }
+    input: {
+        try {
+            console.log(a);
+        } catch (e) {
+            console.log("PASS");
+        }
+        const a = "FAIL";
+    }
+    expect: {
+        try {
+            console.log(a);
+        } catch (e) {
+            console.log("PASS");
+        }
+        const a = "FAIL";
+    }
+    expect_stdout: true
+}
+
+collapse_block: {
+    options = {
+        collapse_vars: true,
+        pure_getters: "strict",
+        unsafe: true,
+    }
+    input: {
+        {
+            const a = typeof console;
+            console.log(a);
+        }
+    }
+    expect: {
+        {
+            const a = typeof console;
+            console.log(a);
+        }
+    }
+    expect_stdout: "object"
+}
+
+reduce_block_1: {
+    options = {
+        reduce_vars: true,
+    }
+    input: {
+        {
+            const a = typeof console;
+            console.log(a);
+        }
+    }
+    expect: {
+        {
+            const a = typeof console;
+            console.log(a);
+        }
+    }
+    expect_stdout: "object"
+}
+
+reduce_block_1_toplevel: {
+    options = {
+        reduce_vars: true,
+        toplevel: true,
+    }
+    input: {
+        {
+            const a = typeof console;
+            console.log(a);
+        }
+    }
+    expect: {
+        var a = typeof console;
+        console.log(a);
+    }
+    expect_stdout: "object"
+}
+
+reduce_block_2: {
+    options = {
+        reduce_vars: true,
+    }
+    input: {
+        {
+            const a = typeof console;
+            console.log(a);
+        }
+        console.log(typeof a);
+    }
+    expect: {
+        {
+            const a = typeof console;
+            console.log(a);
+        }
+        console.log(typeof a);
+    }
+    expect_stdout: true
+}
+
+reduce_block_2_toplevel: {
+    options = {
+        reduce_vars: true,
+        toplevel: true,
+    }
+    input: {
+        {
+            const a = typeof console;
+            console.log(a);
+        }
+        console.log(typeof a);
+    }
+    expect: {
+        {
+            const a = typeof console;
+            console.log(a);
+        }
+        console.log(typeof a);
+    }
+    expect_stdout: true
+}
+
+hoist_props_1: {
+    options = {
+        hoist_props: true,
+        reduce_vars: true,
+    }
+    input: {
+        {
+            const o = {
+                p: "PASS",
+            };
+            console.log(o.p);
+        }
+    }
+    expect: {
+        {
+            const o = {
+                p: "PASS",
+            };
+            console.log(o.p);
+        }
+    }
+    expect_stdout: "PASS"
+}
+
+hoist_props_2: {
+    options = {
+        hoist_props: true,
+        passes: 2,
+        reduce_vars: true,
+        toplevel: true,
+    }
+    input: {
+        {
+            const o = {
+                p: "PASS",
+            };
+            console.log(o.p);
+        }
+    }
+    expect: {
+        var o_p = "PASS";
+        console.log(o_p);
+    }
+    expect_stdout: "PASS"
+}
+
+loop_block_1: {
+    options = {
+        loops: true,
+    }
+    input: {
+        do {
+            const o = console;
+            console.log(typeof o.log);
+        } while (!console);
+    }
+    expect: {
+        do {
+            const o = console;
+            console.log(typeof o.log);
+        } while (!console);
+    }
+    expect_stdout: "function"
+}
+
+loop_block_2: {
+    options = {
+        loops: true,
+    }
+    input: {
+        do {
+            const o = {};
+            (function() {
+                console.log(typeof this, o.p++);
+            })();
+        } while (!console);
+    }
+    expect: {
+        do {
+            const o = {};
+            (function() {
+                console.log(typeof this, o.p++);
+            })();
+        } while (!console);
+    }
+    expect_stdout: "object NaN"
+}
+
+do_continue: {
+    options = {
+        loops: true,
+    }
+    input: {
+        try {
+            do {
+                {
+                    const a = 0;
+                    continue;
+                }
+            } while ([ A ]);
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        try {
+            do {
+                const a = 0;
+                continue;
+            } while ([ A ]);
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+}
+
+catch_ie8_1: {
+    options = {
+        ie8: true,
+        unused: true,
+    }
+    input: {
+        try {} catch (a) {}
+        console.log(function a() {
+            const a = 0;
+        }());
+    }
+    expect: {
+        try {} catch (a) {}
+        console.log(function a() {
+        }());
+    }
+    expect_stdout: "undefined"
+}
+
+catch_ie8_2: {
+    options = {
+        dead_code: true,
+        ie8: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        try {} catch (a) {
+            const b = 0;
+        }
+        try {} catch (b) {}
+        console.log(function() {
+            return this;
+        }().b);
+    }
+    expect: {
+        console.log(function() {
+            return this;
+        }().b);
+    }
+    expect_stdout: "undefined"
+}
+
+dead_block_after_return: {
+    options = {
+        dead_code: true,
+    }
+    input: {
+        (function(a) {
+            console.log(a);
+            return;
+            {
+                const a = 0;
+            }
+        })();
+    }
+    expect: {
+        (function(a) {
+            console.log(a);
+            return;
+            {
+                const a = void 0;
+            }
+        })();
+    }
+    expect_stdout: true
+}
+
+const_to_var_scope_adjustment: {
+    options = {
+        conditionals: true,
+        inline: true,
+        reduce_vars: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        for (var k in [ 42 ])
+            console.log(function f() {
+                if (k) {
+                    const a = 0;
+                }
+            }());
+    }
+    expect: {
+        for (var k in [ 42 ])
+            console.log(void (k && 0));
+    }
+    expect_stdout: "undefined"
+}
+
+do_if_continue_1: {
+    options = {
+        if_return: true,
+    }
+    input: {
+        do {
+            if (console) {
+                console.log("PASS");
+                {
+                    const a = 0;
+                    var b;
+                    continue;
+                }
+            }
+        } while (b);
+    }
+    expect: {
+        do {
+            if (!console);
+            else {
+                console.log("PASS");
+                {
+                    const a = 0;
+                    var b;
+                }
+            }
+        } while (b);
+    }
+    expect_stdout: "PASS"
+}
+
+do_if_continue_2: {
+    options = {
+        if_return: true,
+    }
+    input: {
+        do {
+            if (console) {
+                console.log("PASS");
+                {
+                    const a = 0;
+                    A = 0;
+                    continue;
+                }
+            }
+        } while (A);
+    }
+    expect: {
+        do {
+            if (!console);
+            else {
+                console.log("PASS");
+                {
+                    const a = 0;
+                    A = 0;
+                }
+            }
+        } while (A);
+    }
+    expect_stdout: "PASS"
+}
+
+drop_unused: {
+    options = {
+        evaluate: true,
+        side_effects: true,
+        unused: true,
+    }
+    input: {
+        function f(a) {
+            const b = a, c = b;
+            0 && c.p++;
+        }
+        console.log(f());
+    }
+    expect: {
+        function f(a) {
+            const b = a;
+            b;
+        }
+        console.log(f());
+    }
+    expect_stdout: "undefined"
+}
index 89829b6..3a5a452 100644 (file)
@@ -59,6 +59,11 @@ dead_code_2_should_warn: {
         f();
     }
     expect_stdout: true
+    expect_warnings: [
+        "WARN: Dropping unreachable code [test/compress/dead-code.js:8,12]",
+        "WARN: Declarations in unreachable code! [test/compress/dead-code.js:10,16]",
+        "WARN: Dropping unreachable code [test/compress/dead-code.js:10,16]",
+    ]
     node_version: "<=4"
 }
 
@@ -89,11 +94,23 @@ dead_code_constant_boolean_should_warn_more: {
         function bar() {}
         // nothing for the while
         // as for the for, it should keep:
-        var moo;
         var x = 10, y;
+        var moo;
         bar();
     }
     expect_stdout: true
+    expect_warnings: [
+        "WARN: + in boolean context always true [test/compress/dead-code.js:1,33]",
+        "WARN: Boolean || always true [test/compress/dead-code.js:1,16]",
+        "WARN: Dropping unreachable code [test/compress/dead-code.js:1,45]",
+        "WARN: Declarations in unreachable code! [test/compress/dead-code.js:3,12]",
+        "WARN: Boolean expression always true [test/compress/dead-code.js:6,47]",
+        "WARN: Boolean && always false [test/compress/dead-code.js:6,28]",
+        "WARN: Dropping unreachable code [test/compress/dead-code.js:6,63]",
+        "WARN: Declarations in unreachable code! [test/compress/dead-code.js:9,12]",
+        "WARN: Dropping side-effect-free statement [test/compress/dead-code.js:1,15]",
+        "WARN: Dropping side-effect-free statement [test/compress/dead-code.js:6,28]",
+    ]
     node_version: "<=4"
 }
 
index 137aed8..db488d0 100644 (file)
@@ -90,13 +90,13 @@ non_hoisted_function_after_return_2a: {
         "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:7,16]",
         "WARN: Dropping unused variable a [test/compress/issue-1034.js:4,20]",
         "WARN: Dropping unused function nope [test/compress/issue-1034.js:11,21]",
-        "INFO: pass 0: last_count: Infinity, count: 36",
+        "INFO: pass 0: last_count: Infinity, count: 35",
         "WARN: Dropping unreachable code [test/compress/issue-1034.js:9,12]",
         "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:9,12]",
         "WARN: Dropping unreachable code [test/compress/issue-1034.js:12,12]",
         "INFO: Dropping unused variable b [test/compress/issue-1034.js:7,20]",
         "INFO: Dropping unused variable c [test/compress/issue-1034.js:9,16]",
-        "INFO: pass 1: last_count: 36, count: 18",
+        "INFO: pass 1: last_count: 35, count: 18",
     ]
 }
 
@@ -248,13 +248,13 @@ non_hoisted_function_after_return_2a_strict: {
         "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:8,16]",
         "WARN: Dropping unused variable a [test/compress/issue-1034.js:5,20]",
         "WARN: Dropping unused function nope [test/compress/issue-1034.js:12,21]",
-        "INFO: pass 0: last_count: Infinity, count: 47",
+        "INFO: pass 0: last_count: Infinity, count: 46",
         "WARN: Dropping unreachable code [test/compress/issue-1034.js:10,12]",
         "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:10,12]",
         "WARN: Dropping unreachable code [test/compress/issue-1034.js:13,12]",
         "INFO: Dropping unused variable b [test/compress/issue-1034.js:8,20]",
         "INFO: Dropping unused variable c [test/compress/issue-1034.js:10,16]",
-        "INFO: pass 1: last_count: 47, count: 29",
+        "INFO: pass 1: last_count: 46, count: 29",
     ]
 }
 
index 1fa3d00..cfabe6f 100644 (file)
@@ -547,8 +547,8 @@ dead_code_condition: {
         console.log(a);
     }
     expect: {
-        var c;
         var a = 0, b = 5;
+        var c;
         a += 1, 0,
         console.log(a);
     }
@@ -1197,3 +1197,28 @@ issue_4182_2: {
     }
     expect_stdout: "PASS"
 }
+
+do_continue: {
+    options = {
+        loops: true,
+    }
+    input: {
+        try {
+            do {
+                continue;
+            } while ([ A ]);
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        try {
+            do {
+                continue;
+            } while ([ A ]);
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+}
index 9764f0f..25efd7f 100644 (file)
@@ -848,9 +848,8 @@ collapse_vars_1_true: {
     }
     expect: {
         function f(a, b) {
-            for (;;) {
+            for (;;)
                 if (a.g() || b.p) break;
-            }
         }
     }
 }
index f1d2b44..ec1fb0b 100644 (file)
@@ -121,7 +121,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
                 return;
             }
             // preserve for (var xxx; ...)
-            if (parent instanceof U.AST_For && parent.init === node && node instanceof U.AST_Var) return node;
+            if (parent instanceof U.AST_For && parent.init === node && node instanceof U.AST_Definitions) return node;
             // preserve for (xxx in ...)
             if (parent instanceof U.AST_ForIn && parent.init === node) return node;
 
@@ -145,7 +145,9 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
                 return permute < 2 ? expr : wrap_with_console_log(expr);
             }
             else if (node instanceof U.AST_BlockStatement) {
-                if (in_list) {
+                if (in_list && node.body.filter(function(node) {
+                    return node instanceof U.AST_Const;
+                }).length == 0) {
                     node.start._permute++;
                     CHANGED = true;
                     return List.splice(node.body);
@@ -410,7 +412,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
                     start: {},
                 });
             }
-            else if (node instanceof U.AST_Var) {
+            else if (node instanceof U.AST_Definitions) {
                 // remove empty var statement
                 if (node.definitions.length == 0) return in_list ? List.skip : new U.AST_EmptyStatement({
                     start: {},
index 60f8666..717252d 100644 (file)
@@ -275,6 +275,7 @@ var CANNOT_RETURN = true;
 var NO_DEFUN = false;
 var DEFUN_OK = true;
 var DONT_STORE = true;
+var NO_CONST = true;
 
 var VAR_NAMES = [
     "a",
@@ -312,6 +313,7 @@ var TYPEOF_OUTCOMES = [
     "crap",
 ];
 
+var block_vars = [];
 var unique_vars = [];
 var loops = 0;
 var funcs = 0;
@@ -374,33 +376,66 @@ function filterDirective(s) {
     return s;
 }
 
+function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
+    var block_len = block_vars.length;
+    var var_len = VAR_NAMES.length;
+    var consts = [];
+    unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity");
+    while (!rng(block_vars.length > block_len ? 10 : 100)) {
+        var name = createVarName(MANDATORY, DONT_STORE);
+        consts.push(name);
+        block_vars.push(name);
+    }
+    unique_vars.length -= 6;
+    fn(function() {
+        var s = [];
+        if (consts.length) {
+            var save = VAR_NAMES;
+            VAR_NAMES = VAR_NAMES.filter(function(name) {
+                return consts.indexOf(name) < 0;
+            });
+            var len = VAR_NAMES.length;
+            s.push("const " + consts.map(function(name) {
+                var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
+                VAR_NAMES.push(name);
+                return name + " = " + value;
+            }).join(", ") + ";");
+            VAR_NAMES = save.concat(VAR_NAMES.slice(len));
+        }
+        return s.join("\n");
+    });
+    block_vars.length = block_len;
+    if (consts.length) VAR_NAMES.splice(var_len, consts.length);
+}
+
 function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
     if (--recurmax < 0) { return ";"; }
     if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
-    var namesLenBefore = VAR_NAMES.length;
+    var s = [];
     var name;
-    if (allowDefun || rng(5) > 0) {
-        name = "f" + funcs++;
-    } else {
-        unique_vars.push("a", "b", "c");
-        name = createVarName(MANDATORY, !allowDefun);
-        unique_vars.length -= 3;
-    }
-    var s = [
-        "function " + name + "(" + createParams() + "){",
-        strictMode()
-    ];
-    if (rng(5) === 0) {
-        // functions with functions. lower the recursion to prevent a mess.
-        s.push(createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), DEFUN_OK, canThrow, stmtDepth));
-    } else {
-        // functions with statements
-        s.push(createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth));
-    }
-    s.push("}", "");
-    s = filterDirective(s).join("\n");
+    createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
+        var namesLenBefore = VAR_NAMES.length;
+        if (allowDefun || rng(5) > 0) {
+            name = "f" + funcs++;
+        } else {
+            unique_vars.push("a", "b", "c");
+            name = createVarName(MANDATORY, !allowDefun);
+            unique_vars.length -= 3;
+        }
+        s.push("function " + name + "(" + createParams() + "){", strictMode());
+        s.push(defns());
+        if (rng(5) === 0) {
+            // functions with functions. lower the recursion to prevent a mess.
+            s.push(createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), DEFUN_OK, canThrow, stmtDepth));
+        } else {
+            // functions with statements
+            s.push(_createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth));
+        }
+        s.push("}", "");
+        s = filterDirective(s).join("\n");
 
-    VAR_NAMES.length = namesLenBefore;
+        VAR_NAMES.length = namesLenBefore;
+    });
 
     if (!allowDefun) {
         // avoid "function statements" (decl inside statements)
@@ -414,7 +449,7 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
     return s + ";";
 }
 
-function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
+function _createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
     if (--recurmax < 0) { return ";"; }
     var s = "";
     while (--n > 0) {
@@ -423,6 +458,15 @@ function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotRe
     return s;
 }
 
+function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
+    var s = "";
+    createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
+        s += defns() + "\n";
+        s += _createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth);
+    });
+    return s;
+}
+
 function enableLoopControl(flag, defaultValue) {
     return Array.isArray(flag) && flag.indexOf("") < 0 ? flag.concat("") : flag || defaultValue;
 }
@@ -496,7 +540,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
         var label = createLabel(canBreak, canContinue);
         canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
         canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
-        var key = rng(10) ? "key" + loop : getVarName();
+        var key = rng(10) ? "key" + loop : getVarName(NO_CONST);
         return [
             "{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ",
             label.target + " for (",
@@ -571,13 +615,18 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
             // the catch var should only be accessible in the catch clause...
             // we have to do go through some trouble here to prevent leaking it
             var nameLenBefore = VAR_NAMES.length;
-            var catchName = createVarName(MANDATORY);
-            var freshCatchName = VAR_NAMES.length !== nameLenBefore;
-            if (!catch_redef) unique_vars.push(catchName);
-            s += " catch (" + catchName + ") { " + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + " }";
-            // remove catch name
-            if (!catch_redef) unique_vars.pop();
-            if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1);
+            createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
+                var catchName = createVarName(MANDATORY);
+                var freshCatchName = VAR_NAMES.length !== nameLenBefore;
+                if (!catch_redef) unique_vars.push(catchName);
+                s += " catch (" + catchName + ") { ";
+                s += defns() + "\n";
+                s += _createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth);
+                s += " }";
+                // remove catch name
+                if (!catch_redef) unique_vars.pop();
+                if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1);
+            });
         }
         if (n !== 0) s += " finally { " + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + " }";
         return s;
@@ -597,7 +646,7 @@ function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotR
         if (hadDefault || rng(5) > 0) {
             s.push(
                 "case " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ":",
-                createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
+                _createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
                 rng(10) > 0 ? " break;" : "/* fall-through */",
                 ""
             );
@@ -605,7 +654,7 @@ function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotR
             hadDefault = true;
             s.push(
                 "default:",
-                createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
+                _createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
                 ""
             );
         }
@@ -653,7 +702,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
       case p++:
         return getVarName();
       case p++:
-        return getVarName() + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
+        return getVarName(NO_CONST) + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
       case p++:
         return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
       case p++:
@@ -699,19 +748,22 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
             );
             break;
           default:
-            var instantiate = rng(4) ? "new " : "";
-            s.push(
-                instantiate + "function " + name + "(){",
-                strictMode()
-            );
-            if (instantiate) for (var i = rng(4); --i >= 0;) {
-                if (rng(2)) s.push("this." + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";");
-                else  s.push("this[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";");
-            }
-            s.push(
-                createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
-                rng(2) == 0 ? "}" : "}()"
-            );
+            createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
+                var instantiate = rng(4) ? "new " : "";
+                s.push(
+                    instantiate + "function " + name + "(){",
+                    strictMode(),
+                    defns()
+                );
+                if (instantiate) for (var i = rng(4); --i >= 0;) {
+                    if (rng(2)) s.push("this." + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";");
+                    else  s.push("this[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";");
+                }
+                s.push(
+                    _createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
+                    rng(2) == 0 ? "}" : "}()"
+                );
+            });
             break;
         }
         VAR_NAMES.length = nameLenBefore;
@@ -861,13 +913,16 @@ function createAccessor(recurmax, stmtDepth, canThrow) {
         do {
             prop2 = getDotKey();
         } while (prop1 == prop2);
-        s = [
-            "set " + prop1 + "(" + createVarName(MANDATORY) + "){",
-            strictMode(),
-            createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
-            "this." + prop2 + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";",
-            "},"
-        ];
+        createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
+            s = [
+                "set " + prop1 + "(" + createVarName(MANDATORY) + "){",
+                strictMode(),
+                defns(),
+                _createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
+                "this." + prop2 + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";",
+                "},"
+            ];
+        });
     }
     VAR_NAMES.length = namesLenBefore;
     return filterDirective(s).join("\n");
@@ -906,7 +961,7 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
       case 1:
         return "(" + createUnarySafePrefix() + "(" + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + "))";
       case 2:
-        assignee = getVarName();
+        assignee = getVarName(NO_CONST);
         return "(" + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
       case 3:
         assignee = getVarName();
@@ -968,9 +1023,10 @@ function createUnaryPostfix() {
     return UNARY_POSTFIX[rng(UNARY_POSTFIX.length)];
 }
 
-function getVarName() {
+function getVarName(noConst) {
     // try to get a generated name reachable from current scope. default to just `a`
-    return VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)] || "a";
+    var name = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)];
+    return !name || noConst && block_vars.indexOf(name) >= 0 ? "a" : name;
 }
 
 function createVarName(maybe, dontStore) {
@@ -980,7 +1036,7 @@ function createVarName(maybe, dontStore) {
         do {
             name = VAR_NAMES[rng(VAR_NAMES.length)];
             if (suffix) name += "_" + suffix;
-        } while (unique_vars.indexOf(name) >= 0);
+        } while (unique_vars.indexOf(name) >= 0 || block_vars.indexOf(name) >= 0);
         if (suffix && !dontStore) VAR_NAMES.push(name);
         return name;
     }
@@ -1258,10 +1314,6 @@ function patch_try_catch(orig, toplevel) {
     }
 }
 
-var fallback_options = [ JSON.stringify({
-    compress: false,
-    mangle: false
-}) ];
 var minify_options = require("./options.json").map(JSON.stringify);
 var original_code, original_result, errored;
 var uglify_code, uglify_result, ok;
@@ -1269,10 +1321,19 @@ for (var round = 1; round <= num_iterations; round++) {
     process.stdout.write(round + " of " + num_iterations + "\r");
 
     original_code = createTopLevelCode();
-    var orig_result = [ sandbox.run_code(original_code) ];
+    var orig_result = [ sandbox.run_code(original_code), sandbox.run_code(original_code, true) ];
     errored = typeof orig_result[0] != "string";
-    if (!errored) orig_result.push(sandbox.run_code(original_code, true));
-    (errored ? fallback_options : minify_options).forEach(function(options) {
+    if (errored) {
+        println("//=============================================================");
+        println("// original code");
+        try_beautify(original_code, false, orig_result[0], println);
+        println();
+        println();
+        println("original result:");
+        println(orig_result[0]);
+        println();
+    }
+    minify_options.forEach(function(options) {
         var o = JSON.parse(options);
         var toplevel = sandbox.has_toplevel(o);
         o.validate = true;
@@ -1294,6 +1355,8 @@ for (var round = 1; round <= num_iterations; round++) {
                     ok = sandbox.same_stdout(fuzzy_result, uglify_result);
                 }
             }
+            // ignore difference in error message caused by Temporal Dead Zone
+            if (!ok && errored) ok = uglify_result.name == "ReferenceError" && original_result.name == "ReferenceError";
             // ignore difference in error message caused by `in`
             // ignore difference in depth of termination caused by infinite recursion
             if (!ok) {
@@ -1308,16 +1371,6 @@ for (var round = 1; round <= num_iterations; round++) {
             ok = errored && uglify_code.name == original_result.name;
         }
         if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options);
-        else if (errored) {
-            println("//=============================================================");
-            println("// original code");
-            try_beautify(original_code, toplevel, original_result, println);
-            println();
-            println();
-            println("original result:");
-            println(original_result);
-            println();
-        }
         if (!ok && isFinite(num_iterations)) {
             println();
             process.exit(1);