-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Improve error recovery around 'scoped' modifier parsing #81636
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
base: main
Are you sure you want to change the base?
Conversation
| return true; | ||
|
|
||
| using var beforeScopedResetPoint = this.GetDisposableResetPoint(resetOnDispose: false); | ||
| using var beforeScopedResetPoint = this.GetDisposableResetPoint(resetOnDispose: true); |
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 need to for dual reset points. and we can always trivially clean up. we just let the caller then eat the token if this function says it is a keyword.
| Diagnostic(ErrorCode.ERR_IdentifierExpected, "readonly").WithLocation(3, 37), | ||
| // (3,37): error CS1003: Syntax error, ',' expected | ||
| // public static void M(ref scoped readonly int p) => throw null; | ||
| Diagnostic(ErrorCode.ERR_SyntaxError, "readonly").WithArguments(",").WithLocation(3, 37), |
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.
lots of the PR will just be syntax errors going away, and better errors about misplaced 'scopes' modifiers.
| Diagnostic(ErrorCode.ERR_RefReadOnlyWrongOrdering, "readonly").WithLocation(3, 26), | ||
| // (3,35): error CS0246: The type or namespace name 'scoped' could not be found (are you missing a using directive or an assembly reference?) | ||
| // public static void M(readonly scoped ref int p) => throw null; | ||
| Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "scoped").WithArguments("scoped").WithLocation(3, 35), |
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.
or removing errors where we considered it a type name, but now realie it should be treated as a modifier.
| Diagnostic(ErrorCode.ERR_ScopedAfterInOutRefReadonly, "scoped").WithLocation(1, 29), | ||
| // (1,47): error CS8936: Feature 'ref fields' is not available in C# 10.0. Please use language version 11.0 or greater. | ||
| // void F(ref scoped int b, in scoped int c, out scoped int d) { } | ||
| Diagnostic(ErrorCode.ERR_ScopedAfterInOutRefReadonly, "scoped").WithLocation(1, 47)]); |
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.
the difference between these two is just the errors about a feature not being available. is tehre a helper to filter those out appropriately to make the test cleaner?
| // (1,54): error CS1003: Syntax error, ',' expected | ||
| // void F(ref scoped int b, in scoped int c, out scoped int d) { } | ||
| Diagnostic(ErrorCode.ERR_SyntaxError, "int").WithArguments(",").WithLocation(1, 54) | ||
| ); |
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.
in some tests all syntax diagnostics went away. as such, i added semantic diagnostics to show we still report problems with the scoped modifier.
| M(SyntaxKind.CommaToken); | ||
| N(SyntaxKind.Parameter); | ||
| { | ||
| N(SyntaxKind.ScopedKeyword); |
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 tests demonstrate better understanding, with 'scoped' properly being treated as a modifier.
| Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 26)]); | ||
|
|
||
| N(SyntaxKind.ParenthesizedExpression); | ||
| N(SyntaxKind.ParenthesizedLambdaExpression); |
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.
we didn't even realize this was a lambda before...
|
@dotnet/roslyn-compiler this is ready for review. |
|
|
||
| private bool IsPossibleScopedKeyword(bool isFunctionPointerParameter) | ||
| { | ||
| using var _ = this.GetDisposableResetPoint(resetOnDispose: true); |
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.
for those keeping count, this made for a triple nested reset point. We now just have one reset point.
| // trivial case. scoped ref/out/in/this is definitely the scoped keyword. Note: the only actual legal | ||
| // cases are `scoped ref`, `scoped out`, and `scoped in`. But we detect and allow `scoped this`, `scoped | ||
| // params` and `scoped readonly` as well. These will be reported as errors later in binding. | ||
| if (IsParameterModifierExcludingScoped(this.CurrentToken)) |
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.
we intentionally allow a broader set than before. so you can say things like scoped this. This is caught later in the binder.
| return false; | ||
|
|
||
| // In C# 14 we decided that within a lambda 'scoped' would *always* be a keyword. | ||
| // In C# 14 we decided that within a lambda 'scoped' would *always* be a modifier, not a type. |
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.
refining this comment. It wasn't 100% accurate. It's not always a keyword. But it is a modifier if it previous would have been allowed as a type.
| bool isFunctionPointerParameter, | ||
| bool isLambdaParameter) | ||
| { | ||
| if (this.CurrentToken.ContextualKind != SyntaxKind.ScopedKeyword) |
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 got much easier as we just need to return a bool saying if this should be considered a modifier. We don't actually have to do all the setting-up/rewinding if we change our minds. This means we just need a single reset point in here, and the caller can just eat depending on what we return.
src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs
Outdated
Show resolved
Hide resolved
|
@dotnet/roslyn-compiler ptal. I would likke to take this before #81604 as the latter ends up changing error recovery at the top level, and we have a bunch of tests that test pathological 'scoped' cases at the top level. These result in bad cascading errors today, and bad (but different) cascading errors after that change (making the diff messy). By cleaning things up here and making scoped-parsing very resilient and clean, it improves the end user UX and helps out other changes to improve bad parsing in other scenarios. |
We now allow a misplacced 'scoped' keyword to be treated as a normal modifier, and move teh reporting of issues to the binding phase. Today we get entirely thrown off and enter very bad error recovery that often gets thrown entirely off, creating tons of cascading errors.
Note: the net effect of this is much fewer errors, and much better error recovery. The added length in this PR is the addition of new tests, or updating existing tests to report binding errors to demonstrate that errors are still produced semantically where they would have previously been syntactic.