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

Better error output for await in non-async functions #6720

Closed
babel-bot opened this issue Sep 8, 2016 · 15 comments
Closed

Better error output for await in non-async functions #6720

babel-bot opened this issue Sep 8, 2016 · 15 comments
Labels
claimed good first issue i: enhancement imported outdated A closed issue/PR that is archived due to age. Recommended to make a new issue pkg: parser

Comments

@babel-bot
Copy link
Collaborator

Issue originally reported by @danez in babel/babylon#113

Issue originally made by @SystemParadox on #2387

function test() {
    await foo();
}

Throws: Unexpected token(2:8)

This is a bit misleading. Can we change this to say something like "await can only be used in async functions"?

Thanks.

@babel-bot
Copy link
Collaborator Author

Comment originally made by @motiz88

Hi! I'm trying to implement this and learn some Babylon internals at the same time.

tl;dr: I have a question about a specific implementation idea - see the very end

💭 Larger discussion: await spec

The spec says here under note 1 that (emphasis mine)

yield is Identifier or YieldExpression [ ... ] depending on context. await is always parsed as an AwaitExpression but is a Syntax Error via static semantics. [ ... ]

Does this mean we can skip trying to disambiguate await and just treat it as starting an AwaitExpression everywhere (i.e. immediately an error if not in an async context)? Probably not. I must be missing something. But it would make fixing this issue quite trivial. Moving on...

👉 The issue at hand

I'm assuming the intent here is to optimistically parse await as Identifier in non-async contexts, the way it's already being done. But then if the parse turns out to be a dead end, we should "remember" this choice and emit a descriptive error on the await - not at the dead end.

I came up with the following logic:

  1. When an Identifier with name === "await" is parsed, if we didn't enter parseAwait because we're in a non-async context, save the identifier's position in state.potentialIllegalAwaitAt and keep parsing normally.
  2. When parsing a node fails via unexpected(), and if state.potentialIllegalAwaitAt >= node.start, raise an "await can only be used in async function" error instead of the default "unexpected token".

I'm not 100% sure how to intercept the unexpected() occurrences elegantly while still having access to the current node - which is needed for the check in (2) above. My first approach involved patching a lot of node types and looked a bit like this:

pp.checkIllegalAwaitAndRethrow = function (err, node) {
  if (this.state.potentialIllegalAwaitAt >= node.start) {
    this.raise(this.state.potentialIllegalAwaitAt, "await can only be used in async functions");
  } else {
    throw err;
  }
};

pp.semicolonCheckIllegalAwait = function (node) {
  try {
    this.semicolon();
  } catch (err) {
    this.checkIllegalAwaitAndRethrow(err, node);
  }
};

// Then for *many, many* node types do something like this:
pp.parseReturnStatement = function (node) {
// ... snip ...
  if (this.isLineTerminator()) {
    node.argument = null;
  } else {
    node.argument = this.parseExpression();
    this.semicolonCheckIllegalAwait(node); // instead of this.semicolon();
  }
// ... snip ...
};

But it got out of hand very quickly.

So here's my new recipe, plus some questions.

  1. Manage a "current node" stack in startNode/finishNode (Is this a viable approach? Does something like this already exist?)
  2. In unexpected() itself, check for this.state.potentialIllegalAwaitAt >= currentNode.start and override the message if true.

Honestly, it still seems a bit of a heavy solution that I'm not sure is justifiable here. But at least this way the problem gets treated as a "cross cutting concern", with a lot less code.

I'd appreciate any feedback/ideas! 😀

@babel-bot
Copy link
Collaborator Author

Comment originally made by @kaicataldo

Just realized there actually is a PR open already (sorry, thought that only discussion had occurred), so I'll go ahead and close mine.

@babel-bot
Copy link
Collaborator Author

Comment originally made by @kaicataldo

@motiz88 Actually, looked at your PR and the solution I came up with is very different. Don't want to step on your toes here, but let me know what you think of this: https://github.com/babel/babylon/compare/master...kaicataldo:betterawaiterrormessaging?expand=1

If you think it looks good, I can make a PR with this - just wanted to check in with you since you've already done work on this.

These changes would also fix part of this issue: babel/babylon#134, warning for when enum and await are used as identifiers when source type is module. The implementation of better error messages for await is kind of tied to disallowing await as a reserved word (for AssignmentExpressions, since that all happens in expression.parseExprAtom()), and making a generic solution for both reserved words made more sense to me than only implementing await.

Would love to hear what @danez and @hzoo think too!

@babel-bot
Copy link
Collaborator Author

Comment originally made by @kaicataldo

I'm actually going to see if I can extract the reserved identifier checks out so it's easier to see how all this fits together. Running into some challenges with my own solution - it works great when source type is module, but might actually have to do some kind of flag in state when an await keyword is found for when the source type is script, since identifiers are allowed to be called await. The easiest thing is to have smarter warnings for source type module and keep the same warnings for source type script.

@babel-bot
Copy link
Collaborator Author

Comment originally made by @motiz88

Hey @kaicataldo, apparently GitHub lost a reply I sent via e-mail so I'll try to address everything again.

For one thing, don't worry about my toes 😄 Especially since my PR is a work-in-progress and in a bit of stasis at the moment. But I think allowing await only as the LHS of AssignmentExpression is incorrect, as you've already noticed.

My own solution can probably be simplified and made more correct with the use of lookahead(), which I wasn't actually aware of when I started. I might not get back to it for a while though, so feel free to clone that branch and pull anything of interest from it. e.g.

some kind of flag in state when an await keyword is found

is exactly what I did.

@babel-bot
Copy link
Collaborator Author

Comment originally made by @motiz88

Hey @kaicataldo, don't worry about my toes 😄 But doesn't your current
solution preclude other valid uses of await as an identifier? For example
await.foo, await[foo], await / foo, await(foo), etc. There's a
partial list in my PR.

On Oct 23, 2016 06:08, "Kai Cataldo" notifications@github.com wrote:

@motiz88 https://github.com/motiz88 Actually, looked at your PR and the
solution I came up with is very different. Don't want to step on your toes
here, but let me know what you think of this: kaicataldo/babel@8358cff
kaicataldo/babylon@8358cff

Maybe I'm missing something, but it seems to get the job done. If you
think it looks good, I can make a PR with this - just wanted to check in
with you since you've already done work on this.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
babel/babylon#113 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ACJHpVY0WNZPTHrhffrxdtE-uEq9076gks5q2s-1gaJpZM4J4F4v
.

@hzoo
Copy link
Member

hzoo commented Aug 21, 2018

From https://twitter.com/sebmck/status/1031978115881291776

You can just add a check for state.inFunction in checkReservedWord

We can just cover the basic case for now

@aulisius
Copy link

aulisius commented Aug 24, 2018

Is someone working on this right now? Are there any open PRs?

@existentialism
Copy link
Member

@aulisius not at the moment!

@aulisius
Copy link

Cool! I'll like to take a crack at it, then :D !

@existentialism
Copy link
Member

@aulisius awesome! feel to free to join us on slack if you haven't already!

@brodeynewman
Copy link

How's the progress on this one? I'd like to give it a shot!

@nicolo-ribaudo
Copy link
Member

You can work on this 🙂
If you need help, join our slack channel.

@brodeynewman
Copy link

Awesome 😄

@danez
Copy link
Member

danez commented Nov 6, 2018

The output is now await is a reserved word (2:4) and #7727 might have an effect on this.

Closing as I think the output is better now.

@danez danez closed this as completed Nov 6, 2018
@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Feb 5, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Feb 5, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
claimed good first issue i: enhancement imported outdated A closed issue/PR that is archived due to age. Recommended to make a new issue pkg: parser
Projects
None yet
Development

No branches or pull requests

8 participants