/*
 * 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() {
    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<l$$inline_2;i$$inline_1++){\n" +
        "    var j$$inline_3=arr$$inline_0[i$$inline_1];\n" +
        "    (function(){\n" +
        "       var k$$inline_4=j$$inline_3;\n" +
        "       setTimeout(function(){result+=k$$inline_4},5*i$$inline_1)\n" +
        "     })()\n" +
        "  }\n" +
        "  }\n" +
        "}");
  }

  public void testMethodWithFunctionWithFunction() {
    assumeMinimumCapture = true;
    test("function _testLocalVariable_() {\n" +
        "  var result = 0;\n" +
        "  function foo() {\n" +
        "      var j = [i];\n" +
        "      (function(j) {\n" +
        "        setTimeout(function() { result += j; }, 5 * i);\n" +
        "      })(j);\n" +
        "      j = null;" +
        "  }\n" +
        "  foo();\n" +
        "}",
        "function _testLocalVariable_(){\n" +
        "  var result=0;\n" +
        "  {\n" +
        "  var j$$inline_2=[i];\n" +
        "  {\n" +
        "  var j$$inline_0=j$$inline_2;\n" +  // this temp is needed.
        "  setTimeout(function(){result+=j$$inline_0},5*i);\n" +
        "  }\n" +
        "  j$$inline_2=null\n" + // because this value can be modified later.
        "  }\n" +
        "}");
  }

  // Inline a single reference function into deeper modules
  public void testCrossModuleInlining1() {
    test(createModuleChain(
             // m1
             "function foo(){return f(1)+g(2)+h(3);}",
             // m2
             "foo()"
             ),
         new String[] {
             // m1
             "",
             // m2
             "f(1)+g(2)+h(3);"
            }
        );
  }

  // Inline a single reference function into shallow modules, only if it
  // is cheaper than the call itself.
  public void testCrossModuleInlining2() {
    testSame(createModuleChain(
                // m1
                "foo()",
                // m2
                "function foo(){return f(1)+g(2)+h(3);}"
                )
            );

    test(createModuleChain(
             // m1
             "foo()",
             // m2
             "function foo(){return f();}"
             ),
         new String[] {
             // m1
             "f();",
             // m2
             ""
            }
        );
  }

  // Inline a multi-reference functions into shallow modules, only if it
  // is cheaper than the call itself.
  public void testCrossModuleInlining3() {
    testSame(createModuleChain(
                // m1
                "foo()",
                // m2
                "function foo(){return f(1)+g(2)+h(3);}",
                // m3
                "foo()"
                )
            );

    test(createModuleChain(
             // m1
             "foo()",
             // m2
             "function foo(){return f();}",
             // m3
             "foo()"
             ),
         new String[] {
             // m1
             "f();",
             // m2
             "",
             // m3
             "f();"
            }
         );
  }

  public void test6671158() {
    test(
        "function f() {return g()}" +
        "function Y(a){a.loader_()}" +
        "function _Z(){}" +
        "function _X() { new _Z(a,b, Y(singleton), f()) }",

        "function _Z(){}" +
        "function _X(){" +
        "  var JSCompiler_temp_const$$2=_Z;" +
        "  var JSCompiler_temp_const$$1=a;" +
        "  var JSCompiler_temp_const$$0=b;" +
        "  var JSCompiler_inline_result$$3;" +
        "  {" +
        "    singleton.loader_();" +
        "    JSCompiler_inline_result$$3=void 0;" +
        "  }" +
        "  new JSCompiler_temp_const$$2(" +
        "    JSCompiler_temp_const$$1," +
        "    JSCompiler_temp_const$$0," +
        "    JSCompiler_inline_result$$3," +
        "    g())}");
  }

}
