-
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
Add support for generating the .. x ? [y] : []
pattern in a collection-expression
#69280
Add support for generating the .. x ? [y] : []
pattern in a collection-expression
#69280
Conversation
@@ -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" /> |
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 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) |
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 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) |
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.
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) |
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.
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 :)
.. x ? [y] : []
pattern in a collection-expression
1 | ||
}; | ||
if (b) | ||
c.Add(2); |
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.
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] : []|}]; |
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 error is going to be fixed by chuck soon.
void M() | ||
{ | ||
List<int> c = [1 | ||
]; |
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 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() |
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.
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; |
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.
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)) |
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 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); |
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 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; |
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.
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) |
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.
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) |
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.
similar gnarlyness for VB.
statements = commonForeach.Statement is BlockSyntax block | ||
? block.Statements | ||
: SpecializedCollections.SingletonEnumerable(commonForeach.Statement); | ||
return commonForeach.Expression; |
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.
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) |
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.
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) |
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.
similar gnarlyness for VB.
@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. |
NOte: after the next PR, things should be in a much nicer state. So only 2 more to go with the deep refactorings :) |
@akhera99 ptal :) |
@akhera99 ptal :) |
Part of: #69132
This change makes it so that if we see:
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.