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

Add support for generating the .. x ? [y] : [] pattern in a collection-expression #69280

Merged
merged 12 commits into from
Aug 2, 2023

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented Jul 28, 2023

Part of: #69132

This change makes it so that if we see:

List<int> list = new List<int>();
list.Add(1);
if (x)
    list.Add(2);

We now generate: [1, .. x ? [2] : []].

This is a form which we view as idiomatic for inserting elements conditionally, and will be optimized by the compiler.

As i've been working in this space, i've been pulling out cleanups that i think help the code. This PR is a collection of them.

THis also adds a ton more tests validating behavior.

Note: next PR works a lot on improving the formatting of the produced collection-expressions.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels Jul 28, 2023
@CyrusNajmabadi CyrusNajmabadi changed the title Use collection expression4 WIP: not ready for review. Jul 28, 2023
@CyrusNajmabadi CyrusNajmabadi changed the title WIP: not ready for review. Cleanup/refactor code in 'use collection initializer/expression'. Jul 31, 2023
@@ -83,6 +83,7 @@
<Compile Include="$(MSBuildThisFileDirectory)UseCollectionExpression\CSharpUseCollectionExpressionForEmptyDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseCollectionExpression\CSharpUseCollectionExpressionForArrayDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseCollectionExpression\UseCollectionExpressionHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseCollectionInitializer\CSharpUseCollectionInitializerAnalyzer.cs" />
Copy link
Member Author

Choose a reason for hiding this comment

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

there used to be a single UseCollectionInitializerAnalyzer type that was used for both VB and C#. HOwever, it was getting crushed under the weight of having to have special logic (esp. logic that was really only for C#'s collection expressions).

We now use inheritance here, which cleans up a few things (especially around type arguments).

ForEachStatementSyntax statement,
out SyntaxToken identifier,
out ExpressionSyntax expression,
out IEnumerable<StatementSyntax> statements)
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 is a better way than how i was previously doing things iwth ISYntaxFacts. The problem is that there are too many foreach-forms across C# and VB (and in C# itself). So syntaxfacts didn't really know how to deal with things.

Here though we know exactly what form we have, and we can produce a good output result for that for C# no matter what.

IfStatementSyntax statement,
out ExpressionSyntax condition,
out IEnumerable<StatementSyntax> whenTrueStatements,
out IEnumerable<StatementSyntax>? whenFalseStatements)
Copy link
Member Author

Choose a reason for hiding this comment

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

same issue. C# has if/else (Where else is optional). VB has if/elif/elif/else. These are really different forms, and trying to shoehorn into ISyntaxFacts was not the right way to do things.

if (!sourceText.AreOnSameLine(expression.GetFirstToken(), expression.GetLastToken()))
return true;
}

var totalLength = "{}".Length;
foreach (var match in matches)
Copy link
Member Author

Choose a reason for hiding this comment

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

note: while this is getting cleaned up here, it is also getting refactored heavily in the next PR where i redo how we do formatting here entirely. so don't have to examien this too closely :)

@CyrusNajmabadi CyrusNajmabadi changed the title Cleanup/refactor code in 'use collection initializer/expression'. Add support for generating the .. x ? [y] : [] pattern in a collection-expression Jul 31, 2023
1
};
if (b)
c.Add(2);
Copy link
Member Author

Choose a reason for hiding this comment

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

these tests validate that for the collection INITIALIZER space we don't do anything special. The new form is for the collection EXPRESSION case.

{
void M(bool b)
{
List<int> c = [1, .. {|CS0173:b ? [2] : []|}];
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 error is going to be fixed by chuck soon.

void M()
{
List<int> c = [1
];
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 formatting is terrible. next PR fixes this up. but it's a lot of manual trivia handling, so i'm pulling it out of this PR to keep things sane.

TForeachStatementSyntax,
TIfStatementSyntax,
TVariableDeclaratorSyntax,
TAnalyzer>, new()
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. this part is brain melting. but it means the consumption side of this type (which references the derived types) now gets much simpler. WIll point that out when we get tehre.

// if we have an object creation, and it *already* has an initializer in it (like `new T { { x, y } }`)
// this can't legally become a collection expression.
if (_analyzeForCollectionExpression && this.IsComplexElementInitializer(firstInit))
return 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.

needed to make this bool returning as we cannot use the "empty result means failure" approach for 'collection expressions'. For collection expressions an empty result is fine and means "convert to []".

@@ -107,86 +149,157 @@ protected override void AddMatches(ArrayBuilder<Match<TStatementSyntax>> matches
continue;
}

if (extractedChild is TExpressionStatementSyntax expressionStatement)
if (extractedChild is TExpressionStatementSyntax expressionStatement &&
TryProcessExpressionStatement(expressionStatement))
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 large if/else was extracted into separate nested helper local functions.

One for x.Add(y), one for foreach (var y in z) x.Add(y) and one for if (z) x.Add(y)

while (originalObjectCreationNodes.Count > 0)
{
var (originalObjectCreation, useCollectionExpression) = originalObjectCreationNodes.Pop();
var objectCreation = currentRoot.GetCurrentNodes(originalObjectCreation).Single();

var matches = UseCollectionInitializerAnalyzer<TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, TExpressionStatementSyntax, TForeachStatementSyntax, TVariableDeclaratorSyntax>.Analyze(
semanticModel, syntaxFacts, objectCreation, useCollectionExpression, cancellationToken);
Copy link
Member Author

Choose a reason for hiding this comment

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

no need for consumption sides of the helper type ot have to have so much noise. this was getting particularly awful as i needded to add 'IfStatementSyntax' here (and all the other places).

statements = commonForeach.Statement is BlockSyntax block
? block.Statements
: SpecializedCollections.SingletonEnumerable(commonForeach.Statement);
return commonForeach.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.

example of how gnarly this was trying to be a syntaxfact (which i added in the previous PR). was happy to remove this here.

Public Sub GetPartsOfForeachStatement(statement As SyntaxNode, ByRef identifier As SyntaxToken, ByRef expression As SyntaxNode, ByRef statements As IEnumerable(Of SyntaxNode)) Implements ISyntaxFacts.GetPartsOfForeachStatement
Dim foreachStatement = DirectCast(statement, ForEachStatementSyntax)
Dim declarator = TryCast(foreachStatement.ControlVariable, VariableDeclaratorSyntax)
identifier = If(declarator Is Nothing, Nothing, declarator.Names(0).Identifier)
Copy link
Member Author

Choose a reason for hiding this comment

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

similar gnarlyness for VB.

Public Sub GetPartsOfForeachStatement(statement As SyntaxNode, ByRef identifier As SyntaxToken, ByRef expression As SyntaxNode, ByRef statements As IEnumerable(Of SyntaxNode)) Implements ISyntaxFacts.GetPartsOfForeachStatement
Dim foreachStatement = DirectCast(statement, ForEachStatementSyntax)
Dim declarator = TryCast(foreachStatement.ControlVariable, VariableDeclaratorSyntax)
identifier = If(declarator Is Nothing, Nothing, declarator.Names(0).Identifier)
Copy link
Member Author

Choose a reason for hiding this comment

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

similar gnarlyness for VB.

statements = commonForeach.Statement is BlockSyntax block
? block.Statements
: SpecializedCollections.SingletonEnumerable(commonForeach.Statement);
return commonForeach.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.

example of how gnarly this was trying to be a syntaxfact (which i added in the previous PR). was happy to remove this here.

Public Sub GetPartsOfForeachStatement(statement As SyntaxNode, ByRef identifier As SyntaxToken, ByRef expression As SyntaxNode, ByRef statements As IEnumerable(Of SyntaxNode)) Implements ISyntaxFacts.GetPartsOfForeachStatement
Dim foreachStatement = DirectCast(statement, ForEachStatementSyntax)
Dim declarator = TryCast(foreachStatement.ControlVariable, VariableDeclaratorSyntax)
identifier = If(declarator Is Nothing, Nothing, declarator.Names(0).Identifier)
Copy link
Member Author

Choose a reason for hiding this comment

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

similar gnarlyness for VB.

Public Sub GetPartsOfForeachStatement(statement As SyntaxNode, ByRef identifier As SyntaxToken, ByRef expression As SyntaxNode, ByRef statements As IEnumerable(Of SyntaxNode)) Implements ISyntaxFacts.GetPartsOfForeachStatement
Dim foreachStatement = DirectCast(statement, ForEachStatementSyntax)
Dim declarator = TryCast(foreachStatement.ControlVariable, VariableDeclaratorSyntax)
identifier = If(declarator Is Nothing, Nothing, declarator.Names(0).Identifier)
Copy link
Member Author

Choose a reason for hiding this comment

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

similar gnarlyness for VB.

@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review July 31, 2023 23:36
@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner July 31, 2023 23:36
@CyrusNajmabadi
Copy link
Member Author

@genlu @Cosifne @akhera99 this is ready for review. I'm sorry for al the churn, but i'm trying to have independent PRs that solve core issues, without something absolutely ginormous that rolls it all in.

However, it does mean that as new things are added, there's often reworking of the core logic to try to keep it clean/rational.

@CyrusNajmabadi
Copy link
Member Author

NOte: after the next PR, things should be in a much nicer state. So only 2 more to go with the deep refactorings :)

@CyrusNajmabadi
Copy link
Member Author

@akhera99 ptal :)

@CyrusNajmabadi
Copy link
Member Author

@akhera99 ptal :)

@CyrusNajmabadi CyrusNajmabadi merged commit 21c31f9 into dotnet:main Aug 2, 2023
24 checks passed
@CyrusNajmabadi CyrusNajmabadi deleted the useCollectionExpression4 branch August 2, 2023 22:34
@ghost ghost added this to the Next milestone Aug 2, 2023
@dibarbet dibarbet modified the milestones: Next, 17.8 P2 Aug 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants