diff --git a/src/com/google/javascript/jscomp/FunctionInjector.java b/src/com/google/javascript/jscomp/FunctionInjector.java index 00c0ed755aa..187322d9b50 100644 --- a/src/com/google/javascript/jscomp/FunctionInjector.java +++ b/src/com/google/javascript/jscomp/FunctionInjector.java @@ -159,22 +159,24 @@ boolean doesFunctionMeetMinimumRequirements(final String fnName, Node fnNode) { boolean referencesArguments = NodeUtil.isNameReferenced( block, "arguments", NodeUtil.MATCH_NOT_FUNCTION); - // or it references "eval" or one of its names anywhere. - Predicate p = new Predicate(){ - @Override - public boolean apply(Node n) { - if (n.isName()) { - return n.getString().equals("eval") - || (!fnName.isEmpty() - && n.getString().equals(fnName)) - || (!fnRecursionName.isEmpty() - && n.getString().equals(fnRecursionName)); - } - return false; - } - }; + Predicate blocksInjection = + new Predicate() { + @Override + public boolean apply(Node n) { + if (n.isName()) { + // References "eval" or one of its names anywhere. + return n.getString().equals("eval") + || (!fnName.isEmpty() && n.getString().equals(fnName)) + || (!fnRecursionName.isEmpty() && n.getString().equals(fnRecursionName)); + } else if (n.isSuper()) { + // Don't inline if this function or its inner functions contains super + return true; + } + return false; + } + }; - return !referencesArguments && !NodeUtil.has(block, p, Predicates.alwaysTrue()); + return !referencesArguments && !NodeUtil.has(block, blocksInjection, Predicates.alwaysTrue()); } /** @@ -372,7 +374,7 @@ public void prepare(FunctionInjector injector, Reference ref) { /** * An var declaration and initialization, where the result of the call is * assigned to the declared name - * name. For example: "a = foo();". + * name. For example: "var a = foo();". * VAR * NAME A * CALL diff --git a/test/com/google/javascript/jscomp/InlineFunctionsTest.java b/test/com/google/javascript/jscomp/InlineFunctionsTest.java index d1bf9f2d5f2..7953aef30cf 100644 --- a/test/com/google/javascript/jscomp/InlineFunctionsTest.java +++ b/test/com/google/javascript/jscomp/InlineFunctionsTest.java @@ -3382,4 +3382,22 @@ public void testFunctionReferencingLetInNonGlobalBlock() { "}", "alert(g(10));")); } + + public void testNotInliningFunctionWithSuper() { + // Super field accessor arrow functions like this one are used for transpilation of some + // features such as async functions and async generators. If inlined, it becomes a syntax error + // as the inner function has no super. + testSame( + lines( + "class A { m(){ return 1 } };", + "class B extends A {", + " m() {", + " const super$m = () => super.m;", + " const jscomp$this = this;", + " return function*() {", + " yield super$m().call(jscomp$this);", + " };", + " }", + "}")); + } } diff --git a/test/com/google/javascript/jscomp/IntegrationTest.java b/test/com/google/javascript/jscomp/IntegrationTest.java index 4b8a2f6c738..7bb0eed3d8a 100644 --- a/test/com/google/javascript/jscomp/IntegrationTest.java +++ b/test/com/google/javascript/jscomp/IntegrationTest.java @@ -3275,6 +3275,109 @@ public void testAsyncFunctionInExterns() { TypeCheck.INEXISTENT_PROPERTY); } + public void testAsyncFunctionSuper() { + CompilerOptions options = createCompilerOptions(); + CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options); + options.setLanguageIn(LanguageMode.ECMASCRIPT_2017); + options.setLanguageOut(LanguageMode.ECMASCRIPT_2015); + options.setPropertyRenaming(PropertyRenamingPolicy.OFF); + options.setVariableRenaming(VariableRenamingPolicy.OFF); + options.setPrettyPrint(true); + + // Create a noninjecting compiler avoid comparing all the polyfill code. + useNoninjectingCompiler = true; + + // include externs definitions for the stuff that would have been injected + ImmutableList.Builder externsList = ImmutableList.builder(); + externsList.addAll(externs); + externsList.add(SourceFile.fromCode("extraExterns", "var $jscomp = {};")); + externs = externsList.build(); + + test( + options, + lines( + "class Foo {", + " async bar() {", + " console.log('bar');", + " }", + "}", + "", + "class Baz extends Foo {", + " async bar() {", + " await Promise.resolve();", + " super.bar();", + " }", + "}\n"), + lines( + "class Foo {", + " bar() {", + " return $jscomp.asyncExecutePromiseGeneratorFunction(function*() {", + " console.log(\"bar\");", + " });", + " }", + "}", + "class Baz extends Foo {", + " bar() {", + " const $jscomp$async$this = this, $jscomp$async$super$get$bar = () => super.bar;", + " return $jscomp.asyncExecutePromiseGeneratorFunction(function*() {", + " yield Promise.resolve();", + " $jscomp$async$super$get$bar().call($jscomp$async$this);", + " });", + " }", + "}")); + } + + public void testAsyncIterationSuper() { + CompilerOptions options = createCompilerOptions(); + CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options); + options.setLanguageIn(LanguageMode.ECMASCRIPT_NEXT); + options.setLanguageOut(LanguageMode.ECMASCRIPT_2017); + options.setPropertyRenaming(PropertyRenamingPolicy.OFF); + options.setVariableRenaming(VariableRenamingPolicy.OFF); + options.setPrettyPrint(true); + + // Create a noninjecting compiler avoid comparing all the polyfill code. + useNoninjectingCompiler = true; + + // include externs definitions for the stuff that would have been injected + ImmutableList.Builder externsList = ImmutableList.builder(); + externsList.addAll(externs); + externsList.add(SourceFile.fromCode("extraExterns", "var $jscomp = {};")); + externs = externsList.build(); + + test( + options, + lines( + "class Foo {", + " async *bar() {", + " console.log('bar');", + " }", + "}", + "", + "class Baz extends Foo {", + " async *bar() {", + " super.bar().next();", + " }", + "}\n"), + lines( + "class Foo {", + " bar() {", + " return new $jscomp.AsyncGeneratorWrapper(function*() {", + " console.log(\"bar\");", + " }());", + " }", + "}", + "class Baz extends Foo {", + " bar() {", + " const $jscomp$asyncIter$this = this,", + " $jscomp$asyncIter$super$get$bar = () => super.bar;", + " return new $jscomp.AsyncGeneratorWrapper(function*() {", + " $jscomp$asyncIter$super$get$bar().call($jscomp$asyncIter$this).next();", + " }());", + " }", + "}")); + } + public void testLanguageMode() { CompilerOptions options = createCompilerOptions();