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

Initial support for collection literal parsing. #66417

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented Jan 13, 2023

Followup to #66412

Relates to test plan #66418

if (isPossibleAttributeOrModifier)
var isPossibleModifier = IsAdditionalLocalFunctionModifier(tk)
&& (tk is not (SyntaxKind.AsyncKeyword or SyntaxKind.ScopedKeyword)
|| ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: true));
Copy link
Member Author

Choose a reason for hiding this comment

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

we don't need to check for attributes here since attributes have already been parsed out before this.

@@ -527,7 +527,7 @@ private enum NamespaceParts
// incomplete members must be processed before we add any nodes to the body:
ReduceIncompleteMembers(ref pendingIncompleteMembers, ref openBraceOrSemicolon, ref body, ref initialBadNodes);

var attribute = this.ParseAttributeDeclaration();
var attribute = this.ParseAttributeDeclaration(inExpressionContext: parentKind == SyntaxKind.CompilationUnit);
Copy link
Member Author

Choose a reason for hiding this comment

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

[ at the top level could start an attribute, or could start a collection literal. However, [ inside a namespace always starts an attribute. Good test cases @jnm2 :)

Copy link
Member Author

Choose a reason for hiding this comment

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

while not legal, the parser should also accept:

[IAmAnAttribute(1, 2, 3)]
[on, a, collection, literal].DoWork();

@@ -2776,7 +2793,7 @@ private MemberDeclarationSyntax ParseMemberDeclarationCore(SyntaxKind parentKind

try
{
var attributes = this.ParseAttributeDeclarations();
var attributes = this.ParseAttributeDeclarations(inExpressionContext: false);
Copy link
Member Author

Choose a reason for hiding this comment

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

all the places taht pass in false should have tests taht show we still think tehy are an attribute, even if the user types . after them.


// PROTOTYPE: Def back compat concern here. What if the user has `x ? y?[0] : z` this would be legal,
// but will now change to `y?[0] : z` being a ternary. Have to decide if this is acceptable break.
return this.CurrentToken.Kind != SyntaxKind.ColonToken;
Copy link
Member Author

Choose a reason for hiding this comment

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

some interesting compat cases. we may have to be strict and require the user to write x ? ([1, 2, 3]) : ([4, 5, 6]). But that seems Really unfortunate. So i'm currently allowing the nicer form of x ? [1, 2, 3] : [4, 5, 6], but it may cause a compat break that LDM will have to approve.


if (this.CurrentToken.Kind != SyntaxKind.CloseBracketToken)
{
tryAgain:
Copy link
Member Author

Choose a reason for hiding this comment

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

this form is how we always do separated syntax list parsing. I personally dont' like repeating the entire full form. but it's what we do everywhere wlse, so this is consistent.

Copy link
Member

@cston cston Jan 17, 2023

Choose a reason for hiding this comment

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

Is this referring to the duplicated IsPossibleCollectionCreationExpression() || this.CurrentToken.Kind == SyntaxKind.CommaToken check, before and within the loop? It looks like there's also a duplicated SkipBadCollectionCreationExpressionTokens() check within the loop and following the loop.

If it's simpler to combine the duplicated checks in each case, within a while (true) { ... } for instance, I'd prefer it.

Copy link
Member Author

Choose a reason for hiding this comment

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

I would very much like to keep this form in sync with all the other ParseSeparatedList algorithms we have in the parser. if we do want to change them, doign them all at once would be my preference.

Copy link
Member

Choose a reason for hiding this comment

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

I also really hate this code, and fwiw we are already not consistent on separated syntax list parsing algorithms. ParseFunctionPointerType, for example, uses a much simpler form of this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Funny, I was just noting in #65256 (comment) that switch expression arm parsing does it its own way too :)

I would like for goto tryAgain; to goto the git history and for us to do something uniformly better here. But that seems like work better done in the base branch.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think a very suitable approach would be to have a basic ParseSeparatedList helper which takes in a few callbacks (And some boolean options, like "allowTrailingSeparator"). Using your approach of passing the parser instance along rikki, we could ensure this doesn't cause allocs.

But i was worried about potential perf regressions of doing that, so i wanted to leave to an alternate PR that could be tested in isolation.


var expression = this.ParseExpression();
if (dotDotToken != null)
return _syntaxFactory.SpreadElement(dotDotToken, expression);
Copy link
Member Author

Choose a reason for hiding this comment

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

interesting case woudl be .. without a following expression. .. ..goo. goo: (without a following expression). And things like [x ? 1 : 2 : value] (which i believe the spec will not allow... but i think will be allowed here).

Copy link
Member Author

Choose a reason for hiding this comment

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

also: [ : value] (Without a key).

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner January 15, 2023 20:00
@CyrusNajmabadi
Copy link
Member Author

@jcouv could you take a look?

@jcouv
Copy link
Member

jcouv commented Jan 31, 2023

namespace Microsoft.CodeAnalysis.CSharp.UnitTests

nit: consider file-scoped namespace


In reply to: 1410787932


In reply to: 1410787932


Refers to: src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs:9 in 3db96f8. [](commit_id = 3db96f8, deletion_comment = False)

@RikkiGibson RikkiGibson self-assigned this Jan 31, 2023
@CyrusNajmabadi
Copy link
Member Author

nit: consider file-scoped namespace

Done.

@@ -10103,6 +10150,7 @@ private static Precedence GetPrecedence(SyntaxKind op)
case SyntaxKind.ArrayCreationExpression:
case SyntaxKind.BaseExpression:
case SyntaxKind.CharacterLiteralExpression:
case SyntaxKind.CollectionCreationExpression:
Copy link
Member

@jcouv jcouv Jan 31, 2023

Choose a reason for hiding this comment

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

nit: please mention the precedence (and the resolution on any upcoming ambiguity decision) in the spec. Rex/Bill likely to ask question on that when they get to standardization. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

I can def add more. But it's already in the spec with where this construct is in the grammar. (can't hurt to have more info though of course) :)

{
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref openBracket, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleCollectionElement(),
static (p, closeKind) => p.CurrentToken.Kind == closeKind,
Copy link
Member

@jcouv jcouv Jan 31, 2023

Choose a reason for hiding this comment

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

The shadowing of closeKind is confusing. Let's use different identifiers.
I see that we did that previously in SkipBadInitializerListTokens :-/ Still wouldn't recommend it. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

this shadowing is extremely intentional (and done with all callback lambdas). It's precisely to indicate taht it's the same value, just passed through the lambda as an arg so that you always have hte same gy. Renaming is actually more confusing here as now there's the potential semantic confusion of "oh wait... is this closeKind2 somehow different from the containing closeKind1, and what does that mean?"

As with teh above, if we are to change this, i woulid prefer it be in a separate PR that does that for all the code like this (there are at least a dozen places to fixup if we do this).

if (SyntaxFacts.IsReservedKeyword(this.CurrentToken.Kind) && this.PeekToken(1).Kind == SyntaxKind.ColonToken)
{
var keyword = this.EatTokenWithPrejudice(SyntaxKind.IdentifierToken);
var identifier = ConvertToIdentifier(keyword).WithDiagnosticsGreen(keyword.GetDiagnostics());
Copy link
Member

@jcouv jcouv Jan 31, 2023

Choose a reason for hiding this comment

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

.WithDiagnosticsGreen(keyword.GetDiagnostics())

Is the WithDiagnosticsGreen portion necessary? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. ConvertToIdentifier doesn't preserve diagnostics.

Copy link
Member

Choose a reason for hiding this comment

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

Can there be diagnostics? Other places that use ConvertToIdentifier don't do this

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 think diagnostics can occur. But if they could, then the WithDiagnosticsGreen call should be inside ConvertToIdentifier (like we do in ConvertToKeyword), not the responsibility of the caller of ConvertToIdentifier.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can there be diagnostics?

Yes. EatTokenWithPrejudice adds one.

Other places that use ConvertToIdentifier don't do this

They likely aren't using EatTokenWithPrejudice :)

But if they could, then the WithDiagnosticsGreen call should be inside ConvertToIdentifier

I can do that and see what happens.

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

Done with review pass (iteration 65)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks! (iteration 66)
I'll summon the Gandalf in me to remind you that this shall be squashed 🧙

@CyrusNajmabadi CyrusNajmabadi enabled auto-merge (squash) January 31, 2023 22:42
@CyrusNajmabadi
Copy link
Member Author

image

:)

@jcouv
Copy link
Member

jcouv commented Jan 31, 2023

Thanks! :-)

@@ -15,6 +15,7 @@

namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
{
using Microsoft.CodeAnalysis.CSharp.Symbols;
Copy link
Member

Choose a reason for hiding this comment

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

Microsoft.CodeAnalysis.CSharp.Symbols

Is this used?

Copy link
Member Author

Choose a reason for hiding this comment

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

nope. sorry :( can remove in followup.

@CyrusNajmabadi CyrusNajmabadi merged commit 598cbe4 into dotnet:features/CollectionLiterals Jan 31, 2023
@CyrusNajmabadi CyrusNajmabadi deleted the collectionLiteralParsing branch February 1, 2023 00:29
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.

7 participants