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

Fix parsing of 'file[]' and 'required[]' in old language versions #66769

Merged
merged 10 commits into from
Feb 16, 2023

Conversation

RikkiGibson
Copy link
Contributor

Closes #66646


if (!parsingStatementNotDeclaration)
{
var currentTokenKind = this.CurrentToken.Kind;
if (IsPossibleStartOfTypeDeclaration(currentTokenKind) ||
if ((IsPossibleStartOfTypeDeclaration(currentTokenKind) && currentTokenKind != SyntaxKind.OpenBracketToken) ||
Copy link
Member

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

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 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.

Copy link
Member

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).

Copy link
Contributor Author

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)
Copy link
Contributor Author

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.

Copy link
Contributor Author

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]");
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 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.

Copy link
Contributor Author

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.

@RikkiGibson
Copy link
Contributor Author

As part of this issue I found (I think) that we regressed scenarios like new required(); as a top level statement (which I think worked in C# 9 but not 11).

@@ -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))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

2 things to note:

  1. this helper is only called for ParseMemberDeclarationOrStatement. i.e., top-level declarations that could either be a member or a statement.
  2. 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".

@RikkiGibson RikkiGibson marked this pull request as ready for review February 11, 2023 02:38
@RikkiGibson RikkiGibson requested a review from a team as a code owner February 11, 2023 02:38
@@ -1870,6 +1870,373 @@ public void RequiredModifierIncompleteMember_05()
EOF();
}

[Fact]
public void TypeNamedRequired_CSharp10()
Copy link
Member

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

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'll add back 'class required' here, perhaps it's helpful to be clear about how it parses in each language version.

@RikkiGibson RikkiGibson requested a review from a team February 14, 2023 01:00
@RikkiGibson
Copy link
Contributor Author

@dotnet/roslyn-compiler for a second review

@RikkiGibson RikkiGibson changed the base branch from release/dev17.5 to main February 15, 2023 16:27
@RikkiGibson
Copy link
Contributor Author

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

Choose a reason for hiding this comment

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

TopLevel_NewContextualKeywordArray

Consider adding [InlineData("async")] or a separate test if async is parsed differently than the other cases here.


public unsafe class C
{
public file _file;
Copy link
Member

@cston cston Feb 15, 2023

Choose a reason for hiding this comment

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

public

Consider testing the same class declaration but without the public modifier on the fields, with -langversion:10 and -langversion:11.

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 feel this is adequately covered by TypeNamedRequired_CSharp10 and TypeNamedRequired_CSharp11

@RikkiGibson RikkiGibson merged commit 54b7df9 into dotnet:main Feb 16, 2023
@ghost ghost added this to the Next milestone Feb 16, 2023
@RikkiGibson RikkiGibson deleted the file-parse-compat branch February 16, 2023 07:13
@RikkiGibson RikkiGibson modified the milestones: Next, 17.6 P2 Mar 2, 2023
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.

Visual Studio 17.4 Incorrect Enforcing of New Breaking Change in Earlier C# Versions
4 participants