Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow await inside async arrow params #10469

Merged
merged 2 commits into from Oct 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
83 changes: 59 additions & 24 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -490,11 +490,7 @@ export default class ExpressionParser extends LValParser {
// Parse unary operators, both prefix and postfix.

parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression {
if (
this.isContextual("await") &&
(this.scope.inAsync ||
(!this.scope.inFunction && this.options.allowAwaitOutsideFunction))
) {
if (this.isContextual("await") && this.isAwaitAllowed()) {
return this.parseAwait();
} else if (this.state.type.prefix) {
const node = this.startNode();
Expand Down Expand Up @@ -676,8 +672,8 @@ export default class ExpressionParser extends LValParser {
const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos;
this.state.maybeInArrowParameters = true;
this.state.yieldPos = 0;
this.state.awaitPos = 0;
this.state.yieldPos = -1;
this.state.awaitPos = -1;

this.next();

Expand Down Expand Up @@ -716,8 +712,34 @@ export default class ExpressionParser extends LValParser {

// We keep the old value if it isn't null, for cases like
// (x = async(yield)) => {}
this.state.yieldPos = oldYieldPos || this.state.yieldPos;
this.state.awaitPos = oldAwaitPos || this.state.awaitPos;
//
// Hi developer of the future :) If you are implementing generator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

// arrow functions, please read the note below about "await" and
// verify if the same logic is needed for yield.
if (oldYieldPos !== -1) this.state.yieldPos = oldYieldPos;

// Await is trickier than yield. When parsing a possible arrow function
// (e.g. something starting with `async(`) we don't know if its possible
// parameters will actually be inside an async arrow function or if it is
// a normal call expression.
// If it ended up being a call expression, if we are in a context where
// await expression are disallowed (and thus "await" is an identifier)
// we must be careful not to leak this.state.awaitPos to an even outer
// context, where "await" could not be an identifier.
// For example, this code is valid because "await" isn't directly inside
// an async function:
//
// async function a() {
// function b(param = async (await)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sloppy mode 😢

// }
// }
//
if (
(!this.isAwaitAllowed() && !oldMaybeInArrowParameters) ||
oldAwaitPos !== -1
) {
this.state.awaitPos = oldAwaitPos;
}
}

this.state.maybeInArrowParameters = oldMaybeInArrowParameters;
Expand Down Expand Up @@ -1231,8 +1253,8 @@ export default class ExpressionParser extends LValParser {
const oldAwaitPos = this.state.awaitPos;
const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody;
this.state.maybeInArrowParameters = true;
this.state.yieldPos = 0;
this.state.awaitPos = 0;
this.state.yieldPos = -1;
this.state.awaitPos = -1;
this.state.inFSharpPipelineDirectBody = false;

const innerStartPos = this.state.start;
Expand Down Expand Up @@ -1310,8 +1332,8 @@ export default class ExpressionParser extends LValParser {

// We keep the old value if it isn't null, for cases like
// (x = (yield)) => {}
this.state.yieldPos = oldYieldPos || this.state.yieldPos;
this.state.awaitPos = oldAwaitPos || this.state.awaitPos;
if (oldYieldPos !== -1) this.state.yieldPos = oldYieldPos;
if (oldAwaitPos !== -1) this.state.awaitPos = oldAwaitPos;

if (!exprList.length) {
this.unexpected(this.state.lastTokStart);
Expand Down Expand Up @@ -1804,8 +1826,8 @@ export default class ExpressionParser extends LValParser {
): T {
const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos;
this.state.yieldPos = 0;
this.state.awaitPos = 0;
this.state.yieldPos = -1;
this.state.awaitPos = -1;

this.initFunction(node, isAsync);
node.generator = !!isGenerator;
Expand Down Expand Up @@ -1842,8 +1864,8 @@ export default class ExpressionParser extends LValParser {
const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos;
this.state.maybeInArrowParameters = false;
this.state.yieldPos = 0;
this.state.awaitPos = 0;
this.state.yieldPos = -1;
this.state.awaitPos = -1;

if (params) this.setArrowFunctionParameters(node, params);
this.parseFunctionBody(node, true);
Expand Down Expand Up @@ -2117,11 +2139,18 @@ export default class ExpressionParser extends LValParser {
);
}

if (this.scope.inAsync && word === "await") {
this.raise(
startLoc,
"Can not use 'await' as identifier inside an async function",
);
if (word === "await") {
if (this.scope.inAsync) {
this.raise(
startLoc,
"Can not use 'await' as identifier inside an async function",
);
} else if (
this.state.awaitPos === -1 &&
(this.state.maybeInArrowParameters || this.isAwaitAllowed())
) {
this.state.awaitPos = this.state.start;
}
}

if (this.state.inClassProperty && word === "arguments") {
Expand Down Expand Up @@ -2151,10 +2180,16 @@ export default class ExpressionParser extends LValParser {
}
}

isAwaitAllowed(): boolean {
if (this.scope.inFunction) return this.scope.inAsync;
if (this.options.allowAwaitOutsideFunction) return true;
return false;
}

// Parses await expression inside async function.

parseAwait(): N.AwaitExpression {
if (!this.state.awaitPos) {
if (this.state.awaitPos === -1) {
this.state.awaitPos = this.state.start;
}
const node = this.startNode();
Expand Down Expand Up @@ -2183,7 +2218,7 @@ export default class ExpressionParser extends LValParser {
// Parses yield expression inside generator.

parseYield(noIn?: ?boolean): N.YieldExpression {
if (!this.state.yieldPos) {
if (this.state.yieldPos === -1) {
this.state.yieldPos = this.state.start;
}
const node = this.startNode();
Expand Down
7 changes: 5 additions & 2 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -1050,12 +1050,14 @@ export default class StatementParser extends ExpressionParser {
node.id = this.parseFunctionId(requireId);
}

const oldMaybeInArrowParameters = this.state.maybeInArrowParameters;
const oldInClassProperty = this.state.inClassProperty;
const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos;
this.state.maybeInArrowParameters = false;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed because this test started failing:

async function f() {
  (function await() {});
}

this.state.inClassProperty = false;
this.state.yieldPos = 0;
this.state.awaitPos = 0;
this.state.yieldPos = -1;
this.state.awaitPos = -1;
this.scope.enter(functionFlags(node.async, node.generator));

if (!isStatement) {
Expand Down Expand Up @@ -1084,6 +1086,7 @@ export default class StatementParser extends ExpressionParser {
this.checkFunctionStatementId(node);
}

this.state.maybeInArrowParameters = oldMaybeInArrowParameters;
this.state.inClassProperty = oldInClassProperty;
this.state.yieldPos = oldYieldPos;
this.state.awaitPos = oldAwaitPos;
Expand Down
6 changes: 3 additions & 3 deletions packages/babel-parser/src/parser/util.js
Expand Up @@ -159,15 +159,15 @@ export default class UtilParser extends Tokenizer {

checkYieldAwaitInDefaultParams() {
if (
this.state.yieldPos &&
(!this.state.awaitPos || this.state.yieldPos < this.state.awaitPos)
this.state.yieldPos !== -1 &&
(this.state.awaitPos === -1 || this.state.yieldPos < this.state.awaitPos)
) {
this.raise(
this.state.yieldPos,
"Yield cannot be used as name inside a generator function",
);
}
if (this.state.awaitPos) {
if (this.state.awaitPos !== -1) {
this.raise(
this.state.awaitPos,
"Await cannot be used as name inside an async function",
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-parser/src/tokenizer/state.js
Expand Up @@ -97,8 +97,8 @@ export default class State {
decoratorStack: Array<Array<N.Decorator>> = [[]];

// Positions to delayed-check that yield/await does not exist in default parameters.
yieldPos: number = 0;
awaitPos: number = 0;
yieldPos: number = -1;
awaitPos: number = -1;

// Token store.
tokens: Array<Token | N.Comment> = [];
Expand Down
@@ -0,0 +1 @@
async (a = ({ await }) => {}) => {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually would have read this as a ReferenceError at first but after paying closer attention the early error makes sense 🤔

@@ -0,0 +1,3 @@
{
"throws": "Await cannot be used as name inside an async function (1:20)"
}
@@ -0,0 +1,3 @@
async function f() {
function g(x = async(await)) {}
}