From 1de0f4b6ebb3e56d003666c60de21f2148b67552 Mon Sep 17 00:00:00 2001 From: tbreisacher Date: Wed, 15 Nov 2017 13:22:52 -0800 Subject: [PATCH] Allow expression decomposition in a few places where we previously weren't allowing. These decompositions are only allowed if a new flag, --allow_method_call_decomposing, is passed, because they produce code that will not run on some browsers. This means expressions like x.someMethod(yield 1); can now be transpiled. See https://github.com/google/closure-compiler/wiki/FAQ#i-get-an-undecomposable-expression-error-for-my-yield-or-await-expression-what-do-i-do (which will need to be updated after this is submitted) ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=175870681 --- .../javascript/jscomp/CommandLineRunner.java | 7 + .../javascript/jscomp/CompilerOptions.java | 14 ++ .../javascript/jscomp/Es6ExtractClasses.java | 3 +- .../jscomp/Es6RewriteGenerators.java | 3 +- .../jscomp/ExpressionDecomposer.java | 58 ++++----- .../javascript/jscomp/FunctionInjector.java | 12 +- .../jscomp/Es6RewriteGeneratorsTest.java | 34 ++++- .../jscomp/ExpressionDecomposerTest.java | 123 +++++++++++++++--- .../generator_allow_call_test.js | 75 +++++++++++ .../runtime_tests/simulated_j2cl_test.js | 56 ++++++++ 10 files changed, 324 insertions(+), 61 deletions(-) create mode 100644 test/com/google/javascript/jscomp/runtime_tests/generator_allow_call_test.js create mode 100644 test/com/google/javascript/jscomp/runtime_tests/simulated_j2cl_test.js diff --git a/src/com/google/javascript/jscomp/CommandLineRunner.java b/src/com/google/javascript/jscomp/CommandLineRunner.java index f45ace5ef2f..09089c35dfe 100644 --- a/src/com/google/javascript/jscomp/CommandLineRunner.java +++ b/src/com/google/javascript/jscomp/CommandLineRunner.java @@ -758,6 +758,11 @@ private static class Flags { usage = "Rewrite ES6 library calls to use polyfills provided by the compiler's runtime.") private boolean rewritePolyfills = true; + @Option(name = "--allow_method_call_decomposing", + handler = BooleanOptionHandler.class, + usage = "Allow decomposing x.y(); to: var tmp = x.y; tmp.call(x); Unsafe on IE 8 and 9") + private boolean allowMethodCallDecomposing = false; + @Option( name = "--print_source_after_each_pass", handler = BooleanOptionHandler.class, @@ -1728,6 +1733,8 @@ protected CompilerOptions createOptions() { options.rewritePolyfills = flags.rewritePolyfills && options.getLanguageIn().toFeatureSet().contains(FeatureSet.ES6); + options.setAllowMethodCallDecomposing(flags.allowMethodCallDecomposing); + if (!flags.translationsFile.isEmpty()) { try { options.messageBundle = new XtbMessageBundle( diff --git a/src/com/google/javascript/jscomp/CompilerOptions.java b/src/com/google/javascript/jscomp/CompilerOptions.java index ca11529aaca..b3d18b6ae6c 100644 --- a/src/com/google/javascript/jscomp/CompilerOptions.java +++ b/src/com/google/javascript/jscomp/CompilerOptions.java @@ -979,6 +979,20 @@ public void setTrustedStrings(boolean yes) { trustedStrings = yes; } + private boolean allowMethodCallDecomposing; + + boolean allowMethodCallDecomposing() { + return allowMethodCallDecomposing; + } + + /** + * Setting this to true indicates that it's safe to rewrite x.y() as: fn = x.y; fn.call(x); + * This should be usually be false if supporting IE 8 or IE 9 is necessary. + */ + public void setAllowMethodCallDecomposing(boolean value) { + this.allowMethodCallDecomposing = value; + } + // Should only be used when debugging compiler bugs. boolean printSourceAfterEachPass; diff --git a/src/com/google/javascript/jscomp/Es6ExtractClasses.java b/src/com/google/javascript/jscomp/Es6ExtractClasses.java index 8088b2a9371..227660eb3ab 100644 --- a/src/com/google/javascript/jscomp/Es6ExtractClasses.java +++ b/src/com/google/javascript/jscomp/Es6ExtractClasses.java @@ -65,7 +65,8 @@ public final class Es6ExtractClasses compiler, compiler.getUniqueNameIdSupplier(), consts, - Scope.createGlobalScope(new Node(Token.SCRIPT))); + Scope.createGlobalScope(new Node(Token.SCRIPT)), + compiler.getOptions().allowMethodCallDecomposing()); } @Override diff --git a/src/com/google/javascript/jscomp/Es6RewriteGenerators.java b/src/com/google/javascript/jscomp/Es6RewriteGenerators.java index a9190e5b83f..564331b10b6 100644 --- a/src/com/google/javascript/jscomp/Es6RewriteGenerators.java +++ b/src/com/google/javascript/jscomp/Es6RewriteGenerators.java @@ -1116,7 +1116,8 @@ private final class DecomposeYields extends NodeTraversal.AbstractPreOrderCallba compiler, compiler.getUniqueNameIdSupplier(), consts, - Scope.createGlobalScope(new Node(Token.SCRIPT))); + Scope.createGlobalScope(new Node(Token.SCRIPT)), + compiler.getOptions().allowMethodCallDecomposing()); } @Override diff --git a/src/com/google/javascript/jscomp/ExpressionDecomposer.java b/src/com/google/javascript/jscomp/ExpressionDecomposer.java index 4a5233352ab..75311e50ed2 100644 --- a/src/com/google/javascript/jscomp/ExpressionDecomposer.java +++ b/src/com/google/javascript/jscomp/ExpressionDecomposer.java @@ -59,11 +59,18 @@ enum DecompositionType { private final Set knownConstants; private final Scope scope; + /** + * Whether to allow decomposing foo.bar to "var fn = foo.bar; fn.call(foo);" + * Should be false if targetting IE8 or IE9. + */ + private final boolean allowMethodCallDecomposing; + ExpressionDecomposer( AbstractCompiler compiler, Supplier safeNameIdSupplier, Set constNames, - Scope scope) { + Scope scope, + boolean allowMethodCallDecomposing) { checkNotNull(compiler); checkNotNull(safeNameIdSupplier); checkNotNull(constNames); @@ -71,6 +78,7 @@ enum DecompositionType { this.safeNameIdSupplier = safeNameIdSupplier; this.knownConstants = constNames; this.scope = scope; + this.allowMethodCallDecomposing = allowMethodCallDecomposing; } // An arbitrary limit to prevent catch infinite recursion. @@ -89,7 +97,7 @@ void maybeExposeExpression(Node expression) { i++; if (i > MAX_ITERATIONS) { throw new IllegalStateException( - "DecomposeExpression depth exceeded on :\n" + expression.toStringTree()); + "DecomposeExpression depth exceeded on:\n" + expression.toStringTree()); } } } @@ -196,18 +204,14 @@ private void exposeExpression(Node expressionRoot, Node subExpression) { decomposeSubExpressions(left.getFirstChild(), null, state); } } - } else if (parentType == Token.CALL - && NodeUtil.isGet(parent.getFirstChild())) { + } else if (parentType == Token.CALL && NodeUtil.isGet(parent.getFirstChild())) { Node functionExpression = parent.getFirstChild(); decomposeSubExpressions(functionExpression.getNext(), child, state); // Now handle the call expression if (isExpressionTreeUnsafe(functionExpression, state.sideEffects) && functionExpression.getFirstChild() != grandchild) { - // TODO(johnlenz): In Internet Explorer, non-JavaScript objects such - // as DOM objects can not be decomposed. - checkState(allowObjectCallDecomposing(), "Object method calls can not be decomposed."); - // Either there were preexisting side-effects, or this node has - // side-effects. + checkState(allowMethodCallDecomposing, "Object method calls can not be decomposed."); + // Either there were preexisting side-effects, or this node has side-effects. state.sideEffects = true; // Rewrite the call so "this" is preserved. @@ -239,18 +243,6 @@ private void exposeExpression(Node expressionRoot, Node subExpression) { } } - private static boolean allowObjectCallDecomposing() { - return false; - } - - /** - * @return Whether the node may represent an external method. - */ - private static boolean maybeExternMethod(Node node) { - // TODO(johnlenz): Provide some mechanism for determining this. - return true; - } - /** * @return "expression" or the node closest to "expression", that does not * have a conditional ancestor. @@ -519,20 +511,19 @@ private Node extractExpression(Node expr, Node injectionPoint) { * @return The replacement node. */ private Node rewriteCallExpression(Node call, DecompositionState state) { - checkArgument(call.isCall()); + checkArgument(call.isCall(), call); Node first = call.getFirstChild(); - checkArgument(NodeUtil.isGet(first)); + checkArgument(NodeUtil.isGet(first), first); // Extracts the expression representing the function to call. For example: // "a['b'].c" from "a['b'].c()" - Node getVarNode = extractExpression( - first, state.extractBeforeStatement); + Node getVarNode = extractExpression(first, state.extractBeforeStatement); state.extractBeforeStatement = getVarNode; // Extracts the object reference to be used as "this". For example: // "a['b']" from "a['b'].c" Node getExprNode = getVarNode.getFirstFirstChild(); - checkArgument(NodeUtil.isGet(getExprNode)); + checkArgument(NodeUtil.isGet(getExprNode), getExprNode); Node thisVarNode = extractExpression( getExprNode.getFirstChild(), state.extractBeforeStatement); state.extractBeforeStatement = thisVarNode; @@ -551,9 +542,8 @@ private Node rewriteCallExpression(Node call, DecompositionState state) { // ... Node newCall = IR.call( IR.getprop( - functionNameNode.cloneNode(), - IR.string("call")), - thisNameNode.cloneNode()).srcref(call); + functionNameNode.cloneNode(), IR.string("call")), + thisNameNode.cloneNode()).useSourceInfoIfMissingFromForTree(call); // Throw away the call name call.removeFirstChild(); @@ -562,9 +552,7 @@ private Node rewriteCallExpression(Node call, DecompositionState state) { newCall.addChildrenToBack(call.removeChildren()); } - // Replace the call. - Node callParent = call.getParent(); - callParent.replaceChild(call, newCall); + call.replaceWith(newCall); return newCall; } @@ -824,10 +812,10 @@ private DecompositionType isSubexpressionMovable( // Node first = parent.getFirstChild(); if (requiresDecomposition && parent.isCall() && NodeUtil.isGet(first)) { - if (maybeExternMethod(first)) { - return DecompositionType.UNDECOMPOSABLE; - } else { + if (allowMethodCallDecomposing) { return DecompositionType.DECOMPOSABLE; + } else { + return DecompositionType.UNDECOMPOSABLE; } } } diff --git a/src/com/google/javascript/jscomp/FunctionInjector.java b/src/com/google/javascript/jscomp/FunctionInjector.java index f2b5cca0aaa..6800e37fa61 100644 --- a/src/com/google/javascript/jscomp/FunctionInjector.java +++ b/src/com/google/javascript/jscomp/FunctionInjector.java @@ -449,8 +449,12 @@ private CallSiteType classifyCallSite(Reference ref) { } else { Node expressionRoot = ExpressionDecomposer.findExpressionRoot(callNode); if (expressionRoot != null) { + // TODO(tbreisacher): Change this to compiler.getOptions().allowMethodCallDecomposing(). + // Doing so currently causes a "DecomposeExpression depth exceeded" error. + boolean allowMethodCallDecomposing = false; + ExpressionDecomposer decomposer = new ExpressionDecomposer( - compiler, safeNameIdSupplier, knownConstants, ref.scope); + compiler, safeNameIdSupplier, knownConstants, ref.scope, allowMethodCallDecomposing); DecompositionType type = decomposer.canExposeExpression( callNode); if (type == DecompositionType.MOVABLE) { @@ -467,8 +471,12 @@ private CallSiteType classifyCallSite(Reference ref) { } private ExpressionDecomposer getDecomposer(Scope scope) { + // TODO(tbreisacher): Change this to compiler.getOptions().allowMethodCallDecomposing(). + // Doing so currently causes a "DecomposeExpression depth exceeded" error. + boolean allowMethodCallDecomposing = false; + return new ExpressionDecomposer( - compiler, safeNameIdSupplier, knownConstants, scope); + compiler, safeNameIdSupplier, knownConstants, scope, allowMethodCallDecomposing); } /** diff --git a/test/com/google/javascript/jscomp/Es6RewriteGeneratorsTest.java b/test/com/google/javascript/jscomp/Es6RewriteGeneratorsTest.java index bfd7bccca8f..70fd39fda88 100644 --- a/test/com/google/javascript/jscomp/Es6RewriteGeneratorsTest.java +++ b/test/com/google/javascript/jscomp/Es6RewriteGeneratorsTest.java @@ -21,10 +21,12 @@ /** Unit tests for {@link Es6RewriteGenerators}. */ public final class Es6RewriteGeneratorsTest extends CompilerTestCase { + private boolean allowMethodCallDecomposing; @Override protected void setUp() throws Exception { super.setUp(); + allowMethodCallDecomposing = false; setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015); enableRunTypeCheckAfterProcessing(); } @@ -33,6 +35,7 @@ protected void setUp() throws Exception { protected CompilerOptions getOptions() { CompilerOptions options = super.getOptions(); options.setLanguageOut(LanguageMode.ECMASCRIPT3); + options.setAllowMethodCallDecomposing(allowMethodCallDecomposing); return options; } @@ -406,8 +409,35 @@ public void testWhileLoopsGenerator() { } public void testUndecomposableExpression() { - testError("function *f() { obj.bar(yield 5); }", - Es6ToEs3Util.CANNOT_CONVERT); + testError("function *f() { obj.bar(yield 5); }", Es6ToEs3Util.CANNOT_CONVERT); + } + + public void testDecomposableExpression() { + allowMethodCallDecomposing = true; + rewriteGeneratorBodyWithVars( + "obj.bar(yield 5);", + LINE_JOINER.join( + "var $jscomp$generator$next$arg2;", + "var JSCompiler_temp_const$jscomp$0;", + "var JSCompiler_temp_const$jscomp$1;"), + LINE_JOINER.join( + "case 0:", + " JSCompiler_temp_const$jscomp$1 = obj;", + " JSCompiler_temp_const$jscomp$0 = JSCompiler_temp_const$jscomp$1.bar;", + " $jscomp$generator$state = 1;", + " return { value: 5, done: false };", + "case 1:", + " if (!($jscomp$generator$action$arg ==1 )) {", + " $jscomp$generator$state = 2;", + " break;", + " }", + " $jscomp$generator$state = -1;", + " throw $jscomp$generator$throw$arg;", + "case 2:", + " $jscomp$generator$next$arg2 = $jscomp$generator$next$arg;", + " JSCompiler_temp_const$jscomp$0.call(", + " JSCompiler_temp_const$jscomp$1, $jscomp$generator$next$arg2);", + " $jscomp$generator$state = -1;")); } public void testGeneratorCannotConvertYet() { diff --git a/test/com/google/javascript/jscomp/ExpressionDecomposerTest.java b/test/com/google/javascript/jscomp/ExpressionDecomposerTest.java index ea23c4748fe..84e05680c08 100644 --- a/test/com/google/javascript/jscomp/ExpressionDecomposerTest.java +++ b/test/com/google/javascript/jscomp/ExpressionDecomposerTest.java @@ -31,12 +31,18 @@ import junit.framework.TestCase; /** - * Unit tests for ExpressionDecomposer + * Unit tests for {@link ExpressionDecomposer} + * * @author johnlenz@google.com (John Lenz) */ +// Note: functions "foo" and "goo" are external functions in the helper. public final class ExpressionDecomposerTest extends TestCase { - // Note: functions "foo" and "goo" are external functions - // in the helper. + private boolean allowMethodCallDecomposing; + + @Override + public void setUp() { + allowMethodCallDecomposing = false; + } public void testCanExposeExpression1() { // Can't move or decompose some classes of expressions. @@ -133,24 +139,39 @@ public void testCanExposeExpression3() { "function f(){ return goo() && foo();}", "foo"); } - public void testCanExposeExpression4() { + public void testCanExposeExpression4a() { // 'this' must be preserved in call. helperCanExposeExpression( DecompositionType.UNDECOMPOSABLE, "if (goo.a(1, foo()));", "foo"); } - public void testCanExposeExpression5() { + public void testCanExposeExpression4b() { + allowMethodCallDecomposing = true; + helperCanExposeExpression(DecompositionType.DECOMPOSABLE, "if (goo.a(1, foo()));", "foo"); + } + + public void testCanExposeExpression5a() { // 'this' must be preserved in call. helperCanExposeExpression( DecompositionType.UNDECOMPOSABLE, "if (goo['a'](foo()));", "foo"); } - public void testCanExposeExpression6() { + public void testCanExposeExpression5b() { + allowMethodCallDecomposing = true; + helperCanExposeExpression(DecompositionType.DECOMPOSABLE, "if (goo['a'](foo()));", "foo"); + } + + public void testCanExposeExpression6a() { // 'this' must be preserved in call. helperCanExposeExpression( DecompositionType.UNDECOMPOSABLE, "z:if (goo.a(1, foo()));", "foo"); } + public void testCanExposeExpression6b() { + allowMethodCallDecomposing = true; + helperCanExposeExpression(DecompositionType.DECOMPOSABLE, "z:if (goo.a(1, foo()));", "foo"); + } + public void testCanExposeExpression7() { // Verify calls to function expressions are movable. helperCanExposeFunctionExpression( @@ -434,6 +455,17 @@ public void testExposeExpression16() { "var temp$jscomp$0; if (temp$jscomp$0 = bar()) temp$jscomp$0=foo(); throw temp$jscomp$0;"); } + public void testExposeExpression17() { + allowMethodCallDecomposing = true; + helperExposeExpression( + "x.foo(y())", + "y", + lines( + "var temp_const$jscomp$1 = x;", + "var temp_const$jscomp$0 = temp_const$jscomp$1.foo;", + "temp_const$jscomp$0.call(temp_const$jscomp$1, y());")); + } + public void testMoveClass1() { helperMoveExpression( "alert(class X {});", @@ -448,7 +480,7 @@ public void testMoveClass2() { "var result$jscomp$0 = class X {}; console.log(1, 2, result$jscomp$0);"); } - public void testExposeYieldExpression1() { + public void testMoveYieldExpression1() { helperMoveExpression( "function *f() { return { a: yield 1, c: foo(yield 2, yield 3) }; }", "yield", @@ -477,7 +509,7 @@ public void testExposeYieldExpression1() { "}")); } - public void testExposeYieldExpression2() { + public void testMoveYieldExpression2() { helperMoveExpression( "function *f() { return (yield 1) || (yield 2); }", "yield", @@ -486,7 +518,20 @@ public void testExposeYieldExpression2() { " var result$jscomp$0 = yield 1;", " return result$jscomp$0 || (yield 2);", "}")); + } + + public void testMoveYieldExpression3() { + helperMoveExpression( + "function *f() { return x.y(yield 1); }", + "yield", + lines( + "function *f() {", + " var result$jscomp$0 = yield 1;", + " return x.y(result$jscomp$0);", + "}")); + } + public void testExposeYieldExpression1() { helperExposeExpression( "function *f(x) { return x || (yield 2); }", "yield", @@ -498,6 +543,45 @@ public void testExposeYieldExpression2() { "}")); } + public void testExposeYieldExpression2() { + allowMethodCallDecomposing = true; + helperExposeExpression( + "function *f() { return x.y(yield 1); }", + "yield", + lines( + "function *f() {", + " var temp_const$jscomp$1 = x;", + " var temp_const$jscomp$0 = temp_const$jscomp$1.y;", + " return temp_const$jscomp$0.call(temp_const$jscomp$1, yield 1);", + "}")); + } + + public void testExposeYieldExpression3() { + allowMethodCallDecomposing = true; + helperExposeExpression( + "function *f() { return g.call(yield 1); }", + "yield", + lines( + "function *f() {", + " var temp_const$jscomp$1 = g;", + " var temp_const$jscomp$0 = temp_const$jscomp$1.call;", + " return temp_const$jscomp$0.call(temp_const$jscomp$1, yield 1);", + "}")); + } + + public void testExposeYieldExpression4() { + allowMethodCallDecomposing = true; + helperExposeExpression( + "function *f() { return g.apply([yield 1, yield 2]); }", + "yield", + lines( + "function *f() {", + " var temp_const$jscomp$1 = g;", + " var temp_const$jscomp$0 = temp_const$jscomp$1.apply;", + " return temp_const$jscomp$0.call(temp_const$jscomp$1, [yield 1, yield 2]);", + "}")); + } + // Simple name on LHS of assignment-op. public void testExposePlusEquals1() { helperExposeExpression( @@ -618,14 +702,14 @@ private void helperCanExposeFunctionExpression( Set knownConstants = new HashSet<>(); ExpressionDecomposer decomposer = new ExpressionDecomposer( compiler, compiler.getUniqueNameIdSupplier(), - knownConstants, newScope()); + knownConstants, newScope(), allowMethodCallDecomposing); Node tree = parse(compiler, code); assertNotNull(tree); Node externsRoot = parse(compiler, "function goo() {} function foo() {}"); assertNotNull(externsRoot); - Node callSite = findCall(tree, null, 2); + Node callSite = findCall(tree, null, call); assertNotNull("Call " + call + " was not found.", callSite); compiler.resetUniqueNameId(); @@ -644,7 +728,7 @@ private void helperCanExposeExpression( } ExpressionDecomposer decomposer = new ExpressionDecomposer( compiler, compiler.getUniqueNameIdSupplier(), - knownConstants, newScope()); + knownConstants, newScope(), allowMethodCallDecomposing); Node tree = parse(compiler, code); assertNotNull(tree); @@ -698,7 +782,7 @@ private void helperExposeExpression( } ExpressionDecomposer decomposer = new ExpressionDecomposer( compiler, compiler.getUniqueNameIdSupplier(), - knownConstants, newScope()); + knownConstants, newScope(), allowMethodCallDecomposing); decomposer.setTempNamePrefix("temp"); decomposer.setResultNamePrefix("result"); Node expectedRoot = parse(compiler, expectedResult); @@ -754,7 +838,7 @@ private void helperMoveExpression( ExpressionDecomposer decomposer = new ExpressionDecomposer( compiler, compiler.getUniqueNameIdSupplier(), - knownConstants, newScope()); + knownConstants, newScope(), allowMethodCallDecomposing); decomposer.setTempNamePrefix("temp"); decomposer.setResultNamePrefix("result"); Node expectedRoot = parse(compiler, expectedResult); @@ -778,14 +862,11 @@ private Compiler getCompiler() { CompilerOptions options = new CompilerOptions(); options.setLanguage(LanguageMode.ECMASCRIPT_2015); options.setCodingConvention(new GoogleCodingConvention()); + options.setAllowMethodCallDecomposing(allowMethodCallDecomposing); compiler.initOptions(options); return compiler; } - private static Node findCall(Node n, String name) { - return findCall(n, name, 1); - } - @Nullable private static Node findClass(Node n) { if (n.isClass()) { @@ -800,6 +881,10 @@ private static Node findClass(Node n) { return null; } + private static Node findCall(Node n, String name) { + return findCall(n, name, 1); + } + /** * @param name The name to look for. If name is null, look for a yield expression instead. * @param call The call to look for. @@ -814,9 +899,7 @@ Node find(Node n) { if (n.isCall() || n.isYield()) { if (name == null || (n.isYield() && "yield".equals(name)) - || (n.isCall() - && n.getFirstChild().isName() - && n.getFirstChild().getString().equals(name))) { + || (n.isCall() && n.getFirstChild().matchesQualifiedName(name))) { found++; if (found == call) { return n; diff --git a/test/com/google/javascript/jscomp/runtime_tests/generator_allow_call_test.js b/test/com/google/javascript/jscomp/runtime_tests/generator_allow_call_test.js new file mode 100644 index 00000000000..9d4dd7d9681 --- /dev/null +++ b/test/com/google/javascript/jscomp/runtime_tests/generator_allow_call_test.js @@ -0,0 +1,75 @@ +/* + * Copyright 2017 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. + */ + +/** + * @fileoverview Tests for ES6 generators, which require the + * --allow_method_call_decomposing flag. + */ +goog.require('goog.testing.jsunit'); + +function testYieldInComplexExpression1() { + var arr = []; + function *f() { + var obj = { method(x) { arr.push(x); }}; + obj.method(yield 1); + } + for (let value of f()) { + assertEquals(1, value); + } + assertArrayEquals([undefined], arr); +} + +function testYieldInComplexExpression2() { + var arr = []; + function *f() { + var obj = { method(x) { arr.push(x); }}; + obj.method(yield 1); + } + var gen = f(); + gen.next(); + gen.next(3); + assertArrayEquals([3], arr); +} + +function testYieldInComplexExpression3() { + var arr = []; + function *f() { + function getObj() { + arr.push('getObj called'); + return { method(x) { arr.push(x); } }; + } + getObj().method(yield 1); + } + for (let x of f()) { + assertEquals(1, x); + } + assertArrayEquals(['getObj called', undefined], arr); +} + +function testYieldInComplexExpression4() { + var arr = []; + function *f() { + function getObj() { + arr.push('getObj called'); + return { method(x) { arr.push(x); } }; + } + getObj().method(yield 1); + } + var gen = f(); + gen.next(); + gen.next(3); + assertArrayEquals(['getObj called', 3], arr); +} diff --git a/test/com/google/javascript/jscomp/runtime_tests/simulated_j2cl_test.js b/test/com/google/javascript/jscomp/runtime_tests/simulated_j2cl_test.js new file mode 100644 index 00000000000..dff1188dd6e --- /dev/null +++ b/test/com/google/javascript/jscomp/runtime_tests/simulated_j2cl_test.js @@ -0,0 +1,56 @@ +/* + * Copyright 2017 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. + */ + +/** + * @fileoverview Tests of static async methods. Based on the "jsasync" + * j2cl integration test. + */ +goog.module('jscomp.test.static_async'); + +const testSuite = goog.require('goog.testing.testSuite'); + +class Main { + /** + * @return {IThenable} + * @nocollapse + */ + static async ten() { + return 10; + } + + /** + * @return {IThenable} + * @nocollapse + */ + static async main() { + return Main.same(await Main.ten()); + } + + /** + * @param {number} i + * @return {number} + * @nocollapse + */ + static same(i) { + return i; + } +} + +testSuite({ + testCallStaticAsyncMethod() { + return Main.main(); + } +});