Skip to content

Commit fc117a2

Browse files
authored
[Performance] Simplify UseSpanBasedStringConcat for better performance (#6870)
* [Performance] Simplify UseSpanBasedStringConcat for better performance * Remove unused using
1 parent 8011355 commit fc117a2

File tree

3 files changed

+19
-72
lines changed

3 files changed

+19
-72
lines changed

src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
22

3-
using System.Diagnostics.CodeAnalysis;
43
using Microsoft.CodeAnalysis;
54
using Microsoft.CodeAnalysis.Diagnostics;
65
using Microsoft.CodeAnalysis.Operations;
@@ -11,20 +10,10 @@ namespace Microsoft.NetCore.CSharp.Analyzers.Runtime
1110
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1211
public sealed class CSharpUseSpanBasedStringConcat : UseSpanBasedStringConcat
1312
{
14-
private protected override bool TryGetTopMostConcatOperation(IBinaryOperation binaryOperation, [NotNullWhen(true)] out IBinaryOperation? rootBinaryOperation)
13+
private protected override bool IsTopMostConcatOperation(IBinaryOperation binaryOperation)
1514
{
16-
if (!IsConcatOperation(binaryOperation))
17-
{
18-
rootBinaryOperation = default;
19-
return false;
20-
}
21-
22-
var current = binaryOperation;
23-
while (current.Parent is IBinaryOperation parentBinaryOperation && IsConcatOperation(parentBinaryOperation))
24-
current = parentBinaryOperation;
25-
26-
rootBinaryOperation = current;
27-
return true;
15+
return IsConcatOperation(binaryOperation) &&
16+
(binaryOperation.Parent is not IBinaryOperation parentBinary || !IsConcatOperation(parentBinary));
2817

2918
static bool IsConcatOperation(IBinaryOperation operation)
3019
{

src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs

Lines changed: 13 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Linq;
77
using Analyzer.Utilities;
88
using Analyzer.Utilities.Extensions;
9-
using Analyzer.Utilities.PooledObjects;
109
using Microsoft.CodeAnalysis;
1110
using Microsoft.CodeAnalysis.Diagnostics;
1211
using Microsoft.CodeAnalysis.Operations;
@@ -32,11 +31,9 @@ public abstract class UseSpanBasedStringConcat : DiagnosticAnalyzer
3231
isDataflowRule: false);
3332

3433
/// <summary>
35-
/// If the specified binary operation is a string concatenation operation, we try to walk up to the top-most
36-
/// string-concatenation operation that it is part of. If it is not a string-concatenation operation, we simply
37-
/// return false.
34+
/// Returns true if the specified binary operation is a top-most string concatenation operation
3835
/// </summary>
39-
private protected abstract bool TryGetTopMostConcatOperation(IBinaryOperation binaryOperation, [NotNullWhen(true)] out IBinaryOperation? rootBinaryOperation);
36+
private protected abstract bool IsTopMostConcatOperation(IBinaryOperation binaryOperation);
4037

4138
/// <summary>
4239
/// Remove the built in implicit conversion on operands to concat.
@@ -59,49 +56,19 @@ private void OnCompilationStart(CompilationStartAnalysisContext context)
5956
if (!RequiredSymbols.TryGetSymbols(context.Compilation, out RequiredSymbols symbols))
6057
return;
6158

62-
context.RegisterOperationBlockStartAction(OnOperationBlockStart);
63-
return;
64-
65-
// Local functions
66-
void OnOperationBlockStart(OperationBlockStartAnalysisContext context)
59+
context.RegisterOperationAction(context =>
6760
{
68-
// Maintain set of all top-most concat operations so we don't report sub-expressions of an
69-
// already-reported violation.
61+
// Report diagnostic only if the operation is the top-most concat operation so we don't report sub-expressions of an
62+
// already-reported violation.
7063
// We also don't report any diagnostic if the concat operation has too many operands for the span-based
7164
// Concat overloads to handle.
72-
var topMostConcatOperations = TemporarySet<IBinaryOperation>.Empty;
73-
74-
context.RegisterOperationAction(PopulateTopMostConcatOperations, OperationKind.Binary);
75-
context.RegisterOperationBlockEndAction(ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls);
76-
77-
void PopulateTopMostConcatOperations(OperationAnalysisContext context)
78-
{
79-
// If the current operation is a string-concatenation operation, walk up to the top-most concat
80-
// operation and add it to the set.
81-
var binary = (IBinaryOperation)context.Operation;
82-
if (!TryGetTopMostConcatOperation(binary, out var topMostConcatOperation))
83-
return;
84-
85-
topMostConcatOperations.Add(topMostConcatOperation, context.CancellationToken);
86-
}
87-
88-
void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAnalysisContext context)
89-
{
90-
// We report diagnostics for all top-most concat operations that contain
91-
// direct or conditional substring invocations when there is an applicable span-based overload of
92-
// the string.Concat method.
93-
// We don't report when the concatenation contains anything other than strings or character literals.
94-
foreach (var operation in topMostConcatOperations.NonConcurrentEnumerable)
95-
{
96-
if (ShouldBeReported(operation))
97-
{
98-
context.ReportDiagnostic(operation.CreateDiagnostic(Rule));
99-
}
100-
}
65+
var binary = (IBinaryOperation)context.Operation;
66+
if (IsTopMostConcatOperation(binary) && ShouldBeReported(binary))
67+
context.ReportDiagnostic(binary.CreateDiagnostic(Rule));
68+
}, OperationKind.Binary);
69+
return;
10170

102-
topMostConcatOperations.Free(context.CancellationToken);
103-
}
104-
}
71+
// Local functions
10572

10673
bool ShouldBeReported(IBinaryOperation topMostConcatOperation)
10774
{
@@ -133,8 +100,8 @@ bool ShouldBeReported(IBinaryOperation topMostConcatOperation)
133100

134101
bool IsAnyDirectOrConditionalSubstringInvocation(IOperation operation)
135102
{
136-
if (operation is IConditionalAccessOperation conditionallAccessOperation)
137-
operation = conditionallAccessOperation.WhenNotNull;
103+
if (operation is IConditionalAccessOperation conditionalAccessOperation)
104+
operation = conditionalAccessOperation.WhenNotNull;
138105

139106
return operation is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod);
140107
}

src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,13 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime
1010
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
1111
Public NotInheritable Class BasicUseSpanBasedStringConcat : Inherits UseSpanBasedStringConcat
1212

13-
Private Protected Overrides Function TryGetTopMostConcatOperation(binaryOperation As IBinaryOperation, ByRef rootBinaryOperation As IBinaryOperation) As Boolean
14-
13+
Private Protected Overrides Function IsTopMostConcatOperation(binaryOperation As IBinaryOperation) As Boolean
1514
If Not IsStringConcatOperation(binaryOperation) Then
16-
rootBinaryOperation = Nothing
1715
Return False
1816
End If
1917

20-
Dim parentBinaryOperation = binaryOperation
21-
Dim current As IBinaryOperation
22-
Do
23-
current = parentBinaryOperation
24-
parentBinaryOperation = TryCast(WalkUpImplicitConversionToObject(current.Parent), IBinaryOperation)
25-
Loop While parentBinaryOperation IsNot Nothing AndAlso IsStringConcatOperation(parentBinaryOperation)
26-
27-
rootBinaryOperation = current
28-
Return True
18+
Dim parentBinaryOperation = TryCast(WalkUpImplicitConversionToObject(binaryOperation.Parent), IBinaryOperation)
19+
Return parentBinaryOperation Is Nothing OrElse Not IsStringConcatOperation(parentBinaryOperation)
2920
End Function
3021

3122
Private Protected Overrides Function WalkDownBuiltInImplicitConversionOnConcatOperand(operand As IOperation) As IOperation

0 commit comments

Comments
 (0)