/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; /** * Inline function tests. * @author johnlenz@google.com (john lenz) */ public class InlineFunctionsTest extends CompilerTestCase { boolean allowGlobalFunctionInlining = true; boolean allowBlockInlining = true; final boolean allowExpressionDecomposition = true; final boolean allowFunctionExpressionInlining = true; final boolean allowLocalFunctionInlining = true; boolean assumeStrictThis = false; boolean assumeMinimumCapture = false; public InlineFunctionsTest() { this.enableNormalize(); this.enableMarkNoSideEffects(); } @Override protected void setUp() throws Exception { super.setUp(); super.enableLineNumberCheck(true); allowGlobalFunctionInlining = true; allowBlockInlining = true; assumeStrictThis = false; assumeMinimumCapture = false; } @Override protected CompilerPass getProcessor(Compiler compiler) { compiler.resetUniqueNameId(); return new InlineFunctions( compiler, compiler.getUniqueNameIdSupplier(), allowGlobalFunctionInlining, allowLocalFunctionInlining, allowBlockInlining, assumeStrictThis, assumeMinimumCapture); } /** * Returns the number of times the pass should be run before results are * verified. */ @Override protected int getNumRepetitions() { // Some inlining can only be done in multiple passes. return 3; } public void testInlineEmptyFunction1() { // Empty function, no params. test("function foo(){}" + "foo();", "void 0;"); } public void testInlineEmptyFunction2() { // Empty function, params with no side-effects. test("function foo(){}" + "foo(1, new Date, function(){});", "void 0;"); } public void testInlineEmptyFunction3() { // Empty function, multiple references. test("function foo(){}" + "foo();foo();foo();", "void 0;void 0;void 0"); } public void testInlineEmptyFunction4() { // Empty function, params with side-effects forces block inlining. test("function foo(){}" + "foo(x());", "{var JSCompiler_inline_anon_param_0=x();}"); } public void testInlineEmptyFunction5() { // Empty function, call params with side-effects in expression can not // be inlined. allowBlockInlining = false; testSame("function foo(){}" + "foo(x());"); } public void testInlineFunctions1() { // As simple a test as we can get. test("function foo(){ return 4 }" + "foo();", "4"); } public void testInlineFunctions2() { // inline simple constants // NOTE: CD is not inlined. test("var t;var AB=function(){return 4};" + "function BC(){return 6;}" + "CD=function(x){return x + 5};x=CD(3);y=AB();z=BC();", "var t;CD=function(x){return x+5};x=CD(3);y=4;z=6" ); } public void testInlineFunctions3() { // inline simple constants test("var t;var AB=function(){return 4};" + "function BC(){return 6;}" + "var CD=function(x){return x + 5};x=CD(3);y=AB();z=BC();", "var t;x=3+5;y=4;z=6"); } public void testInlineFunctions4() { // don't inline if there are multiple definitions (need DFA for that). test("var t; var AB = function() { return 4 }; " + "function BC() { return 6; }" + "CD = 0;" + "CD = function(x) { return x + 5 }; x = CD(3); y = AB(); z = BC();", "var t;CD=0;CD=function(x){return x+5};x=CD(3);y=4;z=6"); } public void testInlineFunctions5() { // inline additions test("var FOO_FN=function(x,y) { return \"de\" + x + \"nu\" + y };" + "var a = FOO_FN(\"ez\", \"ts\")", "var a=\"de\"+\"ez\"+\"nu\"+\"ts\""); } public void testInlineFunctions6() { // more complex inlines test("function BAR_FN(x, y, z) { return z(foo(x + y)) }" + "alert(BAR_FN(1, 2, baz))", "alert(baz(foo(1+2)))"); } public void testInlineFunctions7() { // inlines appearing multiple times test("function FN(x,y,z){return x+x+y}" + "var b=FN(1,2,3)", "var b=1+1+2"); } public void testInlineFunctions8() { // check correct parenthesization test("function MUL(x,y){return x*y}function ADD(x,y){return x+y}" + "var a=1+MUL(2,3);var b=2*ADD(3,4)", "var a=1+2*3;var b=2*(3+4)"); } public void testInlineFunctions9() { // don't inline if the input parameter is modified. test("function INC(x){return x++}" + "var y=INC(i)", "var y;{var x$$inline_0=i;" + "y=x$$inline_0++}"); } public void testInlineFunctions10() { test("function INC(x){return x++}" + "var y=INC(i);y=INC(i)", "var y;" + "{var x$$inline_0=i;" + "y=x$$inline_0++}" + "{var x$$inline_2=i;" + "y=x$$inline_2++}"); } public void testInlineFunctions11() { test("function f(x){return x}" + "var y=f(i)", "var y=i"); } public void testInlineFunctions12() { // don't inline if the input parameter has side-effects. allowBlockInlining = false; test("function f(x){return x}" + "var y=f(i)", "var y=i"); testSame("function f(x){return x}" + "var y=f(i++)"); } public void testInlineFunctions13() { // inline as block if the input parameter has side-effects. test("function f(x){return x}" + "var y=f(i++)", "var y;{var x$$inline_0=i++;y=x$$inline_0}"); } public void testInlineFunctions14() { // don't remove functions that are referenced on other ways test("function FOO(x){return x}var BAR=function(y){return y}" + ";b=FOO;a(BAR);x=FOO(1);y=BAR(2)", "function FOO(x){return x}var BAR=function(y){return y}" + ";b=FOO;a(BAR);x=1;y=2"); } public void testInlineFunctions15a() { // closure factories: do inline into global scope. test("function foo(){return function(a){return a+1}}" + "var b=function(){return c};" + "var d=b()+foo()", "var d=c+function(a){return a+1}"); } public void testInlineFunctions15b() { assumeMinimumCapture = false; // closure factories: don't inline closure with locals into global scope. test("function foo(){var x;return function(a){return a+1}}" + "var b=function(){return c};" + "var d=b()+foo()", "function foo(){var x;return function(a){return a+1}}" + "var d=c+foo()"); assumeMinimumCapture = true; test("function foo(){var x;return function(a){return a+1}}" + "var b=function(){return c};" + "var d=b()+foo()", "var JSCompiler_temp_const$$0 = c;\n" + "var JSCompiler_inline_result$$1;\n" + "{\n" + "var x$$inline_2;\n" + "JSCompiler_inline_result$$1 = " + " function(a$$inline_3){ return a$$inline_3+1 };\n" + "}" + "var d=JSCompiler_temp_const$$0 + JSCompiler_inline_result$$1"); } public void testInlineFunctions15c() { assumeMinimumCapture = false; // closure factories: don't inline into non-global scope. test("function foo(){return function(a){return a+1}}" + "var b=function(){return c};" + "function _x(){ var d=b()+foo() }", "function foo(){return function(a){return a+1}}" + "function _x(){ var d=c+foo() }"); assumeMinimumCapture = true; // closure factories: don't inline into non-global scope. test("function foo(){return function(a){return a+1}}" + "var b=function(){return c};" + "function _x(){ var d=b()+foo() }", "function _x(){var d=c+function(a){return a+1}}"); } public void testInlineFunctions15d() { assumeMinimumCapture = false; // closure factories: don't inline functions with vars. test("function foo(){var x; return function(a){return a+1}}" + "var b=function(){return c};" + "function _x(){ var d=b()+foo() }", "function foo(){var x; return function(a){return a+1}}" + "function _x(){ var d=c+foo() }"); assumeMinimumCapture = true; // closure factories: don't inline functions with vars. test("function foo(){var x; return function(a){return a+1}}" + "var b=function(){return c};" + "function _x(){ var d=b()+foo() }", "function _x() { \n" + " var JSCompiler_temp_const$$0 = c;\n" + " var JSCompiler_inline_result$$1;\n" + " {\n" + " var x$$inline_2;\n" + " JSCompiler_inline_result$$1 = " + " function(a$$inline_3) {return a$$inline_3+1};\n" + " }\n" + " var d = JSCompiler_temp_const$$0+JSCompiler_inline_result$$1\n" + "}"); } public void testInlineFunctions16a() { assumeMinimumCapture = false; testSame("function foo(b){return window.bar(function(){c(b)})}" + "var d=foo(e)"); assumeMinimumCapture = true; test( "function foo(b){return window.bar(function(){c(b)})}" + "var d=foo(e)", "var d;{var b$$inline_0=e;" + "d=window.bar(function(){c(b$$inline_0)})}"); } public void testInlineFunctions16b() { test("function foo(){return window.bar(function(){c()})}" + "var d=foo(e)", "var d=window.bar(function(){c()})"); } public void testInlineFunctions17() { // don't inline recursive functions testSame("function foo(x){return x*x+foo(3)}var bar=foo(4)"); } public void testInlineFunctions18() { // TRICKY ... test nested inlines allowBlockInlining = false; test("function foo(a, b){return a+b}" + "function bar(d){return c}" + "var d=foo(bar(1),e)", "var d=c+e"); } public void testInlineFunctions19() { // TRICKY ... test nested inlines // with block inlining possible test("function foo(a, b){return a+b}" + "function bar(d){return c}" + "var d=foo(bar(1),e)", "var d;{d=c+e}"); } public void testInlineFunctions20() { // Make sure both orderings work allowBlockInlining = false; test("function foo(a, b){return a+b}" + "function bar(d){return c}" + "var d=bar(foo(1,e));", "var d=c"); } public void testInlineFunctions21() { // with block inlining possible test("function foo(a, b){return a+b}" + "function bar(d){return c}" + "var d=bar(foo(1,e))", "var d;{d=c}"); } public void testInlineFunctions22() { // Another tricky case ... test nested compiler inlines test("function plex(a){if(a) return 0;else return 1;}" + "function foo(a, b){return bar(a+b)}" + "function bar(d){return plex(d)}" + "var d=foo(1,2)", "var d;{JSCompiler_inline_label_plex_1:{" + "if(1+2){" + "d=0;break JSCompiler_inline_label_plex_1}" + "else{" + "d=1;break JSCompiler_inline_label_plex_1}d=void 0}}"); } public void testInlineFunctions23() { // Test both orderings again test("function complex(a){if(a) return 0;else return 1;}" + "function bar(d){return complex(d)}" + "function foo(a, b){return bar(a+b)}" + "var d=foo(1,2)", "var d;{JSCompiler_inline_label_complex_1:{" + "if(1+2){" + "d=0;break JSCompiler_inline_label_complex_1" + "}else{" + "d=1;break JSCompiler_inline_label_complex_1" + "}d=void 0}}"); } public void testInlineFunctions24() { // Don't inline functions with 'arguments' or 'this' testSame("function foo(x){return this}foo(1)"); } public void testInlineFunctions25() { testSame("function foo(){return arguments[0]}foo()"); } public void testInlineFunctions26() { // Don't inline external functions testSame("function _foo(x){return x}_foo(1)"); } public void testInlineFunctions27() { test("var window = {}; function foo(){window.bar++; return 3;}" + "var x = {y: 1, z: foo(2)};", "var window={};" + "var JSCompiler_inline_result$$0;" + "{" + " window.bar++;" + " JSCompiler_inline_result$$0 = 3;" + "}" + "var x = {y: 1, z: JSCompiler_inline_result$$0};"); } public void testInlineFunctions28() { test("var window = {}; function foo(){window.bar++; return 3;}" + "var x = {y: alert(), z: foo(2)};", "var window = {};" + "var JSCompiler_temp_const$$0 = alert();" + "var JSCompiler_inline_result$$1;" + "{" + " window.bar++;" + " JSCompiler_inline_result$$1 = 3;}" + "var x = {" + " y: JSCompiler_temp_const$$0," + " z: JSCompiler_inline_result$$1" + "};"); } public void testInlineFunctions29() { test("var window = {}; function foo(){window.bar++; return 3;}" + "var x = {a: alert(), b: alert2(), c: foo(2)};", "var window = {};" + "var JSCompiler_temp_const$$1 = alert();" + "var JSCompiler_temp_const$$0 = alert2();" + "var JSCompiler_inline_result$$2;" + "{" + " window.bar++;" + " JSCompiler_inline_result$$2 = 3;}" + "var x = {" + " a: JSCompiler_temp_const$$1," + " b: JSCompiler_temp_const$$0," + " c: JSCompiler_inline_result$$2" + "};"); } public void testInlineFunctions30() { // As simple a test as we can get. testSame("function foo(){ return eval() }" + "foo();"); } public void testInlineFunctions31() { // Don't introduce a duplicate label in the same scope test("function foo(){ lab:{4;} }" + "lab:{foo();}", "lab:{{JSCompiler_inline_label_0:{4}}}"); } public void testMixedModeInlining1() { // Base line tests, direct inlining test("function foo(){return 1}" + "foo();", "1;"); } public void testMixedModeInlining2() { // Base line tests, block inlining. Block inlining is needed by // possible-side-effect parameter. test("function foo(){return 1}" + "foo(x());", "{var JSCompiler_inline_anon_param_0=x();1}"); } public void testMixedModeInlining3() { // Inline using both modes. test("function foo(){return 1}" + "foo();foo(x());", "1;{var JSCompiler_inline_anon_param_0=x();1}"); } public void testMixedModeInlining4() { // Inline using both modes. Alternating. Second call of each type has // side-effect-less parameter, this is thrown away. test("function foo(){return 1}" + "foo();foo(x());" + "foo(1);foo(1,x());", "1;{var JSCompiler_inline_anon_param_0=x();1}" + "1;{var JSCompiler_inline_anon_param_4=x();1}"); } public void testMixedModeInliningCosting1() { // Inline using both modes. Costing estimates. // Base line. test( "function foo(a,b){return a+b+a+b+4+5+6+7+8+9+1+2+3+4+5}" + "foo(1,2);" + "foo(2,3)", "1+2+1+2+4+5+6+7+8+9+1+2+3+4+5;" + "2+3+2+3+4+5+6+7+8+9+1+2+3+4+5"); } public void testMixedModeInliningCosting2() { // Don't inline here because the function definition can not be eliminated. // TODO(johnlenz): Should we add constant removing to the unit test? testSame( "function foo(a,b){return a+b+a+b+4+5+6+7+8+9+1+2+3+4+5}" + "foo(1,2);" + "foo(2,3,x())"); } public void testMixedModeInliningCosting3() { // Do inline here because the function definition can be eliminated. test( "function foo(a,b){return a+b+a+b+4+5+6+7+8+9+1+2+3+10}" + "foo(1,2);" + "foo(2,3,x())", "1+2+1+2+4+5+6+7+8+9+1+2+3+10;" + "{var JSCompiler_inline_anon_param_2=x();" + "2+3+2+3+4+5+6+7+8+9+1+2+3+10}"); } public void testMixedModeInliningCosting4() { // Threshold test. testSame( "function foo(a,b){return a+b+a+b+4+5+6+7+8+9+1+2+3+4+101}" + "foo(1,2);" + "foo(2,3,x())"); } public void testNoInlineIfParametersModified1() { // Assignment test("function f(x){return x=1}f(undefined)", "{var x$$inline_0=undefined;" + "x$$inline_0=1}"); } public void testNoInlineIfParametersModified2() { test("function f(x){return (x)=1;}f(2)", "{var x$$inline_0=2;" + "x$$inline_0=1}"); } public void testNoInlineIfParametersModified3() { // Assignment variant. test("function f(x){return x*=2}f(2)", "{var x$$inline_0=2;" + "x$$inline_0*=2}"); } public void testNoInlineIfParametersModified4() { // Assignment in if. test("function f(x){return x?(x=2):0}f(2)", "{var x$$inline_0=2;" + "x$$inline_0?(" + "x$$inline_0=2):0}"); } public void testNoInlineIfParametersModified5() { // Assignment in if, multiple params test("function f(x,y){return x?(y=2):0}f(2,undefined)", "{var y$$inline_1=undefined;2?(" + "y$$inline_1=2):0}"); } public void testNoInlineIfParametersModified6() { test("function f(x,y){return x?(y=2):0}f(2)", "{var y$$inline_1=void 0;2?(" + "y$$inline_1=2):0}"); } public void testNoInlineIfParametersModified7() { // Increment test("function f(a){return++a<++a}f(1)", "{var a$$inline_0=1;" + "++a$$inline_0<" + "++a$$inline_0}"); } public void testNoInlineIfParametersModified8() { // OK, object parameter modified. test("function f(a){return a.x=2}f(o)", "o.x=2"); } public void testNoInlineIfParametersModified9() { // OK, array parameter modified. test("function f(a){return a[2]=2}f(o)", "o[2]=2"); } public void testInlineNeverPartialSubtitution1() { test("function f(z){return x.y.z;}f(1)", "x.y.z"); } public void testInlineNeverPartialSubtitution2() { test("function f(z){return x.y[z];}f(a)", "x.y[a]"); } public void testInlineNeverMutateConstants() { test("function f(x){return x=1}f(undefined)", "{var x$$inline_0=undefined;" + "x$$inline_0=1}"); } public void testInlineNeverOverrideNewValues() { test("function f(a){return++a<++a}f(1)", "{var a$$inline_0=1;" + "++a$$inline_0<++a$$inline_0}"); } public void testInlineMutableArgsReferencedOnce() { test("function foo(x){return x;}foo([])", "[]"); } public void testNoInlineMutableArgs1() { allowBlockInlining = false; testSame("function foo(x){return x+x} foo([])"); } public void testNoInlineMutableArgs2() { allowBlockInlining = false; testSame("function foo(x){return x+x} foo(new Date)"); } public void testNoInlineMutableArgs3() { allowBlockInlining = false; testSame("function foo(x){return x+x} foo(true&&new Date)"); } public void testNoInlineMutableArgs4() { allowBlockInlining = false; testSame("function foo(x){return x+x} foo({})"); } public void testInlineBlockMutableArgs1() { test("function foo(x){x+x}foo([])", "{var x$$inline_0=[];" + "x$$inline_0+x$$inline_0}"); } public void testInlineBlockMutableArgs2() { test("function foo(x){x+x}foo(new Date)", "{var x$$inline_0=new Date;" + "x$$inline_0+x$$inline_0}"); } public void testInlineBlockMutableArgs3() { test("function foo(x){x+x}foo(true&&new Date)", "{var x$$inline_0=true&&new Date;" + "x$$inline_0+x$$inline_0}"); } public void testInlineBlockMutableArgs4() { test("function foo(x){x+x}foo({})", "{var x$$inline_0={};" + "x$$inline_0+x$$inline_0}"); } public void testShadowVariables1() { // The Normalize pass now guarantees that that globals are never shadowed // by locals. // "foo" is inlined here as its parameter "a" doesn't conflict. // "bar" is assigned a new name. test("var a=0;" + "function foo(a){return 3+a}" + "function bar(){var a=foo(4)}" + "bar();", "var a=0;" + "{var a$$inline_0=3+4}"); } public void testShadowVariables2() { // "foo" is inlined here as its parameter "a" doesn't conflict. // "bar" is inlined as its uses global "a", and does introduce any new // globals. test("var a=0;" + "function foo(a){return 3+a}" + "function bar(){a=foo(4)}" + "bar()", "var a=0;" + "{a=3+4}"); } public void testShadowVariables3() { // "foo" is inlined into exported "_bar", aliasing foo's "a". test("var a=0;" + "function foo(){var a=2;return 3+a}" + "function _bar(){a=foo()}", "var a=0;" + "function _bar(){{var a$$inline_0=2;" + "a=3+a$$inline_0}}"); } public void testShadowVariables4() { // "foo" is inlined. // block access to global "a". test("var a=0;" + "function foo(){return 3+a}" + "function _bar(a){a=foo(4)+a}", "var a=0;function _bar(a$$1){" + "a$$1=" + "3+a+a$$1}"); } public void testShadowVariables5() { // Can't yet inline multiple statements functions into expressions // (though some are possible using the COMMA operator). allowBlockInlining = false; testSame("var a=0;" + "function foo(){var a=4;return 3+a}" + "function _bar(a){a=foo(4)+a}"); } public void testShadowVariables6() { test("var a=0;" + "function foo(){var a=4;return 3+a}" + "function _bar(a){a=foo(4)}", "var a=0;function _bar(a$$2){{" + "var a$$inline_0=4;" + "a$$2=3+a$$inline_0}}"); } public void testShadowVariables7() { assumeMinimumCapture = false; test("var a=3;" + "function foo(){return a}" + "(function(){var a=5;(function(){foo()})()})()", "var a=3;" + "{var a$$inline_0=5;{a}}" ); assumeMinimumCapture = true; test("var a=3;" + "function foo(){return a}" + "(function(){var a=5;(function(){foo()})()})()", "var a=3;" + "{var a$$inline_1=5;{a}}" ); } public void testShadowVariables8() { // this should be inlined test("var a=0;" + "function foo(){return 3}" + "function _bar(){var a=foo()}", "var a=0;" + "function _bar(){var a=3}"); } public void testShadowVariables9() { // this should be inlined too [even if the global is not declared] test("function foo(){return 3}" + "function _bar(){var a=foo()}", "function _bar(){var a=3}"); } public void testShadowVariables10() { // callee var must be renamed. test("var a;function foo(){return a}" + "function _bar(){var a=foo()}", "var a;function _bar(){var a$$1=a}"); } public void testShadowVariables11() { // The call has a local variable // which collides with the function being inlined test("var a=0;var b=1;" + "function foo(){return a+a}" + "function _bar(){var a=foo();alert(a)}", "var a=0;var b=1;" + "function _bar(){var a$$1=a+a;" + "alert(a$$1)}" ); } public void testShadowVariables12() { // 2 globals colliding test("var a=0;var b=1;" + "function foo(){return a+b}" + "function _bar(){var a=foo(),b;alert(a)}", "var a=0;var b=1;" + "function _bar(){var a$$1=a+b," + "b$$1;" + "alert(a$$1)}"); } public void testShadowVariables13() { // The only change is to remove the collision test("var a=0;var b=1;" + "function foo(){return a+a}" + "function _bar(){var c=foo();alert(c)}", "var a=0;var b=1;" + "function _bar(){var c=a+a;alert(c)}"); } public void testShadowVariables14() { // There is a collision even though it is not read. test("var a=0;var b=1;" + "function foo(){return a+b}" + "function _bar(){var c=foo(),b;alert(c)}", "var a=0;var b=1;" + "function _bar(){var c=a+b," + "b$$1;alert(c)}"); } public void testShadowVariables15() { // Both parent and child reference a global test("var a=0;var b=1;" + "function foo(){return a+a}" + "function _bar(){var c=foo();alert(c+a)}", "var a=0;var b=1;" + "function _bar(){var c=a+a;alert(c+a)}"); } public void testShadowVariables16() { assumeMinimumCapture = false; // Inline functions defined as a child of the CALL node. test("var a=3;" + "function foo(){return a}" + "(function(){var a=5;(function(){foo()})()})()", "var a=3;" + "{var a$$inline_0=5;{a}}" ); assumeMinimumCapture = true; // Inline functions defined as a child of the CALL node. test("var a=3;" + "function foo(){return a}" + "(function(){var a=5;(function(){foo()})()})()", "var a=3;" + "{var a$$inline_1=5;{a}}" ); } public void testShadowVariables17() { test("var a=0;" + "function bar(){return a+a}" + "function foo(){return bar()}" + "function _goo(){var a=2;var x=foo();}", "var a=0;" + "function _goo(){var a$$1=2;var x=a+a}"); } public void testShadowVariables18() { test("var a=0;" + "function bar(){return a+a}" + "function foo(){var a=3;return bar()}" + "function _goo(){var a=2;var x=foo();}", "var a=0;" + "function _goo(){var a$$2=2;var x;" + "{var a$$inline_0=3;x=a+a}}"); } public void testCostBasedInlining1() { testSame( "function foo(a){return a}" + "foo=new Function(\"return 1\");" + "foo(1)"); } public void testCostBasedInlining2() { // Baseline complexity tests. // Single call, function not removed. test( "function foo(a){return a}" + "var b=foo;" + "function _t1(){return foo(1)}", "function foo(a){return a}" + "var b=foo;" + "function _t1(){return 1}"); } public void testCostBasedInlining3() { // Two calls, function not removed. test( "function foo(a,b){return a+b}" + "var b=foo;" + "function _t1(){return foo(1,2)}" + "function _t2(){return foo(2,3)}", "function foo(a,b){return a+b}" + "var b=foo;" + "function _t1(){return 1+2}" + "function _t2(){return 2+3}"); } public void testCostBasedInlining4() { // Two calls, function not removed. // Here there isn't enough savings to justify inlining. testSame( "function foo(a,b){return a+b+a+b}" + "var b=foo;" + "function _t1(){return foo(1,2)}" + "function _t2(){return foo(2,3)}"); } public void testCostBasedInlining5() { // Here there is enough savings to justify inlining. test( "function foo(a,b){return a+b+a+b}" + "function _t1(){return foo(1,2)}" + "function _t2(){return foo(2,3)}", "function _t1(){return 1+2+1+2}" + "function _t2(){return 2+3+2+3}"); } public void testCostBasedInlining6() { // Here we have a threshold test. // Do inline here: test( "function foo(a,b){return a+b+a+b+a+b+a+b+4+5+6+7+8+9+1+2+3+4+5}" + "function _t1(){return foo(1,2)}" + "function _t2(){return foo(2,3)}", "function _t1(){return 1+2+1+2+1+2+1+2+4+5+6+7+8+9+1+2+3+4+5}" + "function _t2(){return 2+3+2+3+2+3+2+3+4+5+6+7+8+9+1+2+3+4+5}"); } public void testCostBasedInlining7() { // Don't inline here (not enough savings): testSame( "function foo(a,b){" + " return a+b+a+b+a+b+a+b+4+5+6+7+8+9+1+2+3+4+5+6}" + "function _t1(){return foo(1,2)}" + "function _t2(){return foo(2,3)}"); } public void testCostBasedInlining8() { // Verify multiple references in the same statement: // Here "f" is not known to be removable, as it is a used as parameter // and is not known to be side-effect free. The first call to f() can // not be inlined on the first pass (as the call to f() as a parameter // prevents this). However, the call to f() would be inlinable, if it // is small enough to be inlined without removing the function declaration. // but it is not in this first test. allowBlockInlining = false; testSame("function f(a){return 1 + a + a;}" + "var a = f(f(1));"); } public void testCostBasedInlining9() { // Here both direct and block inlining is used. The call to f as a // parameter is inlined directly, which the call to f with f as a parameter // is inlined using block inlining. test("function f(a){return 1 + a + a;}" + "var a = f(f(1));", "var a;" + "{var a$$inline_0=1+1+1;" + "a=1+a$$inline_0+a$$inline_0}"); } public void testCostBasedInlining10() { // But it is small enough here, and on the second iteration, the remaining // call to f() is inlined, as there is no longer a possible side-effect-ing // parameter. allowBlockInlining = false; test("function f(a){return a + a;}" + "var a = f(f(1));", "var a= 1+1+(1+1);"); } public void testCostBasedInlining11() { // With block inlining test("function f(a){return a + a;}" + "var a = f(f(1))", "var a;" + "{var a$$inline_0=1+1;" + "a=a$$inline_0+a$$inline_0}"); } public void testCostBasedInlining12() { test("function f(a){return 1 + a + a;}" + "var a = f(1) + f(2);", "var a=1+1+1+(1+2+2)"); } public void testCostBasedInliningComplex1() { testSame( "function foo(a){a()}" + "foo=new Function(\"return 1\");" + "foo(1)"); } public void testCostBasedInliningComplex2() { // Baseline complexity tests. // Single call, function not removed. test( "function foo(a){a()}" + "var b=foo;" + "function _t1(){foo(x)}", "function foo(a){a()}" + "var b=foo;" + "function _t1(){{x()}}"); } public void testCostBasedInliningComplex3() { // Two calls, function not removed. test( "function foo(a,b){a+b}" + "var b=foo;" + "function _t1(){foo(1,2)}" + "function _t2(){foo(2,3)}", "function foo(a,b){a+b}" + "var b=foo;" + "function _t1(){{1+2}}" + "function _t2(){{2+3}}"); } public void testCostBasedInliningComplex4() { // Two calls, function not removed. // Here there isn't enough savings to justify inlining. testSame( "function foo(a,b){a+b+a+b}" + "var b=foo;" + "function _t1(){foo(1,2)}" + "function _t2(){foo(2,3)}"); } public void testCostBasedInliningComplex5() { // Here there is enough savings to justify inlining. test( "function foo(a,b){a+b+a+b}" + "function _t1(){foo(1,2)}" + "function _t2(){foo(2,3)}", "function _t1(){{1+2+1+2}}" + "function _t2(){{2+3+2+3}}"); } public void testCostBasedInliningComplex6() { // Here we have a threshold test. // Do inline here: test( "function foo(a,b){a+b+a+b+a+b+a+b+4+5+6+7+8+9+1}" + "function _t1(){foo(1,2)}" + "function _t2(){foo(2,3)}", "function _t1(){{1+2+1+2+1+2+1+2+4+5+6+7+8+9+1}}" + "function _t2(){{2+3+2+3+2+3+2+3+4+5+6+7+8+9+1}}"); } public void testCostBasedInliningComplex7() { // Don't inline here (not enough savings): testSame( "function foo(a,b){a+b+a+b+a+b+a+b+4+5+6+7+8+9+1+2}" + "function _t1(){foo(1,2)}" + "function _t2(){foo(2,3)}"); } public void testCostBasedInliningComplex8() { // Verify multiple references in the same statement. testSame("function _f(a){1+a+a}" + "a=_f(1)+_f(1)"); } public void testCostBasedInliningComplex9() { test("function f(a){1 + a + a;}" + "f(1);f(2);", "{1+1+1}{1+2+2}"); } public void testDoubleInlining1() { allowBlockInlining = false; test("var foo = function(a) { return getWindow(a); };" + "var bar = function(b) { return b; };" + "foo(bar(x));", "getWindow(x)"); } public void testDoubleInlining2() { test("var foo = function(a) { return getWindow(a); };" + "var bar = function(b) { return b; };" + "foo(bar(x));", "{getWindow(x)}"); } public void testNoInlineOfNonGlobalFunction1() { test("var g;function _f(){function g(){return 0}}" + "function _h(){return g()}", "var g;function _f(){}" + "function _h(){return g()}"); } public void testNoInlineOfNonGlobalFunction2() { test("var g;function _f(){var g=function(){return 0}}" + "function _h(){return g()}", "var g;function _f(){}" + "function _h(){return g()}"); } public void testNoInlineOfNonGlobalFunction3() { test("var g;function _f(){var g=function(){return 0}}" + "function _h(){return g()}", "var g;function _f(){}" + "function _h(){return g()}"); } public void testNoInlineOfNonGlobalFunction4() { test("var g;function _f(){function g(){return 0}}" + "function _h(){return g()}", "var g;function _f(){}" + "function _h(){return g()}"); } public void testNoInlineMaskedFunction() { // Normalization makes this test of marginal value. // The unreferenced function is removed. test("var g=function(){return 0};" + "function _f(g){return g()}", "function _f(g$$1){return g$$1()}"); } public void testNoInlineNonFunction() { testSame("var g=3;function _f(){return g()}"); } public void testInlineCall() { test("function f(g) { return g.h(); } f('x');", "\"x\".h()"); } public void testInlineFunctionWithArgsMismatch1() { test("function f(g) { return g; } f();", "void 0"); } public void testInlineFunctionWithArgsMismatch2() { test("function f() { return 0; } f(1);", "0"); } public void testInlineFunctionWithArgsMismatch3() { test("function f(one, two, three) { return one + two + three; } f(1);", "1+void 0+void 0"); } public void testInlineFunctionWithArgsMismatch4() { test("function f(one, two, three) { return one + two + three; }" + "f(1,2,3,4,5);", "1+2+3"); } public void testArgumentsWithSideEffectsNeverInlined1() { allowBlockInlining = false; testSame("function f(){return 0} f(new goo());"); } public void testArgumentsWithSideEffectsNeverInlined2() { allowBlockInlining = false; testSame("function f(g,h){return h+g}f(g(),h());"); } public void testOneSideEffectCallDoesNotRuinOthers() { allowBlockInlining = false; test("function f(){return 0}f(new goo());f()", "function f(){return 0}f(new goo());0"); } public void testComplexInlineNoResultNoParamCall1() { test("function f(){a()}f()", "{a()}"); } public void testComplexInlineNoResultNoParamCall2() { test("function f(){if (true){return;}else;} f();", "{JSCompiler_inline_label_f_0:{" + "if(true)break JSCompiler_inline_label_f_0;else;}}"); } public void testComplexInlineNoResultNoParamCall3() { // We now allow vars in the global space. // Don't inline into vars into global scope. // testSame("function f(){a();b();var z=1+1}f()"); // But do inline into functions test("function f(){a();b();var z=1+1}function _foo(){f()}", "function _foo(){{a();b();var z$$inline_0=1+1}}"); } public void testComplexInlineNoResultWithParamCall1() { test("function f(x){a(x)}f(1)", "{a(1)}"); } public void testComplexInlineNoResultWithParamCall2() { test("function f(x,y){a(x)}var b=1;f(1,b)", "var b=1;{a(1)}"); } public void testComplexInlineNoResultWithParamCall3() { test("function f(x,y){if (x) y(); return true;}var b=1;f(1,b)", "var b=1;{if(1)b();true}"); } public void testComplexInline1() { test("function f(){if (true){return;}else;} z=f();", "{JSCompiler_inline_label_f_0:" + "{if(true){z=void 0;" + "break JSCompiler_inline_label_f_0}else;z=void 0}}"); } public void testComplexInline2() { test("function f(){if (true){return;}else return;} z=f();", "{JSCompiler_inline_label_f_0:{if(true){z=void 0;" + "break JSCompiler_inline_label_f_0}else{z=void 0;" + "break JSCompiler_inline_label_f_0}z=void 0}}"); } public void testComplexInline3() { test("function f(){if (true){return 1;}else return 0;} z=f();", "{JSCompiler_inline_label_f_0:{if(true){z=1;" + "break JSCompiler_inline_label_f_0}else{z=0;" + "break JSCompiler_inline_label_f_0}z=void 0}}"); } public void testComplexInline4() { test("function f(x){a(x)} z = f(1)", "{a(1);z=void 0}"); } public void testComplexInline5() { test("function f(x,y){a(x)}var b=1;z=f(1,b)", "var b=1;{a(1);z=void 0}"); } public void testComplexInline6() { test("function f(x,y){if (x) y(); return true;}var b=1;z=f(1,b)", "var b=1;{if(1)b();z=true}"); } public void testComplexInline7() { test("function f(x,y){if (x) return y(); else return true;}" + "var b=1;z=f(1,b)", "var b=1;{JSCompiler_inline_label_f_2:{if(1){z=b();" + "break JSCompiler_inline_label_f_2}else{z=true;" + "break JSCompiler_inline_label_f_2}z=void 0}}"); } public void testComplexInline8() { test("function f(x){a(x)}var z=f(1)", "var z;{a(1);z=void 0}"); } public void testComplexInlineVars1() { test("function f(){if (true){return;}else;}var z=f();", "var z;{JSCompiler_inline_label_f_0:{" + "if(true){z=void 0;break JSCompiler_inline_label_f_0}else;z=void 0}}"); } public void testComplexInlineVars2() { test("function f(){if (true){return;}else return;}var z=f();", "var z;{JSCompiler_inline_label_f_0:{" + "if(true){z=void 0;break JSCompiler_inline_label_f_0" + "}else{" + "z=void 0;break JSCompiler_inline_label_f_0}z=void 0}}"); } public void testComplexInlineVars3() { test("function f(){if (true){return 1;}else return 0;}var z=f();", "var z;{JSCompiler_inline_label_f_0:{if(true){" + "z=1;break JSCompiler_inline_label_f_0" + "}else{" + "z=0;break JSCompiler_inline_label_f_0}z=void 0}}"); } public void testComplexInlineVars4() { test("function f(x){a(x)}var z = f(1)", "var z;{a(1);z=void 0}"); } public void testComplexInlineVars5() { test("function f(x,y){a(x)}var b=1;var z=f(1,b)", "var b=1;var z;{a(1);z=void 0}"); } public void testComplexInlineVars6() { test("function f(x,y){if (x) y(); return true;}var b=1;var z=f(1,b)", "var b=1;var z;{if(1)b();z=true}"); } public void testComplexInlineVars7() { test("function f(x,y){if (x) return y(); else return true;}" + "var b=1;var z=f(1,b)", "var b=1;var z;" + "{JSCompiler_inline_label_f_2:{if(1){z=b();" + "break JSCompiler_inline_label_f_2" + "}else{" + "z=true;break JSCompiler_inline_label_f_2}z=void 0}}"); } public void testComplexInlineVars8() { test("function f(x){a(x)}var x;var z=f(1)", "var x;var z;{a(1);z=void 0}"); } public void testComplexInlineVars9() { test("function f(x){a(x)}var x;var z=f(1);var y", "var x;var z;{a(1);z=void 0}var y"); } public void testComplexInlineVars10() { test("function f(x){a(x)}var x=blah();var z=f(1);var y=blah();", "var x=blah();var z;{a(1);z=void 0}var y=blah()"); } public void testComplexInlineVars11() { test("function f(x){a(x)}var x=blah();var z=f(1);var y;", "var x=blah();var z;{a(1);z=void 0}var y"); } public void testComplexInlineVars12() { test("function f(x){a(x)}var x;var z=f(1);var y=blah();", "var x;var z;{a(1);z=void 0}var y=blah()"); } public void testComplexInlineInExpresssions1() { test("function f(){a()}var z=f()", "var z;{a();z=void 0}"); } public void testComplexInlineInExpresssions2() { test("function f(){a()}c=z=f()", "var JSCompiler_inline_result$$0;" + "{a();JSCompiler_inline_result$$0=void 0;}" + "c=z=JSCompiler_inline_result$$0"); } public void testComplexInlineInExpresssions3() { test("function f(){a()}c=z=f()", "var JSCompiler_inline_result$$0;" + "{a();JSCompiler_inline_result$$0=void 0;}" + "c=z=JSCompiler_inline_result$$0"); } public void testComplexInlineInExpresssions4() { test("function f(){a()}if(z=f());", "var JSCompiler_inline_result$$0;" + "{a();JSCompiler_inline_result$$0=void 0;}" + "if(z=JSCompiler_inline_result$$0);"); } public void testComplexInlineInExpresssions5() { test("function f(){a()}if(z.y=f());", "var JSCompiler_temp_const$$0=z;" + "var JSCompiler_inline_result$$1;" + "{a();JSCompiler_inline_result$$1=void 0;}" + "if(JSCompiler_temp_const$$0.y=JSCompiler_inline_result$$1);"); } public void testComplexNoInline1() { testSame("function f(){a()}while(z=f())continue"); } public void testComplexNoInline2() { testSame("function f(){a()}do;while(z=f())"); } public void testComplexSample() { String result = "" + "{{" + "var styleSheet$$inline_2=null;" + "if(goog$userAgent$IE)" + "styleSheet$$inline_2=0;" + "else " + "var head$$inline_3=0;" + "{" + "var element$$inline_4=" + "styleSheet$$inline_2;" + "var stylesString$$inline_5=a;" + "if(goog$userAgent$IE)" + "element$$inline_4.cssText=" + "stylesString$$inline_5;" + "else " + "{" + "var propToSet$$inline_6=" + "\"innerText\";" + "element$$inline_4[" + "propToSet$$inline_6]=" + "stylesString$$inline_5" + "}" + "}" + "styleSheet$$inline_2" + "}}"; test("var foo = function(stylesString, opt_element) { " + "var styleSheet = null;" + "if (goog$userAgent$IE)" + "styleSheet = 0;" + "else " + "var head = 0;" + "" + "goo$zoo(styleSheet, stylesString);" + "return styleSheet;" + " };\n " + "var goo$zoo = function(element, stylesString) {" + "if (goog$userAgent$IE)" + "element.cssText = stylesString;" + "else {" + "var propToSet = 'innerText';" + "element[propToSet] = stylesString;" + "}" + "};" + "(function(){foo(a,b);})();", result); } public void testComplexSampleNoInline() { // This is the result we would expect if we could handle "foo = function" String result = "foo=function(stylesString,opt_element){" + "var styleSheet=null;" + "if(goog$userAgent$IE){" + "styleSheet=0" + "}else{" + "var head=0" + "}" + "{var JSCompiler_inline_element_0=styleSheet;" + "var JSCompiler_inline_stylesString_1=stylesString;" + "if(goog$userAgent$IE){" + "JSCompiler_inline_element_0.cssText=" + "JSCompiler_inline_stylesString_1" + "}else{" + "var propToSet=goog$userAgent$WEBKIT?\"innerText\":\"innerHTML\";" + "JSCompiler_inline_element_0[propToSet]=" + "JSCompiler_inline_stylesString_1" + "}}" + "return styleSheet" + "}"; testSame( "foo=function(stylesString,opt_element){" + "var styleSheet=null;" + "if(goog$userAgent$IE)" + "styleSheet=0;" + "else " + "var head=0;" + "" + "goo$zoo(styleSheet,stylesString);" + "return styleSheet" + "};" + "goo$zoo=function(element,stylesString){" + "if(goog$userAgent$IE)" + "element.cssText=stylesString;" + "else{" + "var propToSet=goog$userAgent$WEBKIT?\"innerText\":\"innerHTML\";" + "element[propToSet]=stylesString" + "}" + "}"); } // Test redefinition of parameter name. public void testComplexNoVarSub() { test( "function foo(x){" + "var x;" + "y=x" + "}" + "foo(1)", "{y=1}" ); } public void testComplexFunctionWithFunctionDefinition1() { test("function f(){call(function(){return})}f()", "{call(function(){return})}"); } public void testComplexFunctionWithFunctionDefinition2() { assumeMinimumCapture = false; // Don't inline if local names might be captured. testSame("function f(a){call(function(){return})}f()"); assumeMinimumCapture = true; test("(function(){" + "var f = function(a){call(function(){return a})};f()})()", "{{var a$$inline_0=void 0;call(function(){return a$$inline_0})}}"); } public void testComplexFunctionWithFunctionDefinition2a() { assumeMinimumCapture = false; // Don't inline if local names might be captured. testSame("(function(){" + "var f = function(a){call(function(){return a})};f()})()"); assumeMinimumCapture = true; test("(function(){" + "var f = function(a){call(function(){return a})};f()})()", "{{var a$$inline_0=void 0;call(function(){return a$$inline_0})}}"); } public void testComplexFunctionWithFunctionDefinition3() { assumeMinimumCapture = false; // Don't inline if local names might need to be captured. testSame("function f(){var a; call(function(){return a})}f()"); assumeMinimumCapture = true; test("function f(){var a; call(function(){return a})}f()", "{var a$$inline_0;call(function(){return a$$inline_0})}"); } public void testDecomposePlusEquals() { test("function f(){a=1;return 1} var x = 1; x += f()", "var x = 1;" + "var JSCompiler_temp_const$$0 = x;" + "var JSCompiler_inline_result$$1;" + "{a=1;" + " JSCompiler_inline_result$$1=1}" + "x = JSCompiler_temp_const$$0 + JSCompiler_inline_result$$1;"); } public void testDecomposeFunctionExpressionInCall() { test( "(function(map){descriptions_=map})(\n" + "function(){\n" + "var ret={};\n" + "ret[ONE]='a';\n" + "ret[TWO]='b';\n" + "return ret\n" + "}()\n" + ");", "var JSCompiler_inline_result$$0;" + "{" + "var ret$$inline_1={};\n" + "ret$$inline_1[ONE]='a';\n" + "ret$$inline_1[TWO]='b';\n" + "JSCompiler_inline_result$$0 = ret$$inline_1;\n" + "}" + "{" + "descriptions_=JSCompiler_inline_result$$0;" + "}" ); } public void testInlineConstructor1() { test("function f() {} function _g() {f.call(this)}", "function _g() {void 0}"); } public void testInlineConstructor2() { test("function f() {} f.prototype.a = 0; function _g() {f.call(this)}", "function f() {} f.prototype.a = 0; function _g() {void 0}"); } public void testInlineConstructor3() { test("function f() {x.call(this)} f.prototype.a = 0;" + "function _g() {f.call(this)}", "function f() {x.call(this)} f.prototype.a = 0;" + "function _g() {{x.call(this)}}"); } public void testInlineConstructor4() { test("function f() {x.call(this)} f.prototype.a = 0;" + "function _g() {var t = f.call(this)}", "function f() {x.call(this)} f.prototype.a = 0;" + "function _g() {var t; {x.call(this); t = void 0}}"); } public void testFunctionExpressionInlining1() { test("(function(){})()", "void 0"); } public void testFunctionExpressionInlining2() { test("(function(){foo()})()", "{foo()}"); } public void testFunctionExpressionInlining3() { test("var a = (function(){return foo()})()", "var a = foo()"); } public void testFunctionExpressionInlining4() { test("var a; a = 1 + (function(){return foo()})()", "var a; a = 1 + foo()"); } public void testFunctionExpressionCallInlining1() { test("(function(){}).call(this)", "void 0"); } public void testFunctionExpressionCallInlining2() { test("(function(){foo(this)}).call(this)", "{foo(this)}"); } public void testFunctionExpressionCallInlining3() { test("var a = (function(){return foo(this)}).call(this)", "var a = foo(this)"); } public void testFunctionExpressionCallInlining4() { test("var a; a = 1 + (function(){return foo(this)}).call(this)", "var a; a = 1 + foo(this)"); } public void testFunctionExpressionCallInlining5() { test("a:(function(){return foo()})()", "a:foo()"); } public void testFunctionExpressionCallInlining6() { test("a:(function(){return foo()}).call(this)", "a:foo()"); } public void testFunctionExpressionCallInlining7() { test("a:(function(){})()", "a:void 0"); } public void testFunctionExpressionCallInlining8() { test("a:(function(){}).call(this)", "a:void 0"); } public void testFunctionExpressionCallInlining9() { // ... with unused recursive name. test("(function foo(){})()", "void 0"); } public void testFunctionExpressionCallInlining10() { // ... with unused recursive name. test("(function foo(){}).call(this)", "void 0"); } public void testFunctionExpressionCallInlining11a() { // Inline functions that return inner functions. test("((function(){return function(){foo()}})())();", "{foo()}"); } public void testFunctionExpressionCallInlining11b() { assumeMinimumCapture = false; // Can't inline functions that return inner functions and have local names. testSame("((function(){var a; return function(){foo()}})())();"); assumeMinimumCapture = true; test( "((function(){var a; return function(){foo()}})())();", "var JSCompiler_inline_result$$0;" + "{var a$$inline_1;" + "JSCompiler_inline_result$$0=function(){foo()};}" + "JSCompiler_inline_result$$0()"); } public void testFunctionExpressionCallInlining11c() { // TODO(johnlenz): Can inline, not temps needed. assumeMinimumCapture = false; testSame("function _x() {" + " ((function(){return function(){foo()}})())();" + "}"); assumeMinimumCapture = true; test( "function _x() {" + " ((function(){return function(){foo()}})())();" + "}", "function _x() {" + " {foo()}" + "}"); } public void testFunctionExpressionCallInlining11d() { // TODO(johnlenz): Can inline into a function containing eval, if // no names are introduced. assumeMinimumCapture = false; testSame("function _x() {" + " eval();" + " ((function(){return function(){foo()}})())();" + "}"); assumeMinimumCapture = true; test( "function _x() {" + " eval();" + " ((function(){return function(){foo()}})())();" + "}", "function _x() {" + " eval();" + " {foo()}" + "}"); } public void testFunctionExpressionCallInlining11e() { // No, don't inline into a function containing eval, // if temps are introduced. assumeMinimumCapture = false; testSame("function _x() {" + " eval();" + " ((function(a){return function(){foo()}})())();" + "}"); assumeMinimumCapture = true; test("function _x() {" + " eval();" + " ((function(a){return function(){foo()}})())();" + "}", "function _x() {" + " eval();" + " {foo();}" + "}"); } public void testFunctionExpressionCallInlining12() { // Can't inline functions that recurse. testSame("(function foo(){foo()})()"); } public void testFunctionExpressionOmega() { // ... with unused recursive name. test("(function (f){f(f)})(function(f){f(f)})", "{var f$$inline_0=function(f$$1){f$$1(f$$1)};" + "{{f$$inline_0(f$$inline_0)}}}"); } public void testLocalFunctionInlining1() { test("function _f(){ function g() {} g() }", "function _f(){ void 0 }"); } public void testLocalFunctionInlining2() { test("function _f(){ function g() {foo(); bar();} g() }", "function _f(){ {foo(); bar();} }"); } public void testLocalFunctionInlining3() { test("function _f(){ function g() {foo(); bar();} g() }", "function _f(){ {foo(); bar();} }"); } public void testLocalFunctionInlining4() { test("function _f(){ function g() {return 1} return g() }", "function _f(){ return 1 }"); } public void testLocalFunctionInlining5() { testSame("function _f(){ function g() {this;} g() }"); } public void testLocalFunctionInlining6() { testSame("function _f(){ function g() {this;} return g; }"); } public void testLocalFunctionInliningOnly1() { this.allowGlobalFunctionInlining = true; test("function f(){} f()", "void 0;"); this.allowGlobalFunctionInlining = false; testSame("function f(){} f()"); } public void testLocalFunctionInliningOnly2() { this.allowGlobalFunctionInlining = false; testSame("function f(){} f()"); test("function f(){ function g() {return 1} return g() }; f();", "function f(){ return 1 }; f();"); } public void testLocalFunctionInliningOnly3() { this.allowGlobalFunctionInlining = false; testSame("function f(){} f()"); test("(function(){ function g() {return 1} return g() })();", "(function(){ return 1 })();"); } public void testLocalFunctionInliningOnly4() { this.allowGlobalFunctionInlining = false; testSame("function f(){} f()"); test("(function(){ return (function() {return 1})() })();", "(function(){ return 1 })();"); } public void testInlineWithThis1() { assumeStrictThis = false; // If no "this" is provided it might need to be coerced to the global // "this". testSame("function f(){} f.call();"); testSame("function f(){this} f.call();"); assumeStrictThis = true; // In strict mode, "this" is never coerced so we can use the provided value. test("function f(){} f.call();", "{}"); test("function f(){this} f.call();", "{void 0;}"); } public void testInlineWithThis2() { // "this" can always be replaced with "this" assumeStrictThis = false; test("function f(){} f.call(this);", "void 0"); assumeStrictThis = true; test("function f(){} f.call(this);", "void 0"); } public void testInlineWithThis3() { assumeStrictThis = false; // If no "this" is provided it might need to be coerced to the global // "this". testSame("function f(){} f.call([]);"); assumeStrictThis = true; // In strict mode, "this" is never coerced so we can use the provided value. test("function f(){} f.call([]);", "{}"); } public void testInlineWithThis4() { assumeStrictThis = false; // If no "this" is provided it might need to be coerced to the global // "this". testSame("function f(){} f.call(new g);"); assumeStrictThis = true; // In strict mode, "this" is never coerced so we can use the provided value. test("function f(){} f.call(new g);", "{var JSCompiler_inline_this_0=new g}"); } public void testInlineWithThis5() { assumeStrictThis = false; // If no "this" is provided it might need to be coerced to the global // "this". testSame("function f(){} f.call(g());"); assumeStrictThis = true; // In strict mode, "this" is never coerced so we can use the provided value. test("function f(){} f.call(g());", "{var JSCompiler_inline_this_0=g()}"); } public void testInlineWithThis6() { assumeStrictThis = false; // If no "this" is provided it might need to be coerced to the global // "this". testSame("function f(){this} f.call(new g);"); assumeStrictThis = true; // In strict mode, "this" is never coerced so we can use the provided value. test("function f(){this} f.call(new g);", "{var JSCompiler_inline_this_0=new g;JSCompiler_inline_this_0}"); } public void testInlineWithThis7() { assumeStrictThis = true; // In strict mode, "this" is never coerced so we can use the provided value. test("function f(a){a=1;this} f.call();", "{var a$$inline_0=void 0; a$$inline_0=1; void 0;}"); test("function f(a){a=1;this} f.call(x, x);", "{var a$$inline_0=x; a$$inline_0=1; x;}"); } // http://en.wikipedia.org/wiki/Fixed_point_combinator#Y_combinator public void testFunctionExpressionYCombinator() { assumeMinimumCapture = false; testSame( "var factorial = ((function(M) {\n" + " return ((function(f) {\n" + " return M(function(arg) {\n" + " return (f(f))(arg);\n" + " })\n" + " })\n" + " (function(f) {\n" + " return M(function(arg) {\n" + " return (f(f))(arg);\n" + " })\n" + " }));\n" + " })\n" + " (function(f) {\n" + " return function(n) {\n" + " if (n === 0)\n" + " return 1;\n" + " else\n" + " return n * f(n - 1);\n" + " };\n" + " }));\n" + "\n" + "factorial(5)\n"); assumeMinimumCapture = true; test( "var factorial = ((function(M) {\n" + " return ((function(f) {\n" + " return M(function(arg) {\n" + " return (f(f))(arg);\n" + " })\n" + " })\n" + " (function(f) {\n" + " return M(function(arg) {\n" + " return (f(f))(arg);\n" + " })\n" + " }));\n" + " })\n" + " (function(f) {\n" + " return function(n) {\n" + " if (n === 0)\n" + " return 1;\n" + " else\n" + " return n * f(n - 1);\n" + " };\n" + " }));\n" + "\n" + "factorial(5)\n", "var factorial;\n" + "{\n" + "var M$$inline_4 = function(f$$2) {\n" + " return function(n){if(n===0)return 1;else return n*f$$2(n-1)}\n" + "};\n" + "{\n" + "var f$$inline_0=function(f$$inline_7){\n" + " return M$$inline_4(\n" + " function(arg$$inline_8){\n" + " return f$$inline_7(f$$inline_7)(arg$$inline_8)\n" + " })\n" + "};\n" + "factorial=M$$inline_4(\n" + " function(arg$$inline_1){\n" + " return f$$inline_0(f$$inline_0)(arg$$inline_1)\n" + "});\n" + "}\n" + "}" + "factorial(5)"); } public void testRenamePropertyFunction() { testSame("function JSCompiler_renameProperty(x) {return x} " + "JSCompiler_renameProperty('foo')"); } public void testReplacePropertyFunction() { // baseline: an alias doesn't prevents declaration removal, but not // inlining. test("function f(x) {return x} " + "foo(window, f); f(1)", "function f(x) {return x} " + "foo(window, f); 1"); // a reference passed to JSCompiler_ObjectPropertyString prevents inlining // as well. testSame("function f(x) {return x} " + "new JSCompiler_ObjectPropertyString(window, f); f(1)"); } public void testInlineWithClosureContainingThis() { test("(function (){return f(function(){return this})})();", "f(function(){return this})"); } public void testIssue5159924a() { test("function f() { if (x()) return y() }\n" + "while(1){ var m = f() || z() }", "for(;1;) {" + " var JSCompiler_inline_result$$0;" + " {" + " JSCompiler_inline_label_f_1: {" + " if(x()) {" + " JSCompiler_inline_result$$0 = y();" + " break JSCompiler_inline_label_f_1" + " }" + " JSCompiler_inline_result$$0 = void 0;" + " }" + " }" + " var m=JSCompiler_inline_result$$0 || z()" + "}"); } public void testIssue5159924b() { test("function f() { if (x()) return y() }\n" + "while(1){ var m = f() }", "for(;1;){" + " var m;" + " {" + " JSCompiler_inline_label_f_0: { " + " if(x()) {" + " m = y();" + " break JSCompiler_inline_label_f_0" + " }" + " m = void 0" + " }" + " }" + "}"); } public void testInlineObject() { new StringCompare().testInlineObject(); } private static class StringCompare extends CompilerTestCase { private boolean allowGlobalFunctionInlining = true; StringCompare() { super("", false); this.enableNormalize(); this.enableMarkNoSideEffects(); } @Override public void setUp() throws Exception { super.setUp(); super.enableLineNumberCheck(true); allowGlobalFunctionInlining = true; } @Override protected CompilerPass getProcessor(Compiler compiler) { compiler.resetUniqueNameId(); return new InlineFunctions( compiler, compiler.getUniqueNameIdSupplier(), allowGlobalFunctionInlining, true, // allowLocalFunctionInlining true, // allowBlockInlining true, // assumeStrictThis true); // assumeMinimumCapture } public void testInlineObject() { allowGlobalFunctionInlining = false; // TODO(johnlenz): normalize the AST so an AST comparison can be done. // As is, the expected AST does not match the actual correct result: // The AST matches "g.a()" with a FREE_CALL annotation, but this as // expected string would fail as it won't be mark as a free call. // "(0,g.a)()" matches the output, but not the resulting AST. test("function inner(){function f(){return g.a}(f())()}", "function inner(){(0,g.a)()}"); } } public void testBug4944818() { test( "var getDomServices_ = function(self) {\n" + " if (!self.domServices_) {\n" + " self.domServices_ = goog$component$DomServices.get(" + " self.appContext_);\n" + " }\n" + "\n" + " return self.domServices_;\n" + "};\n" + "\n" + "var getOwnerWin_ = function(self) {\n" + " return getDomServices_(self).getDomHelper().getWindow();\n" + "};\n" + "\n" + "HangoutStarter.prototype.launchHangout = function() {\n" + " var self = a.b;\n" + " var myUrl = new goog.Uri(getOwnerWin_(self).location.href);\n" + "};", "HangoutStarter.prototype.launchHangout = function() { " + " var self$$2 = a.b;" + " var JSCompiler_temp_const$$0 = goog.Uri;" + " var JSCompiler_inline_result$$1;" + " {" + " var self$$inline_2 = self$$2;" + " if (!self$$inline_2.domServices_) {" + " self$$inline_2.domServices_ = goog$component$DomServices.get(" + " self$$inline_2.appContext_);" + " }" + " JSCompiler_inline_result$$1=self$$inline_2.domServices_;" + " }" + " var myUrl = new JSCompiler_temp_const$$0(" + " JSCompiler_inline_result$$1.getDomHelper()." + " getWindow().location.href)" + "}"); } public void testIssue423() { assumeMinimumCapture = false; test( "(function($) {\n" + " $.fn.multicheck = function(options) {\n" + " initialize.call(this, options);\n" + " };\n" + "\n" + " function initialize(options) {\n" + " options.checkboxes = $(this).siblings(':checkbox');\n" + " preload_check_all.call(this);\n" + " }\n" + "\n" + " function preload_check_all() {\n" + " $(this).data('checkboxes');\n" + " }\n" + "})(jQuery)", "(function($){" + " $.fn.multicheck=function(options$$1){" + " {" + " options$$1.checkboxes=$(this).siblings(\":checkbox\");" + " {" + " $(this).data(\"checkboxes\")" + " }" + " }" + " }" + "})(jQuery)"); assumeMinimumCapture = true; test( "(function($) {\n" + " $.fn.multicheck = function(options) {\n" + " initialize.call(this, options);\n" + " };\n" + "\n" + " function initialize(options) {\n" + " options.checkboxes = $(this).siblings(':checkbox');\n" + " preload_check_all.call(this);\n" + " }\n" + "\n" + " function preload_check_all() {\n" + " $(this).data('checkboxes');\n" + " }\n" + "})(jQuery)", "{var $$$inline_0=jQuery;\n" + "$$$inline_0.fn.multicheck=function(options$$inline_4){\n" + " {options$$inline_4.checkboxes=" + "$$$inline_0(this).siblings(\":checkbox\");\n" + " {$$$inline_0(this).data(\"checkboxes\")}" + " }\n" + "}\n" + "}"); } public void testIssue728() { String f = "var f = function() { return false; };"; StringBuilder calls = new StringBuilder(); StringBuilder folded = new StringBuilder(); for (int i = 0; i < 30; i++) { calls.append("if (!f()) alert('x');"); folded.append("if (!false) alert('x');"); } test(f + calls, folded.toString()); } public void testAnonymous1() { assumeMinimumCapture = false; test("(function(){var a=10;(function(){var b=a;a++;alert(b)})()})();", "{var a$$inline_0=10;" + "{var b$$inline_1=a$$inline_0;" + "a$$inline_0++;alert(b$$inline_1)}}"); assumeMinimumCapture = true; test("(function(){var a=10;(function(){var b=a;a++;alert(b)})()})();", "{var a$$inline_2=10;" + "{var b$$inline_0=a$$inline_2;" + "a$$inline_2++;alert(b$$inline_0)}}"); } public void testAnonymous2() { testSame("(function(){eval();(function(){var b=a;a++;alert(b)})()})();"); } public void testAnonymous3() { // Introducing a new value into is tricky assumeMinimumCapture = false; testSame("(function(){var a=10;(function(){arguments;})()})();"); assumeMinimumCapture = true; test("(function(){var a=10;(function(){arguments;})()})();", "{var a$$inline_0=10;(function(){arguments;})();}"); test("(function(){(function(){arguments;})()})();", "{(function(){arguments;})()}"); } public void testLoopWithFunctionWithFunction() { assumeMinimumCapture = true; test("function _testLocalVariableInLoop_() {\n" + " var result = 0;\n" + " function foo() {\n" + " var arr = [1, 2, 3, 4, 5];\n" + " for (var i = 0, l = arr.length; i < l; i++) {\n" + " var j = arr[i];\n" + // don't inline this function, because the correct behavior depends // captured values. " (function() {\n" + " var k = j;\n" + " setTimeout(function() { result += k; }, 5 * i);\n" + " })();\n" + " }\n" + " }\n" + " foo();\n" + "}", "function _testLocalVariableInLoop_(){\n" + " var result=0;\n" + " {" + " var arr$$inline_0=[1,2,3,4,5];\n" + " var i$$inline_1=0;\n" + " var l$$inline_2=arr$$inline_0.length;\n" + " for(;i$$inline_1