-
Notifications
You must be signed in to change notification settings - Fork 4k
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 parsing of 'file[]' and 'required[]' in old language versions #66769
Conversation
|
||
if (!parsingStatementNotDeclaration) | ||
{ | ||
var currentTokenKind = this.CurrentToken.Kind; | ||
if (IsPossibleStartOfTypeDeclaration(currentTokenKind) || | ||
if ((IsPossibleStartOfTypeDeclaration(currentTokenKind) && currentTokenKind != SyntaxKind.OpenBracketToken) || |
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.
what happens with other sorts of suffixes? like ?
and *
?
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 do the right thing in those cases. I think the issue here is IsPossibleStartOfTypeDeclaration
is the wrong helper to use here. That helper tells us if we might be at the start of an attribute list, or start of a modifier list, or at a type declaration keyword like class
/struct
.
I think what we actually want to know is, is this a non-contextual modifier or a type declaration keyword like class
/struct
/etc. Since there's no valid case in which this contextual keyword would be followed by an attribute list.
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 document this explicitly (and make sure we have tests here if not already present).
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.
Done, thanks
@@ -325,6 +325,7 @@ internal CompilationUnitSyntax ParseCompilationUnitCore() | |||
} | |||
} | |||
|
|||
/// <summary>Are we possibly at the start of an attribute list, or at a modifier which is valid on a type, or on a keyword of a type declaration?</summary> | |||
private static bool IsPossibleStartOfTypeDeclaration(SyntaxKind kind) |
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.
Note that the usage of this method on line 1187 (ParseModifiers) really wants to be "is this a valid modifier for a type declaration".
It really makes one wish for reusable/composable patterns, versus being tempted to copy/paste this switch statement around.
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 only fully valid usage of this method IMO is in IsPossibleNamespaceMemberDeclaration.
[Fact] | ||
public void TestNewPartialArray() | ||
{ | ||
UsingTree("new partial[1]"); |
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 used to parse as a bad member declaration, as if it were new partial
followed by an attribute list, which doesn't really make sense.
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.
Might want to check the behavior of new file[1]
and new required[1]
at the top level as well.
As part of this issue I found (I think) that we regressed scenarios like |
@@ -8062,7 +8068,7 @@ private bool IsPossibleNewExpression() | |||
|
|||
// class, struct, enum, interface keywords, but also other modifiers that are not allowed after | |||
// partial keyword but start class declaration, so we can assume the user just swapped them. | |||
if (IsPossibleStartOfTypeDeclaration(PeekToken(2).Kind)) | |||
if (IsTypeModifierOrTypeKeyword(PeekToken(2).Kind)) |
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 things to note:
- this helper is only called for ParseMemberDeclarationOrStatement. i.e., top-level declarations that could either be a member or a statement.
- The intent here seems to be to ask: are we looking at
new partial
followed by something which indicates this is part of a member declaration.new partial [
isn't going to parse as a single member declaration.new partial MODIFIER
is not valid, but we still try to keep going and parse as a single erroneous member declaration in such a case
This probably doesn't behave well when new partial
is followed by a contextual keyword like new partial file ...
. That is still an error case at this point, though, so it just means our error recovery won't be the best. Eventually I'm sure we'll do the work to relax partial modifier ordering, at which point we might be able to refactor a bunch of this code, and maybe do something like "parse the modifiers and see if we had a partial modifier. if it's not the last modifier then do a LangVersion check".
@@ -1870,6 +1870,373 @@ public void RequiredModifierIncompleteMember_05() | |||
EOF(); | |||
} | |||
|
|||
[Fact] | |||
public void TypeNamedRequired_CSharp10() |
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.
There's no type named required in this test, unlike the file version :).
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'll add back 'class required' here, perhaps it's helpful to be clear about how it parses in each language version.
@dotnet/roslyn-compiler for a second review |
Missed the window for 17.5, so will merge to 17.6 to start with. |
[Theory] | ||
[InlineData("file")] | ||
[InlineData("required")] | ||
public void TopLevel_NewContextualKeywordArray(string keyword) |
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.
|
||
public unsafe class C | ||
{ | ||
public file _file; |
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.
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 feel this is adequately covered by TypeNamedRequired_CSharp10
and TypeNamedRequired_CSharp11
Closes #66646