-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
File modifier parsing #60823
File modifier parsing #60823
Conversation
src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: CyrusNajmabadi <cyrus.najmabadi@gmail.com>
"""); | ||
N(SyntaxKind.CompilationUnit); | ||
{ | ||
N(SyntaxKind.IncompleteMember); |
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.
It seems perfectly valid to have a file partial ref struct
. I'll have to fix it to ensure we get a correct parse 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.
I was wrong. partial ref struct
is not allowed. You have to say ref partial struct
.
src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs
Outdated
Show resolved
Hide resolved
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 review pass (commit 10). I did not look in depth at tests.
src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs
Outdated
Show resolved
Hide resolved
@@ -1243,8 +1245,18 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors) | |||
break; | |||
} | |||
|
|||
case DeclarationModifiers.File: | |||
if (!IsFeatureEnabled(MessageID.IDS_FeatureFileTypes) && !ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false)) |
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 love hte feature flag check actually controlling behavior. i think we should just parse this out if ShouldContextualKeywordBeTreatedAsModifier returns true and then report an issue later if the feature is not available. 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's fair. This is also the cause of a breaking change in the new language version. This is also what we are doing for 'required' modifier. See related discussion.
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 ends up biasing our error recovery involving 'file' to: you meant to declare a member that uses this modifier, versus "you must have been using it as an identifier". Compare the _CSharp10 versions of tests to their CSharpNext or unsuffixed counterparts.
{ | ||
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.AsyncKeyword); | ||
Debug.Assert(this.CurrentToken.ContextualKind != this.CurrentToken.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.
consider explicitly stating which contextual keywords this is expected to undertand. i think it's nicer if the reader can know "ok, this is just for async/file/etc." as opposed to "oh... what agbout all the other contextual keywords that we might have??"
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.
Alternatively, consider passing in the token type we are looking to treat as a modifier.
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 now check GetModifier in the assert which achieves what we want--don't need to repeat ourselves here, and we are limited to allowing the contextual keywords which are modifiers.
@@ -8013,7 +7979,7 @@ private bool IsPossibleLocalDeclarationStatement(bool isGlobalScriptLevel) | |||
tk = this.CurrentToken.ContextualKind; | |||
|
|||
var isPossibleAttributeOrModifier = (IsAdditionalLocalFunctionModifier(tk) || tk == SyntaxKind.OpenBracketToken) | |||
&& (tk != SyntaxKind.AsyncKeyword || ShouldAsyncBeTreatedAsModifier(parsingStatementNotDeclaration: true)); | |||
&& (tk != SyntaxKind.AsyncKeyword || ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: 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.
Is this special case of AsyncKeyword correct now?
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.
Yeah, we don't treat 'file' as a modifier when parsing a local declaration, just like we don't treat 'required' as a modifier on local declarations in the required-members feature branch.
EOF(); | ||
} | ||
|
||
// PROTOTYPE(ft): is it fine that records parse as a single declaration here, but not other type kinds? |
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, I would expect all of the type kinds to parse like this. What is causing them to parse as above?
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.
When we try to parse the partial modifier, we check IsPartialMember()
:
roslyn/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Lines 1189 to 1192 in 228845e
case DeclarationModifiers.Partial: | |
var nextToken = PeekToken(1); | |
var isPartialType = this.IsPartialType(); | |
var isPartialMember = this.IsPartialMember(); |
IsPartialMember
simply asks, is this partial
, followed by a possible type, followed by a possible member name.
roslyn/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Lines 1468 to 1475 in 228845e
this.EatToken(); // partial | |
if (this.ScanType() == ScanTypeFlags.NotType) | |
{ | |
return false; | |
} | |
return IsPossibleMemberName(); |
In the case of partial file record
, this is true. In the case of partial file class
it is not. So in the former case we parse partial
as a modifier and go from there to produce a single type declaration, while in the latter case we assume partial
must be an identifier, and use it as a field type.
This is comparable to the parse we give for partial async class X
. SharpLab.
My preference here would be to file a "design debt" item to improve error recovery for contextual modifiers and order-sensitive modifiers across the board, and call this parse good enough for the purposes of feature development.
""", | ||
// (1,14): error CS1585: Member modifier 'ref' must precede the member type and name | ||
// file partial ref struct C { } | ||
Diagnostic(ErrorCode.ERR_BadModifierLocation, "ref").WithArguments("ref").WithLocation(1, 14)); |
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 parse and diagnostic are just awful. I know you're not really changing it that much, but this is just wrong.
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.
Have filed #61025
[Fact] | ||
public void MemberNamedFile_06() | ||
{ | ||
UsingNode($$""" |
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 a breaking change: is LDM comfortable with it?
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.
Adding a note to follow up on it.
src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs
Outdated
Show resolved
Hide resolved
} | ||
|
||
[Fact] | ||
public void MethodNamedRecord_02_CSharpNext() |
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 parse seems incorrect. I expect this to be a record with a missing name, not a constructor declaration(??)
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 think the parse is very similar as it is when the 'file' modifier is removed here. SharpLab.
Done review pass (commit 14) |
Related to #60819