-
Notifications
You must be signed in to change notification settings - Fork 744
Add toplevel await #149
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
Add toplevel await #149
Conversation
1. No incremental reparsing, although the non-incremental reparsing only reparses the statements that might contain await. 2. No TransformFlags, just an approximation in the parser. I haven't added all the code to turn off PossibleTopLevelAwait because I'm not convinced my approach is right. There are 16-17 more places to save/overwrite the toplevel await tracking bool. I made enough changes to pass our test suite, that's all. It's also possible that reparsing, say, a FunctionDeclaration, doesn't change anything anyway, but it's inefficient to reparse it when it's not needed.
| } | ||
|
|
||
| func (p *Parser) reparseTopLevelAwait(sourceFile *ast.SourceFile) *ast.Node { | ||
| statements := []*ast.Statement{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this part is a simplified port from tsc. It's simplified because it drops the attempt to use the incremental parser (which doesn't exist in tsgo).
1. Move possible-await cache to Parser. 2. Ignore "await" identifiers in the same places that tsc does, but implemented in the parser instead. This also caches fewer statements in the possible-await cache than the previous commit.
|
|
||
| func (p *Parser) parseClassDeclarationOrExpression(pos int, hasJSDoc bool, modifiers *ast.ModifierList, kind ast.Kind) *ast.Node { | ||
| saveContextFlags := p.contextFlags | ||
| saveHasAwaitIdentifier := p.statementHasAwaitIdentifier |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are all the save/restores that approximate TransformFlags propagation in tsc.
internal/compiler/parser.go
Outdated
| } | ||
|
|
||
| func (p *Parser) containsPossibleTopLevelAwait(node *ast.Node) bool { | ||
| return !(node.Flags&ast.NodeFlagsAwaitContext != 0) && p.getPossibleAwait(node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return !(node.Flags&ast.NodeFlagsAwaitContext != 0) && p.getPossibleAwait(node) | |
| return node.Flags&ast.NodeFlagsAwaitContext == 0 && p.getPossibleAwait(node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, yes, I saw that as I pasted it and then forgot to go fix it.
That might actually have some effects on things. What errors do you issue on the following cases? // @filename: a.ts
function foo(x = await(10)) {
}
// @filename: b.ts
async function bar(x = await(10)) {
}
// @filename: c.ts
export {};
export function fooExported(x = await(10)) {
}
// @filename: d.ts
export {};
export async function barExported(x = await(10)) {
}Weirdly, we don't have any test case where |
DanielRosenwasser
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't really looked at the diagnostic stitching code yet, but I've left some comments on style and how we run through the statement list that you might want to consider.
Instead of tracking await-possible statements and searching to reconstruct spans, build spans during parsing. Then reparsing can read the spans directly, which simplifies code quite a bit. The downside is that I need to provide indices in parseList. Because Go isn't Javascript, I decided to copy parseList to parseListIndex. Two other options: change parseList to always provide indices then - Add ignored indices to parseStatement et al. - Add a parseList that passes an adapter func which ignores indices. I don't have a strong opinion about which to use, although I lean slightly to adding a second parameter to parseStatement et al. The current code isn't tested because of the way AST baselines aren't in main. I'll push a followup commit with fixes.
|
The new commit simplifies tracking a lot compared to Strada. Instead of tracking await-possible statements and searching to The downside is that I need to provide indices in parseList. Because Go isn't Javascript, I decided to copy parseList to parseListIndex. Two other options: change parseList to always provide indices then
If adapter funcs aren't optimised away, I'd say the current code is best. I tried adding I've tested on our current test cases but I need to try Daniel's cases he suggested on this PR. |
You can largely assume that simple one-liners that aren't generic are inlineable. If you wanted to double check, there are flags that you can pass to the compiler that would show that information. |
Since |
Not Weirdly. |
|
@DanielRosenwasser re your test cases: a: no (parse) errors, @rbuckton Are (c) and (d) correct? |
(c) should be an error because a module is always parsed in strict mode, and (d) should be an error because it will be parsed as an AwaitExpression, but would be a syntax error per https://tc39.es/ecma262/#sec-async-function-definitions-static-semantics-early-errors, which we could also treat as a grammar error. There are two other scenarios to consider: // @filename: e.ts
function fooStrict(x = await(10)) {
"use strict"
}
// @filename: f.ts
(function () {
"use strict"
function fooStrict(x = await(10)) {
}
})()These should be grammar errors because Please note that V8 seems to have a bug here, as it incorrectly allows |
Sorry, this was an incorrect interpretation of 13.1.1. 13.1.1 only disallows To clarify:
|
|
Ah, I see the difference. a,b,c,d,e,f all give grammar errors after parsing correctly. I think the PR's code is working the same as the old code -- by the end of parsing it produces exactly the same trees and exactly the same errors (none). |
I take it the grammar errors for this are not yet ported in the checker, or are they missing/broken in both compilers? |
|
The grammar errors are present and correct in Strada. |
| p.parseExpected(ast.KindImportKeyword) | ||
| afterImportPos := p.nodePos() | ||
| // We don't parse the identifier here in await context, instead we will report a grammar error in the checker. | ||
| saveHasAwaitIdentifier := p.statementHasAwaitIdentifier |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish we didn't have all of this manual saving/restoring in favor of some sort of defer but...
| result.ScriptKind = p.scriptKind | ||
| } | ||
| } | ||
| p.possibleAwaitSpans = []int{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than an int array, you could make this a struct {} but I guess it's fine.
I haven't added all the code to turn off PossibleTopLevelAwait because I'd like some agreement that this approach is good enough. I made enough changes to pass our test suite, in ClassDeclaration and ImportDeclaration parsing, but there are 16-17 more places to save/overwrite the toplevel await tracking bool.
That means in the current commit, for example, a toplevel FunctionDeclaration that contains
awaitsomewhere inside will reparse the FunctionDeclaration, even though the old compiler would not. It's possible that reparsing this doesn't change anything anyway, but it's inefficient to reparse it when it's not needed.