-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Fix parser regression for bad related diagnostic on missing matching brackets #44158
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
Fix parser regression for bad related diagnostic on missing matching brackets #44158
Conversation
I'll admit up front that I don't fully understand the chain of events that led from the parser error to smart indentation breaking. While debugging I was seeing |
!!! I would strongly urge a revert on the commit for 4.3. |
src/compiler/parser.ts
Outdated
const expression = allowInAnd(parseExpression); | ||
parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenPosition); | ||
openParenExists ? parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenPosition) |
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.
Can you make these if else statements instead.. this makes it very hard to read
src/compiler/parser.ts
Outdated
const expression = allowInAnd(parseExpression); | ||
parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenPosition); | ||
openParenExists ? parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenPosition) | ||
: parseExpected(SyntaxKind.CloseParenToken); |
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 is needed in parseObjectLiteralExpression?
I think instead of changing this in all use cases, why not modify parseExpectedMatchingBrackets
to take in result of openingBracket so all cases are always covered and we dont need to review all use cases...
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 don't think it should be needed for object literal expression (or arrays) since we only try to parse them if an opening bracket has already been parsed (or scanned, not sure what the correct terminology is there).
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 bet the easiest way to move forward with this PR is to create a new one that starts with a revert of your previous revert PR, then add back a commit with a fix.
70e8a60
to
e38a0ce
Compare
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.
2 suggestions plus 1 wishful-thinking suggestion
src/compiler/parser.ts
Outdated
@@ -1552,6 +1555,23 @@ namespace ts { | |||
return false; | |||
} | |||
|
|||
function parseExpectedMatchingBrackets(openKind: SyntaxKind, closeKind: SyntaxKind, openParsed: boolean, openPosition: number) { | |||
if (!openParsed) { |
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 openParsed
is false, parseExpected
is equivalent to the code before if (lastError)
.
if openParsed
is true, the code before if (lastError)
should run.
Equivalent code runs either way, so I'd move the openParsed
check after const lastError = ...
If there was an elegant way to change parseExpected
to return its error, I'd recommend replacing if (token() = ...
through const lastError = ...
with const lastError = parseExpected(...
. But it's used all over the place with the current true | false
return type.
const openBracePosition = scanner.getTokenPos(); | ||
parseExpected(SyntaxKind.OpenBraceToken); | ||
const openBraceParsed = parseExpected(SyntaxKind.OpenBraceToken); | ||
const multiLine = scanner.hasPrecedingLineBreak(); | ||
const properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement, /*considerSemicolonAsDelimiter*/ true); | ||
if (!parseExpected(SyntaxKind.CloseBraceToken)) { | ||
const lastError = lastOrUndefined(parseDiagnostics); | ||
if (lastError && lastError.code === Diagnostics._0_expected.code) { | ||
addRelatedInfo( | ||
lastError, | ||
createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_to_match_the_token_here) | ||
); | ||
} | ||
} | ||
parseExpectedMatchingBrackets(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, openBraceParsed, openBracePosition); |
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.
Having to repeat openBracePosition/openBraceParsed every time is still a bit noisy. You could eliminate it using a closure, but I don't think it's worth the performance cost. Still, uh, here's what it would look like:
const parseCloseBrace = parseExpectedMatchingToken(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)
const multiline = ...
const properties = ...
parseCloseBrace()
And then the first few lines of parseExpectedMatchingBrackets look like this:
function parseExpectedMatchingBrackets(openKind: SyntaxKind, closeKind: SyntaxKind) {
const openPosition = getNodePos();
const openParsed = parseExpected(openKind);
return () => {
if (token() === closeKind) { // same as before, but inside a closure
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.
ahh, that's better. Why does it have significantly worse performance though?
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 have to create a closure each time, which closes over openKind
, closeKind
, openPosition
, openParsed
. The naive way to compile this is to put them onto the heap, so the GC now has 4 numbers and a function to collect.
However, those are all numbers or booleans, and v8 might emit fast code. I have no idea; so it's probably worth testing if you like the change enough -- the existing perf suite certainly has enough brackets to stress it.
780102b
to
fa19d47
Compare
@@ -121,7 +121,7 @@ tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(261,1): error TS | |||
if (retValue != 0 ^= { | |||
~~ | |||
!!! error TS1005: ')' expected. | |||
!!! related TS1007 tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts:22:20: The parser expected to find a ')' to match the '(' token here. | |||
!!! related TS1007 tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts:22:19: The parser expected to find a ')' to match the '(' token here. |
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.
Uh-oh, I didn't expect any baseline changes. I counted the spaces and 19 is correct counting from 0; 20 is correct counting from 1. I'm not sure which we do for chars, but I see lines counting from 1 in tests/baselines/reference/jsonParserRecovery/TypeScript_code.errors.txt
I think this change is wrong. Sorry for steering you wrong. =(
The parser expects the keywords
if
,do
,while
, andwith
to be followed by pairs of braces({
,}
) or parentheses ((
,)
).It was also attempting to unconditionally add a related diagnostic for missing closing brackets to the diagnostics for missing opening brackets. When the opening bracket did not exist, this typically meant that diagnostic span was being added beyond the EOF.
Here we avoid associating the missing closing bracket diagnostic with the opening bracket diagnostic if we fail to parse the former. The errors resulting from the regression broke smart indentation on the keywords, thus the indentation tests in this change. We don't have the same concern about
parseExpectedMatchingBrackets
with array and object literals since their parsings are triggered by their opening braces.Regressed by #40884