Skip to content
This repository has been archived by the owner on May 19, 2018. It is now read-only.

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

Closed
danez opened this issue Sep 8, 2016 · 7 comments
Closed

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

danez opened this issue Sep 8, 2016 · 7 comments

Comments

@danez
Copy link
Member

danez commented Sep 8, 2016

Issue originally made by @SystemParadox on babel/babel#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.

@motiz88
Copy link
Contributor

motiz88 commented Sep 12, 2016

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! 😀

@kaicataldo
Copy link
Member

kaicataldo commented Oct 22, 2016

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

@kaicataldo
Copy link
Member

kaicataldo commented Oct 23, 2016

@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: #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!

@kaicataldo
Copy link
Member

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.

@motiz88
Copy link
Contributor

motiz88 commented Oct 24, 2016

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.

@motiz88
Copy link
Contributor

motiz88 commented Oct 25, 2016

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/babylon@8358cff
kaicataldo@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
#113 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ACJHpVY0WNZPTHrhffrxdtE-uEq9076gks5q2s-1gaJpZM4J4F4v
.

@babel-bot
Copy link

This issue has been moved to babel/babel#6720.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants