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

Update "use collection initializer" to use a collection-expression if the user prefers that form. #69219

Merged
merged 44 commits into from
Jul 28, 2023
Merged
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
873f289
Extract helper code
CyrusNajmabadi Jul 24, 2023
1903cf4
Add tests
CyrusNajmabadi Jul 24, 2023
6882edd
crlf
CyrusNajmabadi Jul 24, 2023
c1bb50f
Compiling
CyrusNajmabadi Jul 24, 2023
e278ebf
in progress
CyrusNajmabadi Jul 24, 2023
60bf4c1
In progrss
CyrusNajmabadi Jul 24, 2023
e96f926
Building
CyrusNajmabadi Jul 24, 2023
424722a
Rename
CyrusNajmabadi Jul 24, 2023
95f1180
Rename
CyrusNajmabadi Jul 24, 2023
c9ad1ad
Rename
CyrusNajmabadi Jul 24, 2023
8da263d
Fixes
CyrusNajmabadi Jul 24, 2023
04899dc
Test fixes
CyrusNajmabadi Jul 25, 2023
0681c8d
In progrss
CyrusNajmabadi Jul 25, 2023
594a61c
Merge branch 'useCollectionExpression' into useCollectionExpression2
CyrusNajmabadi Jul 25, 2023
503a97f
Merge branch 'useCollectionExpression' into useCollectionExpression2
CyrusNajmabadi Jul 25, 2023
f738836
Fixes
CyrusNajmabadi Jul 25, 2023
ca91efd
Add suppression
CyrusNajmabadi Jul 25, 2023
57cdd67
updates
CyrusNajmabadi Jul 25, 2023
419565e
Revert
CyrusNajmabadi Jul 25, 2023
1e97397
Fixes
CyrusNajmabadi Jul 25, 2023
9120941
tweak
CyrusNajmabadi Jul 25, 2023
ea38ef0
In progress
CyrusNajmabadi Jul 25, 2023
361487a
In progress
CyrusNajmabadi Jul 25, 2023
3b43bb2
In progress
CyrusNajmabadi Jul 25, 2023
f4fd927
in progress
CyrusNajmabadi Jul 25, 2023
a13673f
Mostly working
CyrusNajmabadi Jul 26, 2023
7072918
Improvements
CyrusNajmabadi Jul 26, 2023
59db01a
Improvements
CyrusNajmabadi Jul 26, 2023
a452a0c
Merge remote-tracking branch 'upstream/main' into useCollectionExpres…
CyrusNajmabadi Jul 26, 2023
9932551
Add test
CyrusNajmabadi Jul 26, 2023
bb6702c
Add test
CyrusNajmabadi Jul 26, 2023
b93d46c
Share codE
CyrusNajmabadi Jul 26, 2023
8dcaadf
Update src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharp…
CyrusNajmabadi Jul 26, 2023
b0c9195
Self documenting
CyrusNajmabadi Jul 26, 2023
3a9c5bb
Simplify
CyrusNajmabadi Jul 26, 2023
9f8bcf2
Merge branch 'useCollectionExpression2' of https://github.com/CyrusNa…
CyrusNajmabadi Jul 26, 2023
560a123
REstore
CyrusNajmabadi Jul 26, 2023
3d629fb
REstore
CyrusNajmabadi Jul 26, 2023
acd85e5
Add comments
CyrusNajmabadi Jul 26, 2023
8f4bfd7
simplify
CyrusNajmabadi Jul 26, 2023
640dd0f
simplify
CyrusNajmabadi Jul 26, 2023
cbf06f5
simplify
CyrusNajmabadi Jul 26, 2023
1d75e4d
Apply suggestions from code review
CyrusNajmabadi Jul 26, 2023
d65ce70
Merge remote-tracking branch 'upstream/main' into useCollectionExpres…
CyrusNajmabadi Jul 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Building
CyrusNajmabadi committed Jul 24, 2023
commit e96f92674fb542988b8e72e50004b80cedaa9534
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ protected override StatementSyntax GetNewStatement(
StatementSyntax statement,
BaseObjectCreationExpressionSyntax objectCreation,
bool useCollectionExpression,
ImmutableArray<StatementSyntax> matches)
ImmutableArray<Match<StatementSyntax>> matches)
{
return statement.ReplaceNode(
objectCreation,
@@ -53,7 +53,7 @@ protected override StatementSyntax GetNewStatement(
private static ExpressionSyntax GetNewObjectCreation(
BaseObjectCreationExpressionSyntax objectCreation,
bool useCollectionExpression,
ImmutableArray<StatementSyntax> matches)
ImmutableArray<Match<StatementSyntax>> matches)
{
var expressions = CreateExpressions(objectCreation, matches);
return useCollectionExpression
@@ -63,7 +63,7 @@ private static ExpressionSyntax GetNewObjectCreation(

private static CollectionExpressionSyntax CreateCollectionExpression(
BaseObjectCreationExpressionSyntax objectCreation,
ImmutableArray<StatementSyntax> matches,
ImmutableArray<Match<StatementSyntax>> matches,
SeparatedSyntaxList<ExpressionSyntax> expressions)
{
using var _ = ArrayBuilder<SyntaxNodeOrToken>.GetInstance(out var nodesAndTokens);
@@ -78,7 +78,7 @@ private static CollectionExpressionSyntax CreateCollectionExpression(
else
{
var expression = (ExpressionSyntax)nodeOrToken.AsNode()!;
nodesAndTokens.Add(matches[expressionIndex] is ForEachStatementSyntax
nodesAndTokens.Add(matches[expressionIndex].UseSpread
? SpreadElement(expression)
: ExpressionElement(expression));
expressionIndex++;
@@ -90,15 +90,15 @@ private static CollectionExpressionSyntax CreateCollectionExpression(

private static SeparatedSyntaxList<ExpressionSyntax> CreateExpressions(
BaseObjectCreationExpressionSyntax objectCreation,
ImmutableArray<StatementSyntax> matches)
ImmutableArray<Match<StatementSyntax>> matches)
{
using var _ = ArrayBuilder<SyntaxNodeOrToken>.GetInstance(out var nodesAndTokens);

UseInitializerHelpers.AddExistingItems(objectCreation, nodesAndTokens);

for (var i = 0; i < matches.Length; i++)
{
var statement = matches[i];
var statement = matches[i].Statement;

var trivia = statement.GetLeadingTrivia();
var newTrivia = i == 0 ? trivia.WithoutLeadingBlankLines() : trivia;
Original file line number Diff line number Diff line change
@@ -17,6 +17,10 @@

namespace Microsoft.CodeAnalysis.UseCollectionInitializer
{
internal readonly record struct Match<TStatementSyntax>(
TStatementSyntax Statement,
bool UseSpread) where TStatementSyntax : SyntaxNode;

internal abstract partial class AbstractUseCollectionInitializerDiagnosticAnalyzer<
TSyntaxKind,
TExpressionSyntax,
@@ -128,7 +132,7 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol ien
if (matches.IsDefaultOrEmpty)
return;

var nodes = ImmutableArray.Create<SyntaxNode>(containingStatement).AddRange(matches);
var nodes = ImmutableArray.Create<SyntaxNode>(containingStatement).AddRange(matches.Select(static m => m.Statement));
var syntaxFacts = GetSyntaxFacts();
if (syntaxFacts.ContainsInterleavedDirective(nodes, cancellationToken))
return;
@@ -146,7 +150,7 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol ien

return;

(ImmutableArray<TStatementSyntax> matches, bool shouldUseCollectionExpression) GetMatches()
(ImmutableArray<Match<TStatementSyntax>> matches, bool shouldUseCollectionExpression) GetMatches()
Copy link
Member Author

Choose a reason for hiding this comment

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

new logic that first tries to see if we can convert to a collection expr. otherwise, falls back to original approach of trying a collection-initializer.

allows us to share the majorty of code and concepts.

{
// Analyze the surrounding statements. First, try a broader set of statements if the language supports
// collection expressions.
@@ -190,13 +194,13 @@ bool AreCollectionExpressionsSupported()

private void FadeOutCode(
SyntaxNodeAnalysisContext context,
ImmutableArray<TStatementSyntax> matches,
ImmutableArray<Match<TStatementSyntax>> matches,
ImmutableArray<Location> locations)
{
var syntaxTree = context.Node.SyntaxTree;
var syntaxFacts = GetSyntaxFacts();

foreach (var match in matches)
foreach (var (match, _) in matches)
{
if (match is TExpressionStatementSyntax)
{
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -25,7 +26,7 @@ internal sealed class UseCollectionInitializerAnalyzer<
TStatementSyntax,
TObjectCreationExpressionSyntax,
TVariableDeclaratorSyntax,
TStatementSyntax>
Match<TStatementSyntax>>
where TExpressionSyntax : SyntaxNode
where TStatementSyntax : SyntaxNode
where TObjectCreationExpressionSyntax : TExpressionSyntax
@@ -38,7 +39,7 @@ internal sealed class UseCollectionInitializerAnalyzer<
private static readonly ObjectPool<UseCollectionInitializerAnalyzer<TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, TExpressionStatementSyntax, TForeachStatementSyntax, TVariableDeclaratorSyntax>> s_pool
= SharedPools.Default<UseCollectionInitializerAnalyzer<TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, TExpressionStatementSyntax, TForeachStatementSyntax, TVariableDeclaratorSyntax>>();

public static ImmutableArray<TStatementSyntax> Analyze(
public static ImmutableArray<Match<TStatementSyntax>> Analyze(
SemanticModel semanticModel,
ISyntaxFacts syntaxFacts,
TObjectCreationExpressionSyntax objectCreationExpression,
@@ -58,7 +59,7 @@ public static ImmutableArray<TStatementSyntax> Analyze(
}
}

protected override void AddMatches(ArrayBuilder<TStatementSyntax> matches)
protected override void AddMatches(ArrayBuilder<Match<TStatementSyntax>> matches)
{
// If containing statement is inside a block (e.g. method), than we need to iterate through its child statements.
// If containing statement is in top-level code, than we need to iterate through child statements of containing compilation unit.
@@ -107,20 +108,42 @@ protected override void AddMatches(ArrayBuilder<TStatementSyntax> matches)

if (extractedChild is TExpressionStatementSyntax expressionStatement)
{
TExpressionSyntax? instance = null;
if (!seenIndexAssignment && TryAnalyzeAddInvocation(expressionStatement, requiredArgumentName: null, out instance))
seenInvocation = true;

if (!seenInvocation && !_analyzeForCollectionExpression && TryAnalyzeIndexAssignment(expressionStatement, out instance))
seenIndexAssignment = true;

if (instance == null)
return;
if (!seenIndexAssignment)
{
// Look for a call to Add or AddRange
if (TryAnalyzeInvocation(
expressionStatement,
addName: WellKnownMemberNames.CollectionInitializerAddMethodName,
requiredArgumentName: null,
out var instance) &&
ValuePatternMatches(instance))
{
seenInvocation = true;
matches.Add(new Match<TStatementSyntax>(expressionStatement, UseSpread: false));
}
else if (
_analyzeForCollectionExpression &&
TryAnalyzeInvocation(
expressionStatement,
addName: nameof(List<int>.AddRange),
requiredArgumentName: null,
out instance))
{
seenInvocation = true;
matches.Add(new Match<TStatementSyntax>(expressionStatement, UseSpread: true));
}
}

if (!ValuePatternMatches(instance))
return;
if (!seenInvocation && !_analyzeForCollectionExpression)
{
if (TryAnalyzeIndexAssignment(expressionStatement, out var instance))
{
seenIndexAssignment = true;
matches.Add(new Match<TStatementSyntax>(expressionStatement, UseSpread: false));
}
}

matches.Add(expressionStatement);
return;
}
else if (extractedChild is TForeachStatementSyntax foreachStatement)
{
@@ -134,14 +157,13 @@ protected override void AddMatches(ArrayBuilder<TStatementSyntax> matches)
return;

if (foreachStatements.ToImmutableArray() is not [TExpressionStatementSyntax childExpressionStatement] ||
!TryAnalyzeAddInvocation(childExpressionStatement, identifier.Text, out var instance) ||
instance is null ||
!TryAnalyzeInvocation(childExpressionStatement, addName: WellKnownMemberNames.CollectionInitializerAddMethodName, identifier.Text, out var instance) ||
!ValuePatternMatches(instance))
{
return;
}

matches.Add(foreachStatement);
matches.Add(new Match<TStatementSyntax>(foreachStatement, UseSpread: true));
}
else
{
@@ -211,8 +233,9 @@ private bool TryAnalyzeIndexAssignment(
return instance != null;
}

private bool TryAnalyzeAddInvocation(
private bool TryAnalyzeInvocation(
TExpressionStatementSyntax statement,
string addName,
string? requiredArgumentName,
[NotNullWhen(true)] out TExpressionSyntax? instance)
{
@@ -271,7 +294,7 @@ private bool TryAnalyzeAddInvocation(
_syntaxFacts.GetPartsOfMemberAccessExpression(memberAccess, out var localInstance, out var memberName);
_syntaxFacts.GetNameAndArityOfSimpleName(memberName, out var name, out var arity);

if (arity != 0 || !Equals(name, WellKnownMemberNames.CollectionInitializerAddMethodName))
if (arity != 0 || !Equals(name, addName))
return false;

instance = localInstance as TExpressionSyntax;
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@ public static ImmutableArray<Match<TExpressionSyntax, TStatementSyntax, TMemberA
CancellationToken cancellationToken)
{
var analyzer = s_pool.Allocate();
analyzer.Initialize(semanticModel, syntaxFacts, objectCreationExpression, areCollectionExpressionsSupported: false, cancellationToken);
analyzer.Initialize(semanticModel, syntaxFacts, objectCreationExpression, analyzeForCollectionExpression: false, cancellationToken);
try
{
return analyzer.AnalyzeWorker();
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ public override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(IDEDiagnosticIds.UseCollectionInitializerDiagnosticId);

protected abstract TStatementSyntax GetNewStatement(
Copy link
Member Author

Choose a reason for hiding this comment

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

moved thsi abstract method from below to here.

TStatementSyntax statement, TObjectCreationExpressionSyntax objectCreation, bool useCollectionExpression, ImmutableArray<TStatementSyntax> matches);
TStatementSyntax statement, TObjectCreationExpressionSyntax objectCreation, bool useCollectionExpression, ImmutableArray<Match<TStatementSyntax>> matches);

protected override bool IncludeDiagnosticDuringFixAll(Diagnostic diagnostic)
=> !diagnostic.Descriptor.ImmutableCustomTags().Contains(WellKnownDiagnosticTags.Unnecessary);
@@ -93,20 +93,20 @@ protected override async Task FixAllAsync(
var matches = UseCollectionInitializerAnalyzer<TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, TExpressionStatementSyntax, TForeachStatementSyntax, TVariableDeclaratorSyntax>.Analyze(
semanticModel, syntaxFacts, objectCreation, useCollectionExpression, cancellationToken);

if (matches == null || matches.Value.Length == 0)
if (matches.IsDefaultOrEmpty)
continue;

var statement = objectCreation.FirstAncestorOrSelf<TStatementSyntax>();
Contract.ThrowIfNull(statement);

var newStatement = GetNewStatement(statement, objectCreation, useCollectionExpression, matches.Value)
var newStatement = GetNewStatement(statement, objectCreation, useCollectionExpression, matches)
.WithAdditionalAnnotations(Formatter.Annotation);

var subEditor = new SyntaxEditor(currentRoot, document.Project.Solution.Services);

subEditor.ReplaceNode(statement, newStatement);
foreach (var match in matches)
subEditor.RemoveNode(match, SyntaxRemoveOptions.KeepUnbalancedDirectives);
subEditor.RemoveNode(match.Statement, SyntaxRemoveOptions.KeepUnbalancedDirectives);

document = document.WithSyntaxRoot(subEditor.GetChangedRoot());
semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
Original file line number Diff line number Diff line change
@@ -86,15 +86,13 @@ protected override async Task FixAllAsync(
var matches = UseNamedMemberInitializerAnalyzer<TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TAssignmentStatementSyntax, TVariableDeclaratorSyntax>.Analyze(
semanticModel, syntaxFacts, objectCreation, cancellationToken);

if (matches == null || matches.Value.Length == 0)
{
if (matches.IsDefaultOrEmpty)
continue;
}

var statement = objectCreation.FirstAncestorOrSelf<TStatementSyntax>();
Contract.ThrowIfNull(statement);

var newStatement = GetNewStatement(statement, objectCreation, matches.Value)
var newStatement = GetNewStatement(statement, objectCreation, matches)
.WithAdditionalAnnotations(Formatter.Annotation);

var subEditor = new SyntaxEditor(currentRoot, document.Project.Solution.Services);
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseCollectionInitializer
statement As StatementSyntax,
objectCreation As ObjectCreationExpressionSyntax,
useCollectionExpression As Boolean,
matches As ImmutableArray(Of StatementSyntax)) As StatementSyntax
matches As ImmutableArray(Of Match(Of StatementSyntax))) As StatementSyntax
Contract.ThrowIfTrue(useCollectionExpression, "VB does not support collection expressions")
Dim newStatement = statement.ReplaceNode(
objectCreation,
@@ -45,7 +45,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseCollectionInitializer
totalTrivia.Add(SyntaxFactory.ElasticMarker)

For Each match In matches
For Each trivia In match.GetLeadingTrivia()
For Each trivia In match.Statement.GetLeadingTrivia()
If trivia.Kind = SyntaxKind.CommentTrivia Then
totalTrivia.Add(trivia)
totalTrivia.Add(SyntaxFactory.ElasticMarker)
@@ -58,7 +58,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseCollectionInitializer

Private Shared Function GetNewObjectCreation(
objectCreation As ObjectCreationExpressionSyntax,
matches As ImmutableArray(Of StatementSyntax)) As ObjectCreationExpressionSyntax
matches As ImmutableArray(Of Match(Of StatementSyntax))) As ObjectCreationExpressionSyntax

Return UseInitializerHelpers.GetNewObjectCreation(
objectCreation,
@@ -68,13 +68,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseCollectionInitializer

Private Shared Function CreateCollectionInitializer(
objectCreation As ObjectCreationExpressionSyntax,
matches As ImmutableArray(Of StatementSyntax)) As CollectionInitializerSyntax
matches As ImmutableArray(Of Match(Of StatementSyntax))) As CollectionInitializerSyntax
Dim nodesAndTokens = ArrayBuilder(Of SyntaxNodeOrToken).GetInstance()

AddExistingItems(objectCreation, nodesAndTokens)

For i = 0 To matches.Length - 1
Dim expressionStatement = DirectCast(matches(i), ExpressionStatementSyntax)
Dim expressionStatement = DirectCast(matches(i).Statement, ExpressionStatementSyntax)

Dim newExpression As ExpressionSyntax
Dim invocationExpression = DirectCast(expressionStatement.Expression, InvocationExpressionSyntax)