Skip to content

Commit 3fbb615

Browse files
committed
Support analyzing overload of BenchmarkRunner.Run that takes a Type parameter created by using a typeof expression
1 parent 1f42b60 commit 3fbb615

File tree

6 files changed

+379
-102
lines changed

6 files changed

+379
-102
lines changed

build/common.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<CheckEolTargetFramework>false</CheckEolTargetFramework>
2222
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodingStyle.ruleset</CodeAnalysisRuleSet>
2323
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
24-
24+
<WarningsNotAsErrors>NU1900</WarningsNotAsErrors>
2525
<Nullable>annotations</Nullable>
2626
<!-- Suppress warning for nuget package used in old (unsupported) tfm. -->
2727
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>

src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,25 @@ public static ImmutableArray<AttributeSyntax> GetAttributes(INamedTypeSymbol? at
7070

7171
return attributesBuilder.ToImmutable();
7272
}
73+
74+
public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol)
75+
{
76+
string typeName;
77+
78+
if (namedTypeSymbol.SpecialType != SpecialType.None)
79+
{
80+
typeName = namedTypeSymbol.ToString();
81+
}
82+
else if (namedTypeSymbol.IsUnboundGenericType)
83+
{
84+
typeName = $"{namedTypeSymbol.Name}<{new string(',', namedTypeSymbol.TypeArguments.Length - 1)}>";
85+
}
86+
else
87+
{
88+
typeName = namedTypeSymbol.Name;
89+
}
90+
91+
return typeName;
92+
}
7393
}
7494
}

src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs

Lines changed: 1 addition & 1 deletion
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@
163163
<value>A benchmark class referenced in the BenchmarkRunner.Run method must be unsealed</value>
164164
</data>
165165
<data name="BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_MessageFormat" xml:space="preserve">
166-
<value>Referenced benchmark class '{0}' cannot be sealed</value>
166+
<value>Referenced benchmark class '{0}' is sealed</value>
167167
</data>
168168
<data name="BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Title" xml:space="preserve">
169169
<value>Benchmark classes must be unsealed</value>

src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,36 +79,56 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
7979
return;
8080
}
8181

82-
if (memberAccessExpression.Expression is not IdentifierNameSyntax typeIdentifier)
82+
if (memberAccessExpression.Expression is not IdentifierNameSyntax identifierNameSyntax)
8383
{
8484
return;
8585
}
8686

87-
var classMemberAccessSymbol = context.SemanticModel.GetTypeInfo(typeIdentifier).Type;
88-
if (classMemberAccessSymbol is null || !classMemberAccessSymbol.Equals(context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Running.BenchmarkRunner"), SymbolEqualityComparer.Default))
87+
var classMemberAccessTypeSymbol = context.SemanticModel.GetTypeInfo(identifierNameSyntax).Type;
88+
if ( classMemberAccessTypeSymbol is null
89+
|| classMemberAccessTypeSymbol.TypeKind == TypeKind.Error
90+
|| !classMemberAccessTypeSymbol.Equals(context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Running.BenchmarkRunner"), SymbolEqualityComparer.Default))
8991
{
9092
return;
9193
}
9294

93-
// TODO: Support Type overloads that use the typeof() expression
94-
95-
if (memberAccessExpression.Name is not GenericNameSyntax genericMethod)
95+
if (memberAccessExpression.Name.Identifier.ValueText != "Run")
9696
{
9797
return;
9898
}
9999

100-
if (genericMethod.Identifier.ValueText != "Run")
100+
INamedTypeSymbol? benchmarkClassTypeSymbol;
101+
Location? diagnosticLocation;
102+
103+
if (memberAccessExpression.Name is GenericNameSyntax genericMethod)
101104
{
102-
return;
103-
}
105+
if (genericMethod.TypeArgumentList.Arguments.Count != 1)
106+
{
107+
return;
108+
}
104109

105-
if (genericMethod.TypeArgumentList.Arguments.Count != 1)
110+
diagnosticLocation = Location.Create(context.FilterTree, genericMethod.TypeArgumentList.Arguments.Span);
111+
benchmarkClassTypeSymbol = context.SemanticModel.GetTypeInfo(genericMethod.TypeArgumentList.Arguments[0]).Type as INamedTypeSymbol;
112+
}
113+
else
106114
{
107-
return;
115+
if (invocationExpression.ArgumentList.Arguments.Count == 0)
116+
{
117+
return;
118+
}
119+
120+
// TODO: Support analyzing an array of typeof() expressions
121+
if (invocationExpression.ArgumentList.Arguments[0].Expression is not TypeOfExpressionSyntax typeOfExpression)
122+
{
123+
return;
124+
}
125+
126+
diagnosticLocation = typeOfExpression.Type.GetLocation();
127+
benchmarkClassTypeSymbol = context.SemanticModel.GetTypeInfo(typeOfExpression.Type).Type as INamedTypeSymbol;
128+
108129
}
109130

110-
var benchmarkClassTypeSymbol = context.SemanticModel.GetTypeInfo(genericMethod.TypeArgumentList.Arguments[0]).Type;
111-
if (benchmarkClassTypeSymbol == null || benchmarkClassTypeSymbol.TypeKind == TypeKind.Error)
131+
if (benchmarkClassTypeSymbol == null || benchmarkClassTypeSymbol.TypeKind == TypeKind.Error || (benchmarkClassTypeSymbol.IsGenericType && !benchmarkClassTypeSymbol.IsUnboundGenericType))
112132
{
113133
return;
114134
}
@@ -166,15 +186,15 @@ bool HasBenchmarkAttribute()
166186
}
167187
}
168188

169-
baseType = baseType.BaseType;
189+
baseType = baseType.OriginalDefinition.BaseType;
170190
}
171191

172192
return false;
173193
}
174194

175195
void ReportDiagnostic(DiagnosticDescriptor diagnosticDescriptor)
176196
{
177-
context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, Location.Create(context.FilterTree, genericMethod.TypeArgumentList.Arguments.Span), benchmarkClassTypeSymbol.Name));
197+
context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, diagnosticLocation, AnalyzerHelper.NormalizeTypeName(benchmarkClassTypeSymbol)));
178198
}
179199
}
180200
}

0 commit comments

Comments
 (0)