/* * Copyright 2004 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; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.javascript.rhino.Node; import java.util.List; import java.util.Map; import java.util.Set; /** * Tests for {@link PeepholeFoldConstants} in isolation. Tests for * the interaction of multiple peephole passes are in * {@link PeepholeIntegrationTest}. */ public class PeepholeFoldConstantsTest extends CompilerTestCase { private boolean late; // TODO(user): Remove this when we no longer need to do string comparison. private PeepholeFoldConstantsTest(boolean compareAsTree) { super("", compareAsTree); } public PeepholeFoldConstantsTest() { super(""); } @Override public void setUp() { late = false; enableLineNumberCheck(true); } @Override public CompilerPass getProcessor(final Compiler compiler) { CompilerPass peepholePass = new PeepholeOptimizationsPass(compiler, new PeepholeFoldConstants(late)); return peepholePass; } @Override protected int getNumRepetitions() { // Reduce this to 1 if we get better expression evaluators. return 2; } private void foldSame(String js) { testSame(js); } private void fold(String js, String expected) { test(js, expected); } private void fold(String js, String expected, DiagnosticType warning) { test(js, expected, null, warning); } // TODO(user): This is same as fold() except it uses string comparison. Any // test that needs tell us where a folding is constructing an invalid AST. private void assertResultString(String js, String expected) { PeepholeFoldConstantsTest scTest = new PeepholeFoldConstantsTest(false); scTest.test(js, expected); } public void testUndefinedComparison1() { fold("undefined == undefined", "true"); fold("undefined == null", "true"); fold("undefined == void 0", "true"); fold("undefined == 0", "false"); fold("undefined == 1", "false"); fold("undefined == 'hi'", "false"); fold("undefined == true", "false"); fold("undefined == false", "false"); fold("undefined === undefined", "true"); fold("undefined === null", "false"); fold("undefined === void 0", "true"); foldSame("undefined == this"); foldSame("undefined == x"); fold("undefined != undefined", "false"); fold("undefined != null", "false"); fold("undefined != void 0", "false"); fold("undefined != 0", "true"); fold("undefined != 1", "true"); fold("undefined != 'hi'", "true"); fold("undefined != true", "true"); fold("undefined != false", "true"); fold("undefined !== undefined", "false"); fold("undefined !== void 0", "false"); fold("undefined !== null", "true"); foldSame("undefined != this"); foldSame("undefined != x"); fold("undefined < undefined", "false"); fold("undefined > undefined", "false"); fold("undefined >= undefined", "false"); fold("undefined <= undefined", "false"); fold("0 < undefined", "false"); fold("true > undefined", "false"); fold("'hi' >= undefined", "false"); fold("null <= undefined", "false"); fold("undefined < 0", "false"); fold("undefined > true", "false"); fold("undefined >= 'hi'", "false"); fold("undefined <= null", "false"); fold("null == undefined", "true"); fold("0 == undefined", "false"); fold("1 == undefined", "false"); fold("'hi' == undefined", "false"); fold("true == undefined", "false"); fold("false == undefined", "false"); fold("null === undefined", "false"); fold("void 0 === undefined", "true"); fold("undefined == NaN", "false"); fold("NaN == undefined", "false"); fold("undefined == Infinity", "false"); fold("Infinity == undefined", "false"); fold("undefined == -Infinity", "false"); fold("-Infinity == undefined", "false"); fold("({}) == undefined", "false"); fold("undefined == ({})", "false"); fold("([]) == undefined", "false"); fold("undefined == ([])", "false"); fold("(/a/g) == undefined", "false"); fold("undefined == (/a/g)", "false"); fold("(function(){}) == undefined", "false"); fold("undefined == (function(){})", "false"); fold("undefined != NaN", "true"); fold("NaN != undefined", "true"); fold("undefined != Infinity", "true"); fold("Infinity != undefined", "true"); fold("undefined != -Infinity", "true"); fold("-Infinity != undefined", "true"); fold("({}) != undefined", "true"); fold("undefined != ({})", "true"); fold("([]) != undefined", "true"); fold("undefined != ([])", "true"); fold("(/a/g) != undefined", "true"); fold("undefined != (/a/g)", "true"); fold("(function(){}) != undefined", "true"); fold("undefined != (function(){})", "true"); foldSame("this == undefined"); foldSame("x == undefined"); } public void testUndefinedComparison2() { fold("\"123\" !== void 0", "true"); fold("\"123\" === void 0", "false"); fold("void 0 !== \"123\"", "true"); fold("void 0 === \"123\"", "false"); } public void testUndefinedComparison3() { fold("\"123\" !== undefined", "true"); fold("\"123\" === undefined", "false"); fold("undefined !== \"123\"", "true"); fold("undefined === \"123\"", "false"); } public void testUndefinedComparison4() { fold("1 !== void 0", "true"); fold("1 === void 0", "false"); fold("null !== void 0", "true"); fold("null === void 0", "false"); fold("undefined !== void 0", "false"); fold("undefined === void 0", "true"); } public void testNullComparison1() { fold("null == undefined", "true"); fold("null == null", "true"); fold("null == void 0", "true"); fold("null == 0", "false"); fold("null == 1", "false"); fold("null == 'hi'", "false"); fold("null == true", "false"); fold("null == false", "false"); fold("null === undefined", "false"); fold("null === null", "true"); fold("null === void 0", "false"); foldSame("null == this"); foldSame("null == x"); fold("null != undefined", "false"); fold("null != null", "false"); fold("null != void 0", "false"); fold("null != 0", "true"); fold("null != 1", "true"); fold("null != 'hi'", "true"); fold("null != true", "true"); fold("null != false", "true"); fold("null !== undefined", "true"); fold("null !== void 0", "true"); fold("null !== null", "false"); foldSame("null != this"); foldSame("null != x"); fold("null < null", "false"); fold("null > null", "false"); fold("null >= null", "true"); fold("null <= null", "true"); foldSame("0 < null"); // foldable fold("true > null", "true"); foldSame("'hi' >= null"); // foldable fold("null <= null", "true"); foldSame("null < 0"); // foldable fold("null > true", "false"); foldSame("null >= 'hi'"); // foldable fold("null <= null", "true"); fold("null == null", "true"); fold("0 == null", "false"); fold("1 == null", "false"); fold("'hi' == null", "false"); fold("true == null", "false"); fold("false == null", "false"); fold("null === null", "true"); fold("void 0 === null", "false"); fold("null == NaN", "false"); fold("NaN == null", "false"); fold("null == Infinity", "false"); fold("Infinity == null", "false"); fold("null == -Infinity", "false"); fold("-Infinity == null", "false"); fold("({}) == null", "false"); fold("null == ({})", "false"); fold("([]) == null", "false"); fold("null == ([])", "false"); fold("(/a/g) == null", "false"); fold("null == (/a/g)", "false"); fold("(function(){}) == null", "false"); fold("null == (function(){})", "false"); fold("null != NaN", "true"); fold("NaN != null", "true"); fold("null != Infinity", "true"); fold("Infinity != null", "true"); fold("null != -Infinity", "true"); fold("-Infinity != null", "true"); fold("({}) != null", "true"); fold("null != ({})", "true"); fold("([]) != null", "true"); fold("null != ([])", "true"); fold("(/a/g) != null", "true"); fold("null != (/a/g)", "true"); fold("(function(){}) != null", "true"); fold("null != (function(){})", "true"); foldSame("({a:f()}) == null"); foldSame("null == ({a:f()})"); foldSame("([f()]) == null"); foldSame("null == ([f()])"); foldSame("this == null"); foldSame("x == null"); } public void testUnaryOps() { // These cases are handled by PeepholeRemoveDeadCode. foldSame("!foo()"); foldSame("~foo()"); foldSame("-foo()"); // These cases are handled here. fold("a=!true", "a=false"); fold("a=!10", "a=false"); fold("a=!false", "a=true"); fold("a=!foo()", "a=!foo()"); fold("a=-0", "a=-0.0"); fold("a=-(0)", "a=-0.0"); fold("a=-Infinity", "a=-Infinity"); fold("a=-NaN", "a=NaN"); fold("a=-foo()", "a=-foo()"); fold("a=~~0", "a=0"); fold("a=~~10", "a=10"); fold("a=~-7", "a=6"); fold("a=+true", "a=1"); fold("a=+10", "a=10"); fold("a=+false", "a=0"); foldSame("a=+foo()"); foldSame("a=+f"); fold("a=+(f?true:false)", "a=+(f?1:0)"); // TODO(johnlenz): foldable fold("a=+0", "a=0"); fold("a=+Infinity", "a=Infinity"); fold("a=+NaN", "a=NaN"); fold("a=+-7", "a=-7"); fold("a=+.5", "a=.5"); fold("a=~0x100000000", "a=~0x100000000", PeepholeFoldConstants.BITWISE_OPERAND_OUT_OF_RANGE); fold("a=~-0x100000000", "a=~-0x100000000", PeepholeFoldConstants.BITWISE_OPERAND_OUT_OF_RANGE); testSame("a=~.5", PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND); } public void testUnaryOpsStringCompare() { // Negatives are folded into a single number node. assertResultString("a=-1", "a=-1"); assertResultString("a=~0", "a=-1"); assertResultString("a=~1", "a=-2"); assertResultString("a=~101", "a=-102"); } public void testFoldLogicalOp() { fold("x = true && x", "x = x"); foldSame("x = [foo()] && x"); fold("x = false && x", "x = false"); fold("x = true || x", "x = true"); fold("x = false || x", "x = x"); fold("x = 0 && x", "x = 0"); fold("x = 3 || x", "x = 3"); fold("x = false || 0", "x = 0"); // unfoldable, because the right-side may be the result fold("a = x && true", "a=x&&true"); fold("a = x && false", "a=x&&false"); fold("a = x || 3", "a=x||3"); fold("a = x || false", "a=x||false"); fold("a = b ? c : x || false", "a=b?c:x||false"); fold("a = b ? x || false : c", "a=b?x||false:c"); fold("a = b ? c : x && true", "a=b?c:x&&true"); fold("a = b ? x && true : c", "a=b?x&&true:c"); // folded, but not here. foldSame("a = x || false ? b : c"); foldSame("a = x && true ? b : c"); fold("x = foo() || true || bar()", "x = foo()||true"); fold("x = foo() || false || bar()", "x = foo()||bar()"); fold("x = foo() || true && bar()", "x = foo()||bar()"); fold("x = foo() || false && bar()", "x = foo()||false"); fold("x = foo() && false && bar()", "x = foo()&&false"); fold("x = foo() && true && bar()", "x = foo()&&bar()"); fold("x = foo() && false || bar()", "x = foo()&&false||bar()"); fold("1 && b()", "b()"); fold("a() && (1 && b())", "a() && b()"); // TODO(johnlenz): Consider folding the following to: // "(a(),1) && b(); fold("(a() && 1) && b()", "(a() && 1) && b()"); // Really not foldable, because it would change the type of the // expression if foo() returns something equivalent, but not // identical, to true. Cf. FoldConstants.tryFoldAndOr(). foldSame("x = foo() && true || bar()"); foldSame("foo() && true || bar()"); } public void testFoldBitwiseOp() { fold("x = 1 & 1", "x = 1"); fold("x = 1 & 2", "x = 0"); fold("x = 3 & 1", "x = 1"); fold("x = 3 & 3", "x = 3"); fold("x = 1 | 1", "x = 1"); fold("x = 1 | 2", "x = 3"); fold("x = 3 | 1", "x = 3"); fold("x = 3 | 3", "x = 3"); fold("x = 1 ^ 1", "x = 0"); fold("x = 1 ^ 2", "x = 3"); fold("x = 3 ^ 1", "x = 2"); fold("x = 3 ^ 3", "x = 0"); fold("x = -1 & 0", "x = 0"); fold("x = 0 & -1", "x = 0"); fold("x = 1 & 4", "x = 0"); fold("x = 2 & 3", "x = 2"); // make sure we fold only when we are supposed to -- not when doing so would // lose information or when it is performed on nonsensical arguments. fold("x = 1 & 1.1", "x = 1"); fold("x = 1.1 & 1", "x = 1"); fold("x = 1 & 3000000000", "x = 0"); fold("x = 3000000000 & 1", "x = 0"); // Try some cases with | as well fold("x = 1 | 4", "x = 5"); fold("x = 1 | 3", "x = 3"); fold("x = 1 | 1.1", "x = 1"); foldSame("x = 1 | 3E9"); fold("x = 1 | 3000000001", "x = -1294967295"); } public void testFoldBitwiseOp2() { fold("x = y & 1 & 1", "x = y & 1"); fold("x = y & 1 & 2", "x = y & 0"); fold("x = y & 3 & 1", "x = y & 1"); fold("x = 3 & y & 1", "x = y & 1"); fold("x = y & 3 & 3", "x = y & 3"); fold("x = 3 & y & 3", "x = y & 3"); fold("x = y | 1 | 1", "x = y | 1"); fold("x = y | 1 | 2", "x = y | 3"); fold("x = y | 3 | 1", "x = y | 3"); fold("x = 3 | y | 1", "x = y | 3"); fold("x = y | 3 | 3", "x = y | 3"); fold("x = 3 | y | 3", "x = y | 3"); fold("x = y ^ 1 ^ 1", "x = y ^ 0"); fold("x = y ^ 1 ^ 2", "x = y ^ 3"); fold("x = y ^ 3 ^ 1", "x = y ^ 2"); fold("x = 3 ^ y ^ 1", "x = y ^ 2"); fold("x = y ^ 3 ^ 3", "x = y ^ 0"); fold("x = 3 ^ y ^ 3", "x = y ^ 0"); fold("x = Infinity | NaN", "x=0"); fold("x = 12 | NaN", "x=12"); } public void testFoldingMixTypesLate() { late = true; fold("x = x + '2'", "x+='2'"); fold("x = +x + +'2'", "x = +x + 2"); fold("x = x - '2'", "x-=2"); fold("x = x ^ '2'", "x^=2"); fold("x = '2' ^ x", "x^=2"); fold("x = '2' & x", "x&=2"); fold("x = '2' | x", "x|=2"); fold("x = '2' | y", "x=2|y"); fold("x = y | '2'", "x=y|2"); fold("x = y | (a && '2')", "x=y|(a&&2)"); fold("x = y | (a,'2')", "x=y|(a,2)"); fold("x = y | (a?'1':'2')", "x=y|(a?1:2)"); fold("x = y | ('x'?'1':'2')", "x=y|('x'?1:2)"); } public void testFoldingMixTypesEarly() { late = false; foldSame("x = x + '2'"); fold("x = +x + +'2'", "x = +x + 2"); fold("x = x - '2'", "x = x - 2"); fold("x = x ^ '2'", "x = x ^ 2"); fold("x = '2' ^ x", "x = 2 ^ x"); fold("x = '2' & x", "x = 2 & x"); fold("x = '2' | x", "x = 2 | x"); fold("x = '2' | y", "x=2|y"); fold("x = y | '2'", "x=y|2"); fold("x = y | (a && '2')", "x=y|(a&&2)"); fold("x = y | (a,'2')", "x=y|(a,2)"); fold("x = y | (a?'1':'2')", "x=y|(a?1:2)"); fold("x = y | ('x'?'1':'2')", "x=y|('x'?1:2)"); } public void testFoldingAdd() { fold("x = null + true", "x=1"); foldSame("x = a + true"); } public void testFoldBitwiseOpStringCompare() { assertResultString("x = -1 | 0", "x=-1"); // EXPR_RESULT case is in in PeepholeIntegrationTest } public void testFoldBitShifts() { fold("x = 1 << 0", "x = 1"); fold("x = -1 << 0", "x = -1"); fold("x = 1 << 1", "x = 2"); fold("x = 3 << 1", "x = 6"); fold("x = 1 << 8", "x = 256"); fold("x = 1 >> 0", "x = 1"); fold("x = -1 >> 0", "x = -1"); fold("x = 1 >> 1", "x = 0"); fold("x = 2 >> 1", "x = 1"); fold("x = 5 >> 1", "x = 2"); fold("x = 127 >> 3", "x = 15"); fold("x = 3 >> 1", "x = 1"); fold("x = 3 >> 2", "x = 0"); fold("x = 10 >> 1", "x = 5"); fold("x = 10 >> 2", "x = 2"); fold("x = 10 >> 5", "x = 0"); fold("x = 10 >>> 1", "x = 5"); fold("x = 10 >>> 2", "x = 2"); fold("x = 10 >>> 5", "x = 0"); fold("x = -1 >>> 1", "x = 2147483647"); // 0x7fffffff fold("x = -1 >>> 0", "x = 4294967295"); // 0xffffffff fold("x = -2 >>> 0", "x = 4294967294"); // 0xfffffffe testSame("3000000000 << 1", PeepholeFoldConstants.BITWISE_OPERAND_OUT_OF_RANGE); testSame("1 << 32", PeepholeFoldConstants.SHIFT_AMOUNT_OUT_OF_BOUNDS); testSame("1 << -1", PeepholeFoldConstants.SHIFT_AMOUNT_OUT_OF_BOUNDS); testSame("3000000000 >> 1", PeepholeFoldConstants.BITWISE_OPERAND_OUT_OF_RANGE); testSame("1 >> 32", PeepholeFoldConstants.SHIFT_AMOUNT_OUT_OF_BOUNDS); testSame("1.5 << 0", PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND); testSame("1 << .5", PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND); testSame("1.5 >>> 0", PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND); testSame("1 >>> .5", PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND); testSame("1.5 >> 0", PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND); testSame("1 >> .5", PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND); } public void testFoldBitShiftsStringCompare() { // Negative numbers. assertResultString("x = -1 << 1", "x=-2"); assertResultString("x = -1 << 8", "x=-256"); assertResultString("x = -1 >> 1", "x=-1"); assertResultString("x = -2 >> 1", "x=-1"); assertResultString("x = -1 >> 0", "x=-1"); } public void testStringAdd() { fold("x = 'a' + \"bc\"", "x = \"abc\""); fold("x = 'a' + 5", "x = \"a5\""); fold("x = 5 + 'a'", "x = \"5a\""); fold("x = 'a' + ''", "x = \"a\""); fold("x = \"a\" + foo()", "x = \"a\"+foo()"); fold("x = foo() + 'a' + 'b'", "x = foo()+\"ab\""); fold("x = (foo() + 'a') + 'b'", "x = foo()+\"ab\""); // believe it! fold("x = foo() + 'a' + 'b' + 'cd' + bar()", "x = foo()+\"abcd\"+bar()"); fold("x = foo() + 2 + 'b'", "x = foo()+2+\"b\""); // don't fold! fold("x = foo() + 'a' + 2", "x = foo()+\"a2\""); fold("x = '' + null", "x = \"null\""); fold("x = true + '' + false", "x = \"truefalse\""); fold("x = '' + []", "x = ''"); // cannot fold (but nice if we can) } public void testIssue821() { foldSame("var a =(Math.random()>0.5? '1' : 2 ) + 3 + 4;"); foldSame("var a = ((Math.random() ? 0 : 1) ||" + "(Math.random()>0.5? '1' : 2 )) + 3 + 4;"); } public void testFoldConstructor() { fold("x = this[new String('a')]", "x = this['a']"); fold("x = ob[new String(12)]", "x = ob['12']"); fold("x = ob[new String(false)]", "x = ob['false']"); fold("x = ob[new String(null)]", "x = ob['null']"); fold("x = 'a' + new String('b')", "x = 'ab'"); fold("x = 'a' + new String(23)", "x = 'a23'"); fold("x = 2 + new String(1)", "x = '21'"); foldSame("x = ob[new String(a)]"); foldSame("x = new String('a')"); foldSame("x = (new String('a'))[3]"); } public void testFoldArithmetic() { fold("x = 10 + 20", "x = 30"); fold("x = 2 / 4", "x = 0.5"); fold("x = 2.25 * 3", "x = 6.75"); fold("z = x * y", "z = x * y"); fold("x = y * 5", "x = y * 5"); fold("x = 1 / 0", "x = 1 / 0"); fold("x = 3 % 2", "x = 1"); fold("x = 3 % -2", "x = 1"); fold("x = -1 % 3", "x = -1"); fold("x = 1 % 0", "x = 1 % 0"); } public void testFoldArithmetic2() { foldSame("x = y + 10 + 20"); foldSame("x = y / 2 / 4"); fold("x = y * 2.25 * 3", "x = y * 6.75"); fold("z = x * y", "z = x * y"); fold("x = y * 5", "x = y * 5"); fold("x = y + (z * 24 * 60 * 60 * 1000)", "x = y + z * 864E5"); } public void testFoldArithmetic3() { fold("x = null * undefined", "x = NaN"); fold("x = null * 1", "x = 0"); fold("x = (null - 1) * 2", "x = -2"); fold("x = (null + 1) * 2", "x = 2"); } public void testFoldArithmeticInfinity() { fold("x=-Infinity-2", "x=-Infinity"); fold("x=Infinity-2", "x=Infinity"); fold("x=Infinity*5", "x=Infinity"); } public void testFoldArithmeticStringComp() { // Negative Numbers. assertResultString("x = 10 - 20", "x=-10"); } public void testFoldComparison() { fold("x = 0 == 0", "x = true"); fold("x = 1 == 2", "x = false"); fold("x = 'abc' == 'def'", "x = false"); fold("x = 'abc' == 'abc'", "x = true"); fold("x = \"\" == ''", "x = true"); fold("x = foo() == bar()", "x = foo()==bar()"); fold("x = 1 != 0", "x = true"); fold("x = 'abc' != 'def'", "x = true"); fold("x = 'a' != 'a'", "x = false"); fold("x = 1 < 20", "x = true"); fold("x = 3 < 3", "x = false"); fold("x = 10 > 1.0", "x = true"); fold("x = 10 > 10.25", "x = false"); fold("x = y == y", "x = y==y"); fold("x = y < y", "x = false"); fold("x = y > y", "x = false"); fold("x = 1 <= 1", "x = true"); fold("x = 1 <= 0", "x = false"); fold("x = 0 >= 0", "x = true"); fold("x = -1 >= 9", "x = false"); fold("x = true == true", "x = true"); fold("x = false == false", "x = true"); fold("x = false == null", "x = false"); fold("x = false == true", "x = false"); fold("x = true == null", "x = false"); fold("0 == 0", "true"); fold("1 == 2", "false"); fold("'abc' == 'def'", "false"); fold("'abc' == 'abc'", "true"); fold("\"\" == ''", "true"); foldSame("foo() == bar()"); fold("1 != 0", "true"); fold("'abc' != 'def'", "true"); fold("'a' != 'a'", "false"); fold("1 < 20", "true"); fold("3 < 3", "false"); fold("10 > 1.0", "true"); fold("10 > 10.25", "false"); foldSame("x == x"); fold("x < x", "false"); fold("x > x", "false"); fold("1 <= 1", "true"); fold("1 <= 0", "false"); fold("0 >= 0", "true"); fold("-1 >= 9", "false"); fold("true == true", "true"); fold("false == null", "false"); fold("false == true", "false"); fold("true == null", "false"); } // ===, !== comparison tests public void testFoldComparison2() { fold("x = 0 === 0", "x = true"); fold("x = 1 === 2", "x = false"); fold("x = 'abc' === 'def'", "x = false"); fold("x = 'abc' === 'abc'", "x = true"); fold("x = \"\" === ''", "x = true"); fold("x = foo() === bar()", "x = foo()===bar()"); fold("x = 1 !== 0", "x = true"); fold("x = 'abc' !== 'def'", "x = true"); fold("x = 'a' !== 'a'", "x = false"); fold("x = y === y", "x = y===y"); fold("x = true === true", "x = true"); fold("x = false === false", "x = true"); fold("x = false === null", "x = false"); fold("x = false === true", "x = false"); fold("x = true === null", "x = false"); fold("0 === 0", "true"); fold("1 === 2", "false"); fold("'abc' === 'def'", "false"); fold("'abc' === 'abc'", "true"); fold("\"\" === ''", "true"); foldSame("foo() === bar()"); // TODO(johnlenz): It would be nice to handle these cases as well. foldSame("1 === '1'"); foldSame("1 === true"); foldSame("1 !== '1'"); foldSame("1 !== true"); fold("1 !== 0", "true"); fold("'abc' !== 'def'", "true"); fold("'a' !== 'a'", "false"); foldSame("x === x"); fold("true === true", "true"); fold("false === null", "false"); fold("false === true", "false"); fold("true === null", "false"); } public void testFoldComparison3() { fold("x = !1 == !0", "x = false"); fold("x = !0 == !0", "x = true"); fold("x = !1 == !1", "x = true"); fold("x = !1 == null", "x = false"); fold("x = !1 == !0", "x = false"); fold("x = !0 == null", "x = false"); fold("!0 == !0", "true"); fold("!1 == null", "false"); fold("!1 == !0", "false"); fold("!0 == null", "false"); fold("x = !0 === !0", "x = true"); fold("x = !1 === !1", "x = true"); fold("x = !1 === null", "x = false"); fold("x = !1 === !0", "x = false"); fold("x = !0 === null", "x = false"); fold("!0 === !0", "true"); fold("!1 === null", "false"); fold("!1 === !0", "false"); fold("!0 === null", "false"); } public void testFoldGetElem() { fold("x = [,10][0]", "x = void 0"); fold("x = [10, 20][0]", "x = 10"); fold("x = [10, 20][1]", "x = 20"); testSame("x = [10, 20][0.5]", PeepholeFoldConstants.INVALID_GETELEM_INDEX_ERROR); testSame("x = [10, 20][-1]", PeepholeFoldConstants.INDEX_OUT_OF_BOUNDS_ERROR); testSame("x = [10, 20][2]", PeepholeFoldConstants.INDEX_OUT_OF_BOUNDS_ERROR); foldSame("x = [foo(), 0][1]"); fold("x = [0, foo()][1]", "x = foo()"); foldSame("x = [0, foo()][0]"); } public void testFoldComplex() { fold("x = (3 / 1.0) + (1 * 2)", "x = 5"); fold("x = (1 == 1.0) && foo() && true", "x = foo()&&true"); fold("x = 'abc' + 5 + 10", "x = \"abc510\""); } public void testFoldLeft() { foldSame("(+x - 1) + 2"); // not yet fold("(+x + 1) + 2", "+x + 3"); } public void testFoldArrayLength() { // Can fold fold("x = [].length", "x = 0"); fold("x = [1,2,3].length", "x = 3"); fold("x = [a,b].length", "x = 2"); // Not handled yet fold("x = [,,1].length", "x = 3"); // Cannot fold fold("x = [foo(), 0].length", "x = [foo(),0].length"); fold("x = y.length", "x = y.length"); } public void testFoldStringLength() { // Can fold basic strings. fold("x = ''.length", "x = 0"); fold("x = '123'.length", "x = 3"); // Test Unicode escapes are accounted for. fold("x = '123\u01dc'.length", "x = 4"); } public void testFoldTypeof() { fold("x = typeof 1", "x = \"number\""); fold("x = typeof 'foo'", "x = \"string\""); fold("x = typeof true", "x = \"boolean\""); fold("x = typeof false", "x = \"boolean\""); fold("x = typeof null", "x = \"object\""); fold("x = typeof undefined", "x = \"undefined\""); fold("x = typeof void 0", "x = \"undefined\""); fold("x = typeof []", "x = \"object\""); fold("x = typeof [1]", "x = \"object\""); fold("x = typeof [1,[]]", "x = \"object\""); fold("x = typeof {}", "x = \"object\""); fold("x = typeof function() {}", "x = 'function'"); foldSame("x = typeof[1,[foo()]]"); foldSame("x = typeof{bathwater:baby()}"); } public void testFoldInstanceOf() { // Non object types are never instances of anything. fold("64 instanceof Object", "false"); fold("64 instanceof Number", "false"); fold("'' instanceof Object", "false"); fold("'' instanceof String", "false"); fold("true instanceof Object", "false"); fold("true instanceof Boolean", "false"); fold("!0 instanceof Object", "false"); fold("!0 instanceof Boolean", "false"); fold("false instanceof Object", "false"); fold("null instanceof Object", "false"); fold("undefined instanceof Object", "false"); fold("NaN instanceof Object", "false"); fold("Infinity instanceof Object", "false"); // Array and object literals are known to be objects. fold("[] instanceof Object", "true"); fold("({}) instanceof Object", "true"); // These cases is foldable, but no handled currently. foldSame("new Foo() instanceof Object"); // These would require type information to fold. foldSame("[] instanceof Foo"); foldSame("({}) instanceof Foo"); fold("(function() {}) instanceof Object", "true"); // An unknown value should never be folded. foldSame("x instanceof Foo"); } public void testDivision() { // Make sure the 1/3 does not expand to 0.333333 fold("print(1/3)", "print(1/3)"); // Decimal form is preferable to fraction form when strings are the // same length. fold("print(1/2)", "print(0.5)"); } public void testAssignOpsLate() { late = true; fold("x=x+y", "x+=y"); foldSame("x=y+x"); fold("x=x*y", "x*=y"); fold("x=y*x", "x*=y"); fold("x.y=x.y+z", "x.y+=z"); foldSame("next().x = next().x + 1"); fold("x=x-y", "x-=y"); foldSame("x=y-x"); fold("x=x|y", "x|=y"); fold("x=y|x", "x|=y"); fold("x=x*y", "x*=y"); fold("x=y*x", "x*=y"); fold("x.y=x.y+z", "x.y+=z"); foldSame("next().x = next().x + 1"); // This is OK, really. fold("({a:1}).a = ({a:1}).a + 1", "({a:1}).a = 2"); } public void testAssignOpsEarly() { late = false; foldSame("x=x+y"); foldSame("x=y+x"); foldSame("x=x*y"); foldSame("x=y*x"); foldSame("x.y=x.y+z"); foldSame("next().x = next().x + 1"); foldSame("x=x-y"); foldSame("x=y-x"); foldSame("x=x|y"); foldSame("x=y|x"); foldSame("x=x*y"); foldSame("x=y*x"); foldSame("x.y=x.y+z"); foldSame("next().x = next().x + 1"); // This is OK, really. fold("({a:1}).a = ({a:1}).a + 1", "({a:1}).a = 2"); } public void testFoldAdd1() { fold("x=false+1","x=1"); fold("x=true+1","x=2"); fold("x=1+false","x=1"); fold("x=1+true","x=2"); } public void testFoldLiteralNames() { foldSame("NaN == NaN"); foldSame("Infinity == Infinity"); foldSame("Infinity == NaN"); fold("undefined == NaN", "false"); fold("undefined == Infinity", "false"); foldSame("Infinity >= Infinity"); foldSame("NaN >= NaN"); } public void testFoldLiteralsTypeMismatches() { fold("true == true", "true"); fold("true == false", "false"); fold("true == null", "false"); fold("false == null", "false"); // relational operators convert its operands fold("null <= null", "true"); // 0 = 0 fold("null >= null", "true"); fold("null > null", "false"); fold("null < null", "false"); fold("false >= null", "true"); // 0 = 0 fold("false <= null", "true"); fold("false > null", "false"); fold("false < null", "false"); fold("true >= null", "true"); // 1 > 0 fold("true <= null", "false"); fold("true > null", "true"); fold("true < null", "false"); fold("true >= false", "true"); // 1 > 0 fold("true <= false", "false"); fold("true > false", "true"); fold("true < false", "false"); } public void testFoldLeftChildConcat() { foldSame("x +5 + \"1\""); fold("x+\"5\" + \"1\"", "x + \"51\""); // fold("\"a\"+(c+\"b\")","\"a\"+c+\"b\""); fold("\"a\"+(\"b\"+c)","\"ab\"+c"); } public void testFoldLeftChildOp() { fold("x * Infinity * 2", "x * Infinity"); foldSame("x - Infinity - 2"); // want "x-Infinity" foldSame("x - 1 + Infinity"); foldSame("x - 2 + 1"); foldSame("x - 2 + 3"); foldSame("1 + x - 2 + 1"); foldSame("1 + x - 2 + 3"); foldSame("1 + x - 2 + 3 - 1"); foldSame("f(x)-0"); foldSame("x-0-0"); foldSame("x+2-2+2"); foldSame("x+2-2+2-2"); foldSame("x-2+2"); foldSame("x-2+2-2"); foldSame("x-2+2-2+2"); foldSame("1+x-0-NaN"); foldSame("1+f(x)-0-NaN"); foldSame("1+x-0+NaN"); foldSame("1+f(x)-0+NaN"); foldSame("1+x+NaN"); // unfoldable foldSame("x+2-2"); // unfoldable foldSame("x+2"); // nothing to do foldSame("x-2"); // nothing to do } public void testFoldSimpleArithmeticOp() { foldSame("x*NaN"); foldSame("NaN/y"); foldSame("f(x)-0"); foldSame("f(x)*1"); foldSame("1*f(x)"); foldSame("0+a+b"); foldSame("0-a-b"); foldSame("a+b-0"); foldSame("(1+x)*NaN"); foldSame("(1+f(x))*NaN"); // don't fold side-effects } public void testFoldLiteralsAsNumbers() { fold("x/'12'","x/12"); fold("x/('12'+'6')", "x/126"); fold("true*x", "1*x"); fold("x/false", "x/0"); // should we add an error check? :) } public void testNotFoldBackToTrueFalse() { late = false; fold("!0", "true"); fold("!1", "false"); fold("!3", "false"); late = true; foldSame("!0"); foldSame("!1"); fold("!3", "false"); foldSame("false"); foldSame("true"); } public void testFoldBangConstants() { fold("1 + !0", "2"); fold("1 + !1", "1"); fold("'a ' + !1", "'a false'"); fold("'a ' + !0", "'a true'"); } public void testFoldMixed() { fold("''+[1]", "'1'"); foldSame("false+[]"); // would like: "\"false\"" } public void testFoldVoid() { foldSame("void 0"); fold("void 1", "void 0"); fold("void x", "void 0"); fold("void x()", "void x()"); } public void testObjectLiteral() { test("(!{})", "false"); test("(!{a:1})", "false"); testSame("(!{a:foo()})"); testSame("(!{'a':foo()})"); } public void testArrayLiteral() { test("(![])", "false"); test("(![1])", "false"); test("(![a])", "false"); testSame("(![foo()])"); } public void testIssue601() { testSame("'\\v' == 'v'"); testSame("'v' == '\\v'"); testSame("'\\u000B' == '\\v'"); } public void testFoldObjectLiteralRef1() { // Leave extra side-effects in place testSame("var x = ({a:foo(),b:bar()}).a"); testSame("var x = ({a:1,b:bar()}).a"); testSame("function f() { return {b:foo(), a:2}.a; }"); // on the LHS the object act as a temporary leave it in place. testSame("({a:x}).a = 1"); test("({a:x}).a += 1", "({a:x}).a = x + 1"); testSame("({a:x}).a ++"); testSame("({a:x}).a --"); // functions can't reference the object through 'this'. testSame("({a:function(){return this}}).a"); testSame("({get a() {return this}}).a"); testSame("({set a(b) {return this}}).a"); // Leave unknown props alone, the might be on the prototype testSame("({}).a"); // setters by themselves don't provide a definition testSame("({}).a"); testSame("({set a(b) {}}).a"); // sets don't hide other definitions. test("({a:1,set a(b) {}}).a", "1"); // get is transformed to a call (gets don't have self referential names) test("({get a() {}}).a", "(function (){})()"); // sets don't hide other definitions. test("({get a() {},set a(b) {}}).a", "(function (){})()"); // a function remains a function not a call. test("var x = ({a:function(){return 1}}).a", "var x = function(){return 1}"); test("var x = ({a:1}).a", "var x = 1"); test("var x = ({a:1, a:2}).a", "var x = 2"); test("var x = ({a:1, a:foo()}).a", "var x = foo()"); test("var x = ({a:foo()}).a", "var x = foo()"); test("function f() { return {a:1, b:2}.a; }", "function f() { return 1; }"); // GETELEM is handled the same way. test("var x = ({'a':1})['a']", "var x = 1"); } public void testFoldObjectLiteralRef2() { late = false; test("({a:x}).a += 1", "({a:x}).a = x + 1"); late = true; testSame("({a:x}).a += 1"); } public void testIEString() { testSame("!+'\\v1'"); } public void testIssue522() { testSame("[][1] = 1;"); } private static final List LITERAL_OPERANDS = ImmutableList.of( "null", "undefined", "void 0", "true", "false", "!0", "!1", "0", "1", "''", "'123'", "'abc'", "'def'", "NaN", "Infinity", // TODO(nicksantos): Add more literals "-Infinity" //"({})", // "[]" //"[0]", //"Object", //"(function() {})" ); public void testInvertibleOperators() { Map inverses = ImmutableMap.builder() .put("==", "!=") .put("===", "!==") .put("<=", ">") .put("<", ">=") .put(">=", "<") .put(">", "<=") .put("!=", "==") .put("!==", "===") .build(); Set comparators = ImmutableSet.of("<=", "<", ">=", ">"); Set equalitors = ImmutableSet.of("==", "==="); Set uncomparables = ImmutableSet.of("undefined", "void 0"); List operators = ImmutableList.copyOf(inverses.values()); for (int iOperandA = 0; iOperandA < LITERAL_OPERANDS.size(); iOperandA++) { for (int iOperandB = 0; iOperandB < LITERAL_OPERANDS.size(); iOperandB++) { for (int iOp = 0; iOp < operators.size(); iOp++) { String a = LITERAL_OPERANDS.get(iOperandA); String b = LITERAL_OPERANDS.get(iOperandB); String op = operators.get(iOp); String inverse = inverses.get(op); // Test invertability. if (comparators.contains(op) && (uncomparables.contains(a) || uncomparables.contains(b))) { assertSameResults(join(a, op, b), "false"); assertSameResults(join(a, inverse, b), "false"); } else if (a.equals(b) && equalitors.contains(op)) { if (a.equals("NaN") || a.equals("Infinity") || a.equals("-Infinity")) { foldSame(join(a, op, b)); foldSame(join(a, inverse, b)); } else { assertSameResults(join(a, op, b), "true"); assertSameResults(join(a, inverse, b), "false"); } } else { assertNotSameResults(join(a, op, b), join(a, inverse, b)); } } } } } public void testCommutativeOperators() { late = true; List operators = ImmutableList.of( "==", "!=", "===", "!==", "*", "|", "&", "^"); for (int iOperandA = 0; iOperandA < LITERAL_OPERANDS.size(); iOperandA++) { for (int iOperandB = iOperandA; iOperandB < LITERAL_OPERANDS.size(); iOperandB++) { for (int iOp = 0; iOp < operators.size(); iOp++) { String a = LITERAL_OPERANDS.get(iOperandA); String b = LITERAL_OPERANDS.get(iOperandB); String op = operators.get(iOp); // Test commutativity. // TODO(nicksantos): Eventually, all cases should be collapsed. assertSameResultsOrUncollapsed(join(a, op, b), join(b, op, a)); } } } } public void testConvertToNumberNegativeInf() { foldSame("var x = 3 * (r ? Infinity : -Infinity);"); } private String join(String operandA, String op, String operandB) { return operandA + " " + op + " " + operandB; } private void assertSameResultsOrUncollapsed(String exprA, String exprB) { String resultA = process(exprA); String resultB = process(exprB); // TODO: why is nothing done with this? if (resultA.equals(print(exprA))) { foldSame(exprA); foldSame(exprB); } else { assertSameResults(exprA, exprB); } } private void assertSameResults(String exprA, String exprB) { assertEquals( "Expressions did not fold the same\nexprA: " + exprA + "\nexprB: " + exprB, process(exprA), process(exprB)); } private void assertNotSameResults(String exprA, String exprB) { assertFalse( "Expressions folded the same\nexprA: " + exprA + "\nexprB: " + exprB, process(exprA).equals(process(exprB))); } private String process(String js) { return printHelper(js, true); } private String print(String js) { return printHelper(js, false); } private String printHelper(String js, boolean runProcessor) { Compiler compiler = createCompiler(); CompilerOptions options = getOptions(); compiler.init( ImmutableList.of(), ImmutableList.of(SourceFile.fromCode("testcode", js)), options); Node root = compiler.parseInputs(); assertTrue("Unexpected parse error(s): " + Joiner.on("\n").join(compiler.getErrors()) + "\nEXPR: " + js, root != null); Node externsRoot = root.getFirstChild(); Node mainRoot = externsRoot.getNext(); if (runProcessor) { getProcessor(compiler).process(externsRoot, mainRoot); } return compiler.toSource(mainRoot); } }