Skip to content

Commit c5b0e12

Browse files
committed
Add support to analyze implicit conversion from an array to a Span of said array for [Arguments] attribute values
1 parent 851b45e commit c5b0e12

File tree

9 files changed

+207
-121
lines changed

9 files changed

+207
-121
lines changed

src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using Microsoft.CodeAnalysis;
44
using Microsoft.CodeAnalysis.CSharp;
55
using Microsoft.CodeAnalysis.CSharp.Syntax;
6-
using System.Collections.Generic;
6+
77
using System.Collections.Immutable;
88
using System.Linq;
99

@@ -105,19 +105,44 @@ public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol)
105105
return typeName;
106106
}
107107

108-
public static bool IsConstantAssignableToType(Compilation compilation, ITypeSymbol targetType, string valueExpression)
108+
public static bool IsAssignableToField(Compilation compilation, ITypeSymbol targetType, string valueExpression)
109+
{
110+
var code = $$"""
111+
file static class Internal {
112+
static readonly {{targetType}} x = {{valueExpression}};
113+
}
114+
""";
115+
116+
var syntaxTree = CSharpSyntaxTree.ParseText(code);
117+
118+
var compilationErrors = compilation.AddSyntaxTrees(syntaxTree)
119+
.GetSemanticModel(syntaxTree)
120+
.GetMethodBodyDiagnostics()
121+
.Where(d => d.DefaultSeverity == DiagnosticSeverity.Error)
122+
.ToList();
123+
124+
return compilationErrors.Count == 0;
125+
}
126+
127+
public static bool IsAssignableToLocal(Compilation compilation, ITypeSymbol targetType, string valueExpression)
109128
{
110129
var code = $$"""
111-
file class Internal {
112-
{{targetType}} x = {{valueExpression}};
130+
file static class Internal {
131+
static Internal() {
132+
{{targetType}} x = {{valueExpression}};
133+
}
113134
}
114135
""";
115136

116137
var syntaxTree = CSharpSyntaxTree.ParseText(code);
117138

118-
var diagnostics = compilation.AddSyntaxTrees(syntaxTree).GetSemanticModel(syntaxTree).GetMethodBodyDiagnostics();
139+
var compilationErrors = compilation.AddSyntaxTrees(syntaxTree)
140+
.GetSemanticModel(syntaxTree)
141+
.GetMethodBodyDiagnostics()
142+
.Where(d => d.DefaultSeverity == DiagnosticSeverity.Error)
143+
.ToList();
119144

120-
return diagnostics.Length == 0;
145+
return compilationErrors.Count == 0;
121146
}
122147
}
123148
}

src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -176,41 +176,38 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
176176
else
177177
{
178178
var attributeArgumentSyntaxValueType = context.SemanticModel.GetTypeInfo(attributeArgumentSyntax.Expression).Type;
179-
if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol)
179+
if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol && arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_Object)
180180
{
181-
if (arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_Object)
181+
if (attributeArgumentSyntax.Expression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax)
182182
{
183-
if (attributeArgumentSyntax.Expression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax)
183+
if (arrayCreationExpressionSyntax.Initializer == null)
184184
{
185-
if (arrayCreationExpressionSyntax.Initializer == null)
185+
var rankSpecifierSizeSyntax = arrayCreationExpressionSyntax.Type.RankSpecifiers.First().Sizes.First();
186+
if (rankSpecifierSizeSyntax is LiteralExpressionSyntax literalExpressionSyntax && literalExpressionSyntax.IsKind(SyntaxKind.NumericLiteralExpression))
186187
{
187-
var rankSpecifierSizeSyntax = arrayCreationExpressionSyntax.Type.RankSpecifiers.First().Sizes.First();
188-
if (rankSpecifierSizeSyntax is LiteralExpressionSyntax literalExpressionSyntax && literalExpressionSyntax.IsKind(SyntaxKind.NumericLiteralExpression))
188+
if (literalExpressionSyntax.Token.Value is 0)
189189
{
190-
if (literalExpressionSyntax.Token.Value is int rankSpecifierSize && rankSpecifierSize == 0)
190+
if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0)
191191
{
192-
if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0)
193-
{
194-
ReportMustHaveMatchingValueCountDiagnostic(literalExpressionSyntax.GetLocation(), 0);
195-
}
192+
ReportMustHaveMatchingValueCountDiagnostic(literalExpressionSyntax.GetLocation(), 0);
196193
}
197194
}
198195
}
199-
else
196+
}
197+
else
198+
{
199+
if (methodDeclarationSyntax.ParameterList.Parameters.Count != arrayCreationExpressionSyntax.Initializer.Expressions.Count)
200200
{
201-
if (methodDeclarationSyntax.ParameterList.Parameters.Count != arrayCreationExpressionSyntax.Initializer.Expressions.Count)
202-
{
203-
ReportMustHaveMatchingValueCountDiagnostic(arrayCreationExpressionSyntax.Initializer.Expressions.Count == 0
204-
? arrayCreationExpressionSyntax.Initializer.GetLocation()
205-
: Location.Create(context.FilterTree, arrayCreationExpressionSyntax.Initializer.Expressions.Span),
206-
arrayCreationExpressionSyntax.Initializer.Expressions.Count);
201+
ReportMustHaveMatchingValueCountDiagnostic(arrayCreationExpressionSyntax.Initializer.Expressions.Count == 0
202+
? arrayCreationExpressionSyntax.Initializer.GetLocation()
203+
: Location.Create(context.FilterTree, arrayCreationExpressionSyntax.Initializer.Expressions.Span),
204+
arrayCreationExpressionSyntax.Initializer.Expressions.Count);
207205

208-
continue;
209-
}
210-
211-
// ReSharper disable once PossibleNullReferenceException
212-
ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => arrayCreationExpressionSyntax.Initializer.Expressions[i]);
206+
continue;
213207
}
208+
209+
// ReSharper disable once PossibleNullReferenceException
210+
ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => arrayCreationExpressionSyntax.Initializer.Expressions[i]);
214211
}
215212
}
216213
}
@@ -282,7 +279,7 @@ void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(Func<int, ExpressionSyn
282279
var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntax).Type;
283280
if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error)
284281
{
285-
if (!AnalyzerHelper.IsConstantAssignableToType(context.Compilation, methodParameterTypeSymbol, valueExpressionString))
282+
if (!AnalyzerHelper.IsAssignableToLocal(context.Compilation, methodParameterTypeSymbol, valueExpressionString))
286283
{
287284
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(),
288285
valueExpressionSyntax.ToString(),

src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -179,45 +179,42 @@ private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext c
179179
// Array creation expression
180180

181181
var attributeArgumentSyntaxValueType = context.SemanticModel.GetTypeInfo(attributeArgumentSyntax.Expression).Type;
182-
if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol)
182+
if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol && arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_Object)
183183
{
184-
if (arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_Object)
184+
if (attributeArgumentSyntax.Expression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax)
185185
{
186-
if (attributeArgumentSyntax.Expression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax)
186+
if (arrayCreationExpressionSyntax.Initializer == null)
187187
{
188-
if (arrayCreationExpressionSyntax.Initializer == null)
188+
var rankSpecifierSizeSyntax = arrayCreationExpressionSyntax.Type.RankSpecifiers.First().Sizes.First();
189+
if (rankSpecifierSizeSyntax is LiteralExpressionSyntax literalExpressionSyntax && literalExpressionSyntax.IsKind(SyntaxKind.NumericLiteralExpression))
189190
{
190-
var rankSpecifierSizeSyntax = arrayCreationExpressionSyntax.Type.RankSpecifiers.First().Sizes.First();
191-
if (rankSpecifierSizeSyntax is LiteralExpressionSyntax literalExpressionSyntax && literalExpressionSyntax.IsKind(SyntaxKind.NumericLiteralExpression))
191+
if (literalExpressionSyntax.Token.Value is 0)
192192
{
193-
if (literalExpressionSyntax.Token.Value is 0)
194-
{
195-
context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule,
196-
arrayCreationExpressionSyntax.GetLocation()));
197-
}
193+
context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule,
194+
arrayCreationExpressionSyntax.GetLocation()));
198195
}
199-
200-
return;
201196
}
202197

203-
if (!arrayCreationExpressionSyntax.Initializer.Expressions.Any())
204-
{
205-
context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule,
206-
arrayCreationExpressionSyntax.Initializer.GetLocation()));
198+
return;
199+
}
207200

208-
return;
209-
}
201+
if (!arrayCreationExpressionSyntax.Initializer.Expressions.Any())
202+
{
203+
context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule,
204+
arrayCreationExpressionSyntax.Initializer.GetLocation()));
210205

211-
if (arrayCreationExpressionSyntax.Initializer.Expressions.Count == 1)
212-
{
213-
context.ReportDiagnostic(Diagnostic.Create(UnnecessarySingleValuePassedToAttributeRule,
214-
arrayCreationExpressionSyntax.Initializer.Expressions[0].GetLocation()));
215-
}
206+
return;
207+
}
216208

217-
foreach (var expressionSyntax in arrayCreationExpressionSyntax.Initializer.Expressions)
218-
{
219-
ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(expressionSyntax);
220-
}
209+
if (arrayCreationExpressionSyntax.Initializer.Expressions.Count == 1)
210+
{
211+
context.ReportDiagnostic(Diagnostic.Create(UnnecessarySingleValuePassedToAttributeRule,
212+
arrayCreationExpressionSyntax.Initializer.Expressions[0].GetLocation()));
213+
}
214+
215+
foreach (var expressionSyntax in arrayCreationExpressionSyntax.Initializer.Expressions)
216+
{
217+
ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(expressionSyntax);
221218
}
222219
}
223220

@@ -253,7 +250,7 @@ void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(ExpressionSyntax valueE
253250
var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntax).Type;
254251
if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error)
255252
{
256-
if (!AnalyzerHelper.IsConstantAssignableToType(context.Compilation, expectedValueTypeSymbol, valueExpressionString))
253+
if (!AnalyzerHelper.IsAssignableToField(context.Compilation, expectedValueTypeSymbol, valueExpressionString))
257254
{
258255
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(),
259256
valueExpressionString,

src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@
148148
<value>Referenced generic benchmark class '{0}' has no [GenericTypeArguments] attribute(s)</value>
149149
</data>
150150
<data name="BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute_Description" xml:space="preserve">
151-
<value>A generic benchmark class referenced in the BenchmarkRunner.Run method must be must be annotated with at least one [GenericTypeArguments] attribute</value>
151+
<value>A generic benchmark class referenced in the BenchmarkRunner.Run method must be annotated with at least one [GenericTypeArguments] attribute</value>
152152
</data>
153153
<data name="General_BenchmarkClass_ClassMustBeNonStatic_Title" xml:space="preserve">
154154
<value>Benchmark classes must be non-static</value>
@@ -250,7 +250,7 @@
250250
<value>Benchmark method without [Arguments] attribute(s) cannot declare parameters</value>
251251
</data>
252252
<data name="Attributes_ArgumentsAttribute_MustHaveMatchingValueType_MessageFormat" xml:space="preserve">
253-
<value>Unexpected type for value '{0}'. Expected '{1}' but found '{2}'.</value>
253+
<value>Unexpected type for argument value '{0}'. Expected '{1}' but found '{2}'.</value>
254254
</data>
255255
<data name="Attributes_GeneralParameterAttributes_PropertyMustBePublic_MessageFormat" xml:space="preserve">
256256
<value>Property '{0}' annotated with [{1}] must be public</value>

0 commit comments

Comments
 (0)