Skip to content
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

Merged
merged 16 commits into from
May 9, 2022
Merged

Conversation

RikkiGibson
Copy link
Contributor

@RikkiGibson RikkiGibson commented Apr 18, 2022

Related to #60819

@RikkiGibson RikkiGibson marked this pull request as ready for review April 18, 2022 22:40
@RikkiGibson RikkiGibson requested a review from a team as a code owner April 18, 2022 22:40
@RikkiGibson RikkiGibson changed the base branch from main to features/file-types April 18, 2022 22:40
@RikkiGibson RikkiGibson requested a review from a team as a code owner April 20, 2022 22:20
@RikkiGibson RikkiGibson removed the request for review from a team April 21, 2022 21:09
""");
N(SyntaxKind.CompilationUnit);
{
N(SyntaxKind.IncompleteMember);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IncompleteMember

Is this expected?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

@RikkiGibson RikkiGibson requested a review from 333fred April 27, 2022 19:06
Copy link
Member

@333fred 333fred left a 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.

@@ -1243,8 +1245,18 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors)
break;
}

case DeclarationModifiers.File:
if (!IsFeatureEnabled(MessageID.IDS_FeatureFileTypes) && !ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false))
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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);
Copy link
Member

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??"

Copy link
Member

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.

Copy link
Contributor Author

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));
Copy link
Member

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?

Copy link
Contributor Author

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?
Copy link
Member

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?

Copy link
Contributor Author

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():

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.

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));
Copy link
Member

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.

Copy link
Contributor Author

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($$"""
Copy link
Member

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?

Copy link
Contributor Author

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.

}

[Fact]
public void MethodNamedRecord_02_CSharpNext()
Copy link
Member

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(??)

Copy link
Contributor Author

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.

@333fred
Copy link
Member

333fred commented Apr 28, 2022

Done review pass (commit 14)

@RikkiGibson RikkiGibson merged commit 4550ac3 into dotnet:features/file-types May 9, 2022
@RikkiGibson RikkiGibson deleted the ft-syntax branch May 9, 2022 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants