Skip to content

Commit

Permalink
Update async await transpilation to avoid captures of super
Browse files Browse the repository at this point in the history
Closes #3103

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=217866046
  • Loading branch information
ChadKillingsworth authored and blickly committed Oct 19, 2018
1 parent 45a8658 commit 79891f0
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 22 deletions.
79 changes: 72 additions & 7 deletions src/com/google/javascript/jscomp/RewriteAsyncFunctions.java
Expand Up @@ -120,16 +120,54 @@ void recordAsyncArgumentsReplacementWasDone() {
}
}

private final Deque<LexicalContext> contextStack;
private final AbstractCompiler compiler;
private static final FeatureSet transpiledFeatures =
FeatureSet.BARE_MINIMUM.with(Feature.ASYNC_FUNCTIONS);

public RewriteAsyncFunctions(AbstractCompiler compiler) {
checkNotNull(compiler);
this.compiler = compiler;
private final Deque<LexicalContext> contextStack;
private final AbstractCompiler compiler;

/**
* If this option is set to true, then this pass will rewrite references to properties using super
* (e.g. `super.method()`) to avoid using `super` within an arrow function.
*
* <p>This option exists due to a bug in MS Edge 17 which causes it to fail to access super
* properties correctly from within arrow functions.
*
* <p>See https://github.com/Microsoft/ChakraCore/issues/5784
*
* <p>If the final compiler output will not include ES6 classes, this option should not be set. It
* isn't needed since the `super` references will be transpiled away anyway. Also, when this
* option is set it uses `Object.getPrototypeOf()` to rewrite `super`, which may not exist in
* pre-ES6 JS environments.
*/
private final boolean rewriteSuperPropertyReferencesWithoutSuper;

private RewriteAsyncFunctions(Builder builder) {
checkNotNull(builder);
this.compiler = builder.compiler;
this.contextStack = new ArrayDeque<>();
this.contextStack.addFirst(new LexicalContext());
this.rewriteSuperPropertyReferencesWithoutSuper =
builder.rewriteSuperPropertyReferencesWithoutSuper;
}

static class Builder {
private final AbstractCompiler compiler;
private boolean rewriteSuperPropertyReferencesWithoutSuper = false;

Builder(AbstractCompiler compiler) {
checkNotNull(compiler);
this.compiler = compiler;
}

Builder rewriteSuperPropertyReferencesWithoutSuper(boolean value) {
rewriteSuperPropertyReferencesWithoutSuper = value;
return this;
}

RewriteAsyncFunctions build() {
return new RewriteAsyncFunctions(this);
}
}

@Override
Expand Down Expand Up @@ -244,9 +282,36 @@ private void convertAsyncFunction(NodeTraversal t, LexicalContext functionContex
NodeUtil.addFeatureToScript(t.getCurrentFile(), Feature.CONST_DECLARATIONS);
}
for (String replacedMethodName : functionContext.replacedSuperProperties) {
Node superReference;
if (rewriteSuperPropertyReferencesWithoutSuper) {
// Rewrite to avoid using `super` within an arrow function.
// See more information on definition of this option.
// TODO(bradfordcsmith): RewriteAsyncIteration and RewriteAsyncFunctions have the
// same logic for dealing with super references. Consider having them share
// it from a common place instead of duplicating.

// static super: Object.getPrototypeOf(this);
superReference =
IR.call(IR.getprop(IR.name("Object"), IR.string("getPrototypeOf")), IR.thisNode());
if (!originalFunction.getParent().isStaticMember()) {
// instance super: Object.getPrototypeOf(Object.getPrototypeOf(this))
superReference =
IR.call(IR.getprop(IR.name("Object"), IR.string("getPrototypeOf")), superReference);
}
} else {
superReference = IR.superNode();
}

// const super$get$x = () => super.x;
Node arrowFunction = IR.arrowFunction(
IR.name(""), IR.paramList(), IR.getprop(IR.superNode(), IR.string(replacedMethodName)));
// OR avoid super for static method (class object -> superclass object)
// const super$get$x = () => Object.getPrototypeOf(this).x
// OR avoid super for instance method (instance -> prototype -> super prototype)
// const super$get$x = () => Object.getPrototypeOf(Object.getPrototypeOf(this)).x
Node arrowFunction =
IR.arrowFunction(
IR.name(""),
IR.paramList(),
IR.getprop(superReference, IR.string(replacedMethodName)));
compiler.reportChangeToChangeScope(arrowFunction);
NodeUtil.addFeatureToScript(t.getCurrentFile(), Feature.ARROW_FUNCTIONS);

Expand Down
73 changes: 65 additions & 8 deletions src/com/google/javascript/jscomp/RewriteAsyncIteration.java
Expand Up @@ -80,6 +80,22 @@ public final class RewriteAsyncIteration implements NodeTraversal.Callback, HotS
private final String argumentsVarName = "$jscomp$asyncIter$arguments";
private final String superPropGetterPrefix = "$jscomp$asyncIter$super$get$";

/**
* If this option is set to true, then this pass will rewrite references to properties using super
* (e.g. `super.method()`) to avoid using `super` within an arrow function.
*
* <p>This option exists due to a bug in MS Edge 17 which causes it to fail to access super
* properties correctly from within arrow functions.
*
* <p>See https://github.com/Microsoft/ChakraCore/issues/5784
*
* <p>If the final compiler output will not include ES6 classes, this option should not be set. It
* isn't needed since the `super` references will be transpiled away anyway. Also, when this
* option is set it uses `Object.getPrototypeOf()` to rewrite `super`, which may not exist in
* pre-ES6 JS environments.
*/
private final boolean rewriteSuperPropertyReferencesWithoutSuper;

/**
* Tracks a function and its context of this/arguments/super, if such a context exists.
*/
Expand Down Expand Up @@ -142,9 +158,30 @@ private static final class ThisSuperArgsContext {
}
}

RewriteAsyncIteration(AbstractCompiler compiler) {
this.compiler = compiler;
private RewriteAsyncIteration(Builder builder) {
this.compiler = builder.compiler;
this.contextStack = new ArrayDeque<>();
this.rewriteSuperPropertyReferencesWithoutSuper =
builder.rewriteSuperPropertyReferencesWithoutSuper;
}

static class Builder {
private final AbstractCompiler compiler;
private boolean rewriteSuperPropertyReferencesWithoutSuper = false;

Builder(AbstractCompiler compiler) {
checkNotNull(compiler);
this.compiler = compiler;
}

Builder rewriteSuperPropertyReferencesWithoutSuper(boolean value) {
rewriteSuperPropertyReferencesWithoutSuper = value;
return this;
}

RewriteAsyncIteration build() {
return new RewriteAsyncIteration(this);
}
}

@Override
Expand Down Expand Up @@ -558,16 +595,36 @@ private void prependTempVarDeclarations(LexicalContext ctx, NodeTraversal t) {
.useSourceInfoFromForTree(block));
}
for (String replacedMethodName : thisSuperArgsCtx.usedSuperProperties) {
// { // prefixBlock
// const $jscomp$asyncIter$this = this;
// const $jscomp$asyncIter$arguments = arguments;
// const $jscomp$asyncIter$super$get$x = () => super.x;
// }
// const super$get$x = () => super.x;
// OR avoid super for static method (class object -> superclass object)
// const super$get$x = () => Object.getPrototypeOf(this).x
// OR avoid super for instance method (instance -> prototype -> super prototype)
// const super$get$x = () => Object.getPrototypeOf(Object.getPrototypeOf(this)).x
Node superReference;
if (rewriteSuperPropertyReferencesWithoutSuper) {
// Rewrite to avoid using `super` within an arrow function.
// See more information on definition of this option.
// TODO(bradfordcsmith): RewriteAsyncIteration and RewriteAsyncFunctions have the
// same logic for dealing with super references. Consider having them share
// it from a common place instead of duplicating.

// static super: Object.getPrototypeOf(this);
superReference =
IR.call(IR.getprop(IR.name("Object"), IR.string("getPrototypeOf")), IR.thisNode());
if (!ctx.function.getParent().isStaticMember()) {
// instance super: Object.getPrototypeOf(Object.getPrototypeOf(this))
superReference =
IR.call(IR.getprop(IR.name("Object"), IR.string("getPrototypeOf")), superReference);
}
} else {
superReference = IR.superNode();
}

Node arrowFunction =
IR.arrowFunction(
IR.name(""),
IR.paramList(),
IR.getprop(IR.superNode(), IR.string(replacedMethodName)));
IR.getprop(superReference, IR.string(replacedMethodName)));
compiler.reportChangeToChangeScope(arrowFunction);
NodeUtil.addFeatureToScript(t.getCurrentFile(), Feature.ARROW_FUNCTIONS);
String superReplacementName = superPropGetterPrefix + replacedMethodName;
Expand Down
16 changes: 14 additions & 2 deletions src/com/google/javascript/jscomp/TranspilationPasses.java
Expand Up @@ -217,7 +217,13 @@ protected FeatureSet featureSet() {
new HotSwapPassFactory("rewriteAsyncFunctions") {
@Override
protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
return new RewriteAsyncFunctions(compiler);
return new RewriteAsyncFunctions.Builder(compiler)
// If ES6 classes will not be transpiled away later,
// transpile away property references that use `super` in async functions.
// See explanation in RewriteAsyncFunctions.
.rewriteSuperPropertyReferencesWithoutSuper(
!compiler.getOptions().needsTranspilationFrom(FeatureSet.ES6))
.build();
}

@Override
Expand All @@ -230,7 +236,13 @@ protected FeatureSet featureSet() {
new HotSwapPassFactory("rewriteAsyncIteration") {
@Override
protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
return new RewriteAsyncIteration(compiler);
return new RewriteAsyncIteration.Builder(compiler)
// If ES6 classes will not be transpiled away later,
// transpile away property references that use `super` in async iteration.
// See explanation in RewriteAsyncIteration.
.rewriteSuperPropertyReferencesWithoutSuper(
!compiler.getOptions().needsTranspilationFrom(FeatureSet.ES6))
.build();
}

@Override
Expand Down
6 changes: 4 additions & 2 deletions test/com/google/javascript/jscomp/IntegrationTest.java
Expand Up @@ -3988,7 +3988,8 @@ public void testAsyncFunctionSuper() {
"}",
"class Baz extends Foo {",
" bar() {",
" const $jscomp$async$this = this, $jscomp$async$super$get$bar = () => super.bar;",
" const $jscomp$async$this = this, $jscomp$async$super$get$bar =",
" () => Object.getPrototypeOf(Object.getPrototypeOf(this)).bar;",
" return $jscomp.asyncExecutePromiseGeneratorFunction(function*() {",
" yield Promise.resolve();",
" $jscomp$async$super$get$bar().call($jscomp$async$this);",
Expand Down Expand Up @@ -4041,7 +4042,8 @@ public void testAsyncIterationSuper() {
"class Baz extends Foo {",
" bar() {",
" const $jscomp$asyncIter$this = this,",
" $jscomp$asyncIter$super$get$bar = () => super.bar;",
" $jscomp$asyncIter$super$get$bar =",
" () => Object.getPrototypeOf(Object.getPrototypeOf(this)).bar;",
" return new $jscomp.AsyncGeneratorWrapper(function*() {",
" $jscomp$asyncIter$super$get$bar().call($jscomp$asyncIter$this).next();",
" }());",
Expand Down
73 changes: 72 additions & 1 deletion test/com/google/javascript/jscomp/RewriteAsyncFunctionsTest.java
Expand Up @@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;

import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -37,7 +38,10 @@ public void setUp() throws Exception {

@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new RewriteAsyncFunctions(compiler);
return new RewriteAsyncFunctions.Builder(compiler)
.rewriteSuperPropertyReferencesWithoutSuper(
!compiler.getOptions().needsTranspilationFrom(FeatureSet.ES6))
.build();
}

// Don't let the compiler actually inject any code.
Expand Down Expand Up @@ -142,6 +146,73 @@ public void testInnerSuperReference() {
"}"));
}

@Test
public void testInnerSuperCallEs2015Out() {
setLanguageOut(LanguageMode.ECMASCRIPT_2015);
test(
lines(
"class A {",
" m() {",
" return this;",
" }",
"}",
"class X extends A {",
" async m() {",
" return super.m();",
" }",
"}"),
lines(
"class A {",
" m() {",
" return this;",
" }",
"}",
"class X extends A {",
" m() {",
" const $jscomp$async$this = this;",
" const $jscomp$async$super$get$m =",
" () => Object.getPrototypeOf(Object.getPrototypeOf(this)).m;",
" return $jscomp.asyncExecutePromiseGeneratorFunction(",
" function* () {",
" return $jscomp$async$super$get$m().call($jscomp$async$this);",
" });",
" }",
"}"));
}

@Test
public void testInnerSuperCallStaticEs2015Out() {
setLanguageOut(LanguageMode.ECMASCRIPT_2015);
test(
lines(
"class A {",
" static m() {",
" return this;",
" }",
"}",
"class X extends A {",
" static async m() {",
" return super.m();",
" }",
"}"),
lines(
"class A {",
" static m() {",
" return this;",
" }",
"}",
"class X extends A {",
" static m() {",
" const $jscomp$async$this = this;",
" const $jscomp$async$super$get$m = () => Object.getPrototypeOf(this).m;",
" return $jscomp.asyncExecutePromiseGeneratorFunction(",
" function* () {",
" return $jscomp$async$super$get$m().call($jscomp$async$this);",
" });",
" }",
"}"));
}

@Test
public void testNestedArrowFunctionUsingThis() {
test(
Expand Down
Expand Up @@ -16,6 +16,7 @@
package com.google.javascript.jscomp;

import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -47,7 +48,10 @@ protected CompilerOptions getOptions() {

@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new RewriteAsyncIteration(compiler);
return new RewriteAsyncIteration.Builder(compiler)
.rewriteSuperPropertyReferencesWithoutSuper(
!compiler.getOptions().needsTranspilationFrom(FeatureSet.ES6))
.build();
}

@Test
Expand Down Expand Up @@ -225,7 +229,8 @@ public void testInnerSuperReferenceInAsyncGenerator() {
"}",
"class X extends A {",
" m() {",
" const $jscomp$asyncIter$super$get$m = () => super.m;",
" const $jscomp$asyncIter$super$get$m =",
" () => Object.getPrototypeOf(Object.getPrototypeOf(this)).m;",
" return new $jscomp.AsyncGeneratorWrapper(",
" function* () {",
" const tmp = $jscomp$asyncIter$super$get$m();",
Expand Down

0 comments on commit 79891f0

Please sign in to comment.