-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Leverage syntax cursor as part of reparse #39216
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
Conversation
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.
An initial question for context: is reparsing part of incremental parsing? If not, where does it happen?
I just started looking at the parser change. I'll finish tomorrow.
return parent.kind === SyntaxKind.TypeQuery || parent.kind === SyntaxKind.TypeReference; | ||
} | ||
return false; | ||
return (<QualifiedName>parent).right === 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.
I'm inferring that this change means
- QualifiedNames are only ever children of TypeQuery/TypeReference
- Unlike before, only top-level qualified names are identifier names, or else identifier names are parsed such that only top-level qualified names are actually passed to isIdentifierName.
Am I right?
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.
That is incorrect. The function is testing whether node
is an IdentifierName in ES. The IdentifierName production is generally used for property names (i.e. ({ foo: 1 })
or obj.foo
), where reserved words are not forbidden. A QualifiedName
is a TS-only syntax, but is essentially similar to a property access expression when used as an expression. The name of a QualifiedName
should always be considered an IdentifierName, even when it is not the child of a TypeQuery
or TypeReference
. The case this addresses is this:
export {};
import X = someNamespace.await;
Without this change, we would incorrectly error on await
since the namespace import is at the top level of a module.
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.
Actually, all identifiers are IdentifierName in ES. The difference is that there are essentially 4 categories of identifiers:
- IdentifierReference - An identifier that is a PrimaryExpression (e.g.,
id
inid()
,id.prop
,id + 1
, etc.). - BindingIdentifier - An identifier that introduces a binding in the current scope (e.g.,
id
invar id
,function id() {}
,class id {}
, etc.). - LabelIdentifier - An identifier that introduces a new label for use with
break
/continue
(e.g.,id
inid: while(true) break id
, etc.). - Any other IdentifierName such as the ones used in LiteralPropertyName and MemberExpression, ExportSpecifier.
There is only one caller to this function, and what it is actually checking are these static semantics:
IdentifierReference:
yield
BindingIdentifier:yield
LabelIdentifier:yield
It is a Syntax Error if the code matched by this production is contained in strict mode code.
IdentifierReference:
await
BindingIdentifier:await
LabelIdentifier:await
It is a Syntax Error if the goal symbol of the syntactic grammar is Module.
BindingIdentifier[Yield, Await]:
yield
It is a Syntax Error if this production has a
[Yield]
parameter.BindingIdentifier[Yield, Await]:
await
It is a Syntax Error if this production has an
[Await]
parameter.IdentifierReference[Yield, Await]: Identifier
BindingIdentifier[Yield, Await]: Identifier
LabelIdentifier[Yield, Await]: IdentifierIt is a Syntax Error if this production has a
[Yield]
parameter and StringValue of Identifier is"yield"
.
It is a Syntax Error if this production has an[Await]
parameter and StringValue of Identifier is"await"
.
So what this function is actually testing is that the node is in the other category.
Reparsing happens during initial parse (though it can also happen during incremental parse). We added reparsing for In ECMAScript, you start with either a Script or a Module goal symbol. When you parse a Script, In TypeScript, we don't distinguish between a Script or a Module. Instead, we parse the whole file, allowing As a result, we start by optimistically parsing the file as a Script (i.e. without the This is roughly analogous to how ECMAScript handles cover grammars. For TypeScript, we essentially have a CoverScriptOrModule cover grammar that we must reparse as necessary to result in the correct Script or Module goal that ECMAScript understands. |
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.
You should probably add a test for the decorator parsing change in this PR, no?
const diagnosticStart = findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= prevStatement.pos); | ||
const diagnosticEnd = diagnosticStart >= 0 ? findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= nextStatement.pos, diagnosticStart) : -1; | ||
if (diagnosticStart >= 0) { | ||
addRange(parseDiagnostics, savedParseDiagnostics, diagnosticStart, diagnosticEnd >= 0 ? diagnosticEnd : undefined); |
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.
If we reparse a statement, is it possible when we exit the speculation helper that we'd need to adjust diagnostic positions here? I guess not, since we stay in the speculation helper so long as the statement positions aren't aligned.
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.
No, we shouldn't need to adjust. If the resulting statement overlaps an existing statement that follows it then we would reparse the statement that follows as well and use the newly-generated diagnostics for that statement.
@weswigham The change to decorator parsing was added to improve the output seen in existing tests, such as the ones in topLevelAwaitErrors.1.ts. The result is this: https://github.com/microsoft/TypeScript/pull/39216/files?file-filters%5B%5D=.txt#diff-0b3f587cbe2d54bf17336640d40ccf9aR63-R65 Without this change, we end up parsing the |
@typescript-bot cherry-pick this to release-4.0 and LKG |
Heya @DanielRosenwasser, I've started to run the task to cherry-pick this into |
Component commits: 6298d84 Leverage syntax cursor as part of reparse
Hey @DanielRosenwasser, I've opened #39219 for you. |
@DanielRosenwasser shouldn't we just be merging master into |
* upstream/master: Do not add reexported names to the exportSpecifiers list of moduleinfo (microsoft#39213) Update user baselines (microsoft#39214) Leverage syntax cursor as part of reparse (microsoft#39216) Update failed test tracking to support Mocha 6+ (microsoft#39211) Update user baselines (microsoft#39196) LEGO: check in for master to temporary branch. # Conflicts: # src/compiler/parser.ts
🤖 Pick PR #39216 (Leverage syntax cursor as part of r...) into release-4.0
When reparsing top-level
await
, we might end up in a state where reparse would have consumed more tokens than the original statement. This changes top-levelawait
reparse to leverage aSyntaxCursor
and continue to reparse following statements if the current statement'send
changes.This also changes our parse for BindingIdentifier to allow
yield
andawait
during parse, but error on them during bind just as we do for other strict-mode reserved identifiers.Fixes #39186