-
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
Support converting arrays, whose elements are assigned afterwards, to a collection expression. #69415
Support converting arrays, whose elements are assigned afterwards, to a collection expression. #69415
Changes from all commits
1c6f65e
93ebb0d
5b4a86c
a27aa68
1328a09
7c3590d
e83ff84
0986e84
a4527fe
6b2758c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,18 +3,14 @@ | |
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Threading; | ||
using Microsoft.CodeAnalysis.CodeStyle; | ||
using Microsoft.CodeAnalysis.CSharp.Analyzers.UseCollectionExpression; | ||
using Microsoft.CodeAnalysis.CSharp.Extensions; | ||
using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Options; | ||
using Microsoft.CodeAnalysis.PooledObjects; | ||
using Microsoft.CodeAnalysis.Text; | ||
using Microsoft.CodeAnalysis.UseCollectionExpression; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.UseCollectionExpression; | ||
|
||
|
@@ -158,94 +154,11 @@ public static ImmutableArray<CollectionExpressionMatch> TryGetMatches( | |
StackAllocArrayCreationExpressionSyntax expression, | ||
CancellationToken cancellationToken) | ||
{ | ||
// has to either be `stackalloc X[]` or `stackalloc X[const]`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved to common helper code, since we'll use it for stackalloc and new array creations. |
||
if (expression.Type is not ArrayTypeSyntax { RankSpecifiers: [{ Sizes: [var size] } rankSpecifier] } arrayType) | ||
return default; | ||
|
||
using var _ = ArrayBuilder<CollectionExpressionMatch>.GetInstance(out var matches); | ||
|
||
if (size is OmittedArraySizeExpressionSyntax) | ||
{ | ||
// `stackalloc int[]` on its own is illegal. Has to either have a size, or an initializer. | ||
if (expression.Initializer is null) | ||
return default; | ||
} | ||
else | ||
{ | ||
// if `stackalloc X[val]`, then it `val` has to be a constant value. | ||
if (semanticModel.GetConstantValue(size, cancellationToken).Value is not int sizeValue) | ||
return default; | ||
|
||
if (expression.Initializer != null) | ||
{ | ||
// if there is an initializer, then it has to have the right number of elements. | ||
if (sizeValue != expression.Initializer.Expressions.Count) | ||
return default; | ||
} | ||
else | ||
{ | ||
// if there is no initializer, we have to be followed by direct statements that initialize the right | ||
// number of elements. | ||
|
||
// This needs to be local variable like `ReadOnlySpan<T> x = stackalloc ... | ||
if (expression.WalkUpParentheses().Parent is not EqualsValueClauseSyntax | ||
{ | ||
Parent: VariableDeclaratorSyntax | ||
{ | ||
Identifier.ValueText: var variableName, | ||
Parent.Parent: LocalDeclarationStatementSyntax localDeclarationStatement | ||
}, | ||
}) | ||
{ | ||
return default; | ||
} | ||
|
||
var currentStatement = localDeclarationStatement.GetNextStatement(); | ||
for (var currentIndex = 0; currentIndex < sizeValue; currentIndex++) | ||
{ | ||
// Each following statement needs to of the form: | ||
// | ||
// x[...] = | ||
if (currentStatement is not ExpressionStatementSyntax | ||
{ | ||
Expression: AssignmentExpressionSyntax | ||
{ | ||
Left: ElementAccessExpressionSyntax | ||
{ | ||
Expression: IdentifierNameSyntax { Identifier.ValueText: var elementName }, | ||
ArgumentList.Arguments: [var elementArgument], | ||
} elementAccess, | ||
} | ||
} expressionStatement) | ||
{ | ||
return default; | ||
} | ||
|
||
// Ensure we're indexing into the variable created. | ||
if (variableName != elementName) | ||
return default; | ||
|
||
// The indexing value has to equal the corresponding location in the result. | ||
if (semanticModel.GetConstantValue(elementArgument.Expression, cancellationToken).Value is not int indexValue || | ||
indexValue != currentIndex) | ||
{ | ||
return default; | ||
} | ||
|
||
// this looks like a good statement, add to the right size of the assignment to track as that's what | ||
// we'll want to put in the final collection expression. | ||
matches.Add(new(expressionStatement, UseSpread: false)); | ||
currentStatement = currentStatement.GetNextStatement(); | ||
} | ||
} | ||
} | ||
|
||
if (!UseCollectionExpressionHelpers.CanReplaceWithCollectionExpression( | ||
semanticModel, expression, skipVerificationForReplacedNode: true, cancellationToken)) | ||
{ | ||
return default; | ||
} | ||
|
||
return matches.ToImmutable(); | ||
return UseCollectionExpressionHelpers.TryGetMatches( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. calls into the shared helper now. |
||
semanticModel, | ||
expression, | ||
static e => e.Type, | ||
static e => e.Initializer, | ||
cancellationToken); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.UseCollectionInitializer; | ||
|
||
namespace Microsoft.CodeAnalysis.UseCollectionExpression; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a few types were in the wrong namespace. fixed up with downstream consequences. |
||
namespace Microsoft.CodeAnalysis.CSharp.UseCollectionExpression; | ||
|
||
/// <inheritdoc cref="Match{TStatementSyntax}"/> | ||
internal readonly record struct CollectionExpressionMatch( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,16 +3,18 @@ | |
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Threading; | ||
using Microsoft.CodeAnalysis.CSharp.Extensions; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.CSharp.Utilities; | ||
using Microsoft.CodeAnalysis.PooledObjects; | ||
using Microsoft.CodeAnalysis.Shared.Extensions; | ||
using Microsoft.CodeAnalysis.Text; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.Analyzers.UseCollectionExpression; | ||
namespace Microsoft.CodeAnalysis.CSharp.UseCollectionExpression; | ||
|
||
using static SyntaxFactory; | ||
|
||
|
@@ -293,4 +295,106 @@ private static bool ShouldReplaceExistingExpressionEntirely( | |
// For this we want to remove the 'new' portion, but keep the collection on its own line. | ||
return false; | ||
} | ||
|
||
public static ImmutableArray<CollectionExpressionMatch> TryGetMatches<TArrayCreationExpressionSyntax>( | ||
SemanticModel semanticModel, | ||
TArrayCreationExpressionSyntax expression, | ||
Func<TArrayCreationExpressionSyntax, TypeSyntax> getType, | ||
Func<TArrayCreationExpressionSyntax, InitializerExpressionSyntax?> getInitializer, | ||
CancellationToken cancellationToken) | ||
where TArrayCreationExpressionSyntax : ExpressionSyntax | ||
{ | ||
Contract.ThrowIfFalse(expression is ArrayCreationExpressionSyntax or StackAllocArrayCreationExpressionSyntax); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just a move. |
||
|
||
// has to either be `stackalloc X[]` or `stackalloc X[const]`. | ||
if (getType(expression) is not ArrayTypeSyntax { RankSpecifiers: [{ Sizes: [var size] }, ..] }) | ||
return default; | ||
|
||
using var _ = ArrayBuilder<CollectionExpressionMatch>.GetInstance(out var matches); | ||
|
||
var initializer = getInitializer(expression); | ||
if (size is OmittedArraySizeExpressionSyntax) | ||
{ | ||
// `stackalloc int[]` on its own is illegal. Has to either have a size, or an initializer. | ||
if (initializer is null) | ||
return default; | ||
} | ||
else | ||
{ | ||
// if `stackalloc X[val]`, then it `val` has to be a constant value. | ||
if (semanticModel.GetConstantValue(size, cancellationToken).Value is not int sizeValue) | ||
return default; | ||
|
||
if (initializer != null) | ||
{ | ||
// if there is an initializer, then it has to have the right number of elements. | ||
if (sizeValue != initializer.Expressions.Count) | ||
return default; | ||
} | ||
else | ||
{ | ||
// if there is no initializer, we have to be followed by direct statements that initialize the right | ||
// number of elements. | ||
|
||
// This needs to be local variable like `ReadOnlySpan<T> x = stackalloc ... | ||
if (expression.WalkUpParentheses().Parent is not EqualsValueClauseSyntax | ||
{ | ||
Parent: VariableDeclaratorSyntax | ||
{ | ||
Identifier.ValueText: var variableName, | ||
Parent.Parent: LocalDeclarationStatementSyntax localDeclarationStatement | ||
}, | ||
}) | ||
{ | ||
return default; | ||
} | ||
|
||
var currentStatement = localDeclarationStatement.GetNextStatement(); | ||
for (var currentIndex = 0; currentIndex < sizeValue; currentIndex++) | ||
{ | ||
// Each following statement needs to of the form: | ||
// | ||
// x[...] = | ||
if (currentStatement is not ExpressionStatementSyntax | ||
{ | ||
Expression: AssignmentExpressionSyntax | ||
{ | ||
Left: ElementAccessExpressionSyntax | ||
{ | ||
Expression: IdentifierNameSyntax { Identifier.ValueText: var elementName }, | ||
ArgumentList.Arguments: [var elementArgument], | ||
} elementAccess, | ||
} | ||
} expressionStatement) | ||
{ | ||
return default; | ||
} | ||
|
||
// Ensure we're indexing into the variable created. | ||
if (variableName != elementName) | ||
return default; | ||
|
||
// The indexing value has to equal the corresponding location in the result. | ||
if (semanticModel.GetConstantValue(elementArgument.Expression, cancellationToken).Value is not int indexValue || | ||
indexValue != currentIndex) | ||
{ | ||
return default; | ||
} | ||
|
||
// this looks like a good statement, add to the right size of the assignment to track as that's what | ||
// we'll want to put in the final collection expression. | ||
matches.Add(new(expressionStatement, UseSpread: false)); | ||
currentStatement = currentStatement.GetNextStatement(); | ||
} | ||
} | ||
} | ||
|
||
if (!CanReplaceWithCollectionExpression( | ||
semanticModel, expression, skipVerificationForReplacedNode: true, cancellationToken)) | ||
{ | ||
return default; | ||
} | ||
|
||
return matches.ToImmutable(); | ||
} | ||
} |
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.
calls into new helper that shares logic with stackalloc creation analysis.