fix corner cases with block-scoped functions (#4695)
authorAlex Lam S.L <alexlamsl@gmail.com>
Fri, 26 Feb 2021 20:16:14 +0000 (20:16 +0000)
committerGitHub <noreply@github.com>
Fri, 26 Feb 2021 20:16:14 +0000 (04:16 +0800)
lib/compress.js
lib/scope.js
test/compress/const.js
test/compress/dead-code.js
test/compress/functions.js
test/compress/let.js

index 16d86d9..0e69880 100644 (file)
@@ -1509,11 +1509,22 @@ merge(Compressor.prototype, {
         return stat instanceof AST_Const || stat instanceof AST_DefClass || stat instanceof AST_Let;
     }
 
+    function safe_to_trim(stat) {
+        if (stat instanceof AST_LambdaDefinition) {
+            var def = stat.name.definition();
+            return def.scope === stat.name.scope || all(def.references, function(ref) {
+                var scope = ref.scope;
+                do {
+                    if (scope === stat.name.scope) return true;
+                } while (scope = scope.parent_scope);
+            });
+        }
+        return !is_lexical_definition(stat);
+    }
+
     function as_statement_array(thing) {
         if (thing === null) return [];
-        if (thing instanceof AST_BlockStatement) return all(thing.body, function(stat) {
-            return !is_lexical_definition(stat);
-        }) ? thing.body : [ thing ];
+        if (thing instanceof AST_BlockStatement) return all(thing.body, safe_to_trim) ? thing.body : [ thing ];
         if (thing instanceof AST_EmptyStatement) return [];
         if (is_statement(thing)) return [ thing ];
         throw new Error("Can't convert thing to statement array");
@@ -2732,9 +2743,7 @@ merge(Compressor.prototype, {
             for (var i = 0; i < statements.length;) {
                 var stat = statements[i];
                 if (stat instanceof AST_BlockStatement) {
-                    if (all(stat.body, function(stat) {
-                        return !is_lexical_definition(stat);
-                    })) {
+                    if (all(stat.body, safe_to_trim)) {
                         CHANGED = true;
                         eliminate_spurious_blocks(stat.body);
                         [].splice.apply(statements, [i, 1].concat(stat.body));
@@ -2972,15 +2981,16 @@ merge(Compressor.prototype, {
             }
 
             function extract_functions() {
-                var tail = statements.slice(i + 1);
-                statements.length = i + 1;
-                return tail.filter(function(stat) {
-                    if (stat instanceof AST_Defun) {
-                        statements.push(stat);
+                var defuns = [];
+                var tail = statements.splice(i + 1).filter(function(stat) {
+                    if (stat instanceof AST_LambdaDefinition) {
+                        defuns.push(stat);
                         return false;
                     }
                     return true;
                 });
+                [].push.apply(all(tail, safe_to_trim) ? statements : tail, defuns);
+                return tail;
             }
 
             function as_statement_array_with_return(node, ab) {
@@ -3439,7 +3449,7 @@ merge(Compressor.prototype, {
         function push(node) {
             if (block) {
                 block.push(node);
-                if (is_lexical_definition(node)) block.required = true;
+                if (!safe_to_trim(node)) block.required = true;
             } else {
                 target.push(node);
             }
@@ -5118,7 +5128,7 @@ merge(Compressor.prototype, {
             return in_list ? List.skip : make_node(AST_EmptyStatement, node);
           case 1:
             var stat = node.body[0];
-            if (is_lexical_definition(stat)) return node;
+            if (!safe_to_trim(stat)) return node;
             if (parent instanceof AST_IterationStatement && stat instanceof AST_LambdaDefinition) return node;
             return stat;
         }
index 799b0dc..600a686 100644 (file)
@@ -327,6 +327,13 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
                     self.uses_eval = true;
                 }
             }
+            if (sym.init instanceof AST_LambdaDefinition && sym.scope !== sym.init.name.scope) {
+                var scope = node.scope;
+                do {
+                    if (scope === sym.init.name.scope) break;
+                } while (scope = scope.parent_scope);
+                if (!scope) sym.init = undefined;
+            }
             node.thedef = sym;
             node.reference(options);
             return true;
index be49e84..4306fff 100644 (file)
@@ -1454,3 +1454,47 @@ issue_4689: {
     expect_stdout: "PASS"
     node_version: ">=4"
 }
+
+issue_4691: {
+    options = {
+        if_return: true,
+        toplevel: true,
+    }
+    input: {
+        function A() {}
+        A.prototype.f = function() {
+            if (!this)
+                return;
+            const a = "PA";
+            function g(b) {
+                h(a + b);
+            }
+            [ "SS" ].forEach(function(c) {
+                g(c);
+            });
+        };
+        function h(d) {
+            console.log(d);
+        }
+        new A().f();
+    }
+    expect: {
+        function A() {}
+        A.prototype.f = function() {
+            if (this) {
+                const a = "PA";
+                [ "SS" ].forEach(function(c) {
+                    g(c);
+                });
+                function g(b) {
+                    h(a + b);
+                }
+            }
+        };
+        function h(d) {
+            console.log(d);
+        }
+        new A().f();
+    }
+    expect_stdout: "PASS"
+}
index 1720a0d..9101869 100644 (file)
@@ -53,8 +53,10 @@ dead_code_2_should_warn: {
             g();
             x = 10;
             throw new Error("foo");
-            var x;
-            function g(){};
+            {
+                var x;
+                function g(){};
+            }
         }
         f();
     }
@@ -62,7 +64,6 @@ dead_code_2_should_warn: {
     expect_warnings: [
         "WARN: Dropping unreachable code [test/compress/dead-code.js:8,12]",
     ]
-    node_version: "<=4"
 }
 
 dead_code_constant_boolean_should_warn_more: {
@@ -88,8 +89,10 @@ dead_code_constant_boolean_should_warn_more: {
         bar();
     }
     expect: {
-        var foo;
-        function bar() {}
+        {
+            var foo;
+            function bar() {}
+        }
         // nothing for the while
         // as for the for, it should keep:
         var x = 10, y;
index fe9307c..44e41df 100644 (file)
@@ -5018,9 +5018,12 @@ catch_no_argname: {
         try {
             throw a;
         } catch {
-            console.log(a, a, a);
+            function g() {
+                return a;
+            }
+            console.log(a, a, g());
         }
-        console.log(a, a, a);
+        console.log(a, a, g());
     }
     expect_stdout: [
         "PASS PASS PASS",
@@ -5558,3 +5561,140 @@ issue_4659_3: {
     }
     expect_stdout: "1"
 }
+
+block_scope_1: {
+    input: {
+        console.log(typeof f);
+        function f() {}
+    }
+    expect: {
+        console.log(typeof f);
+        function f() {}
+    }
+    expect_stdout: "function"
+}
+
+block_scope_1_compress: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+        typeofs: true,
+        unused: true,
+    }
+    input: {
+        console.log(typeof f);
+        function f() {}
+    }
+    expect: {
+        console.log("function");
+    }
+    expect_stdout: "function"
+}
+
+block_scope_2: {
+    input: {
+        {
+            console.log(typeof f);
+        }
+        function f() {}
+    }
+    expect: {
+        console.log(typeof f);
+        function f() {}
+    }
+    expect_stdout: "function"
+}
+
+block_scope_2_compress: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+        typeofs: true,
+        unused: true,
+    }
+    input: {
+        {
+            console.log(typeof f);
+        }
+        function f() {}
+    }
+    expect: {
+        console.log("function");
+    }
+    expect_stdout: "function"
+}
+
+block_scope_3: {
+    input: {
+        console.log(typeof f);
+        {
+            function f() {}
+        }
+    }
+    expect: {
+        console.log(typeof f);
+        {
+            function f() {}
+        }
+    }
+    expect_stdout: true
+}
+
+block_scope_3_compress: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+        typeofs: true,
+        unused: true,
+    }
+    input: {
+        console.log(typeof f);
+        {
+            function f() {}
+        }
+    }
+    expect: {
+        console.log(typeof f);
+        {
+            function f() {}
+        }
+    }
+    expect_stdout: true
+}
+
+block_scope_4: {
+    input: {
+        {
+            console.log(typeof f);
+            function f() {}
+        }
+    }
+    expect: {
+        console.log(typeof f);
+        function f() {}
+    }
+    expect_stdout: "function"
+}
+
+block_scope_4_compress: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+        typeofs: true,
+        unused: true,
+    }
+    input: {
+        {
+            console.log(typeof f);
+            function f() {}
+        }
+    }
+    expect: {
+        console.log("function");
+    }
+    expect_stdout: "function"
+}
index b5005eb..04002e9 100644 (file)
@@ -1357,3 +1357,50 @@ issue_4689: {
     expect_stdout: "PASS"
     node_version: ">=4"
 }
+
+issue_4691: {
+    options = {
+        if_return: true,
+        toplevel: true,
+    }
+    input: {
+        "use strict";
+        function A() {}
+        A.prototype.f = function() {
+            if (!this)
+                return;
+            let a = "PA";
+            function g(b) {
+                h(a + b);
+            }
+            [ "SS" ].forEach(function(c) {
+                g(c);
+            });
+        };
+        function h(d) {
+            console.log(d);
+        }
+        new A().f();
+    }
+    expect: {
+        "use strict";
+        function A() {}
+        A.prototype.f = function() {
+            if (this) {
+                let a = "PA";
+                [ "SS" ].forEach(function(c) {
+                    g(c);
+                });
+                function g(b) {
+                    h(a + b);
+                }
+            }
+        };
+        function h(d) {
+            console.log(d);
+        }
+        new A().f();
+    }
+    expect_stdout: "PASS"
+    node_version: ">=4"
+}