Skip to content

Commit bde31fd

Browse files
committed
* Integer attribute values that fit within target type range should not trigger mismatching type diagnostics
* Test all valid attribute value types when performing type matching
1 parent a41008f commit bde31fd

File tree

14 files changed

+688
-186
lines changed

14 files changed

+688
-186
lines changed

src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
using Microsoft.CodeAnalysis;
44
using Microsoft.CodeAnalysis.CSharp.Syntax;
55

6+
using System;
67
using System.Collections.Immutable;
78

89
internal static class AnalyzerHelper
910
{
10-
public static LocalizableResourceString GetResourceString(string name) => new LocalizableResourceString(name, BenchmarkDotNetAnalyzerResources.ResourceManager, typeof(BenchmarkDotNetAnalyzerResources));
11+
public static LocalizableResourceString GetResourceString(string name) => new(name, BenchmarkDotNetAnalyzerResources.ResourceManager, typeof(BenchmarkDotNetAnalyzerResources));
1112

1213
public static INamedTypeSymbol? GetBenchmarkAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.BenchmarkAttribute");
1314

@@ -90,5 +91,90 @@ public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol)
9091

9192
return typeName;
9293
}
94+
95+
public static bool ValueFitsInType(object value, ITypeSymbol targetType)
96+
{
97+
98+
99+
try
100+
{
101+
switch (targetType.SpecialType)
102+
{
103+
case SpecialType.System_Byte:
104+
var byteVal = Convert.ToInt64(value);
105+
106+
return byteVal is >= byte.MinValue and <= byte.MaxValue;
107+
108+
case SpecialType.System_SByte:
109+
var sbyteVal = Convert.ToInt64(value);
110+
111+
return sbyteVal is >= sbyte.MinValue and <= sbyte.MaxValue;
112+
113+
case SpecialType.System_Int16:
114+
var int16Val = Convert.ToInt64(value);
115+
116+
return int16Val is >= short.MinValue and <= short.MaxValue;
117+
118+
case SpecialType.System_UInt16:
119+
var uint16Val = Convert.ToInt64(value);
120+
121+
return uint16Val is >= ushort.MinValue and <= ushort.MaxValue;
122+
123+
case SpecialType.System_Int32:
124+
var int32Val = Convert.ToInt64(value);
125+
126+
return int32Val is >= int.MinValue and <= int.MaxValue;
127+
128+
case SpecialType.System_UInt32:
129+
var uint32Val = Convert.ToInt64(value);
130+
131+
return uint32Val is >= uint.MinValue and <= uint.MaxValue;
132+
133+
case SpecialType.System_Int64:
134+
{
135+
_ = Convert.ToInt64(value);
136+
}
137+
138+
return true;
139+
140+
case SpecialType.System_UInt64:
141+
var val = Convert.ToInt64(value);
142+
143+
return val >= 0;
144+
145+
case SpecialType.System_Single:
146+
if (value is double)
147+
{
148+
return false;
149+
}
150+
151+
var floatVal = Convert.ToSingle(value);
152+
153+
return !float.IsInfinity(floatVal);
154+
155+
case SpecialType.System_Double:
156+
var doubleVal = Convert.ToDouble(value);
157+
158+
return !double.IsInfinity(doubleVal);
159+
160+
case SpecialType.System_Decimal:
161+
if (value is double or float)
162+
{
163+
return false;
164+
}
165+
166+
_ = Convert.ToDecimal(value);
167+
168+
return true;
169+
170+
default:
171+
return false;
172+
}
173+
}
174+
catch (Exception)
175+
{
176+
return false;
177+
}
178+
}
93179
}
94180
}

src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer
4343
isEnabledByDefault: true,
4444
description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueType_Description)));
4545

46-
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
47-
RequiresBenchmarkAttributeRule,
48-
MethodWithoutAttributeMustHaveNoParametersRule,
49-
MustHaveMatchingValueCountRule,
50-
MustHaveMatchingValueTypeRule
51-
);
46+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
47+
[
48+
RequiresBenchmarkAttributeRule,
49+
MethodWithoutAttributeMustHaveNoParametersRule,
50+
MustHaveMatchingValueCountRule,
51+
MustHaveMatchingValueTypeRule
52+
];
5253

5354
public override void Initialize(AnalysisContext analysisContext)
5455
{
@@ -70,12 +71,12 @@ public override void Initialize(AnalysisContext analysisContext)
7071

7172
private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
7273
{
73-
if (!(context.Node is MethodDeclarationSyntax methodDeclarationSyntax))
74+
if (context.Node is not MethodDeclarationSyntax methodDeclarationSyntax)
7475
{
7576
return;
7677
}
7778

78-
var argumentsAttributeTypeSymbol = GetArgumentsAttributeTypeSymbol(context.Compilation);
79+
var argumentsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute");
7980
if (argumentsAttributeTypeSymbol == null)
8081
{
8182
return;
@@ -104,7 +105,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
104105
return;
105106
}
106107

107-
var methodParameterTypeSymbolsBuilder = ImmutableArray.CreateBuilder<ITypeSymbol>(methodDeclarationSyntax.ParameterList.Parameters.Count);
108+
var methodParameterTypeSymbolsBuilder = ImmutableArray.CreateBuilder<ITypeSymbol?>(methodDeclarationSyntax.ParameterList.Parameters.Count);
108109

109110
foreach (var parameterSyntax in methodDeclarationSyntax.ParameterList.Parameters)
110111
{
@@ -168,7 +169,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
168169
continue;
169170
}
170171

171-
ReportIfValueTypeMismatchDiagnostic(i => collectionExpressionSyntax.Elements[i] is ExpressionElementSyntax expressionElementSyntax ? expressionElementSyntax.Expression : null);
172+
ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => collectionExpressionSyntax.Elements[i] is ExpressionElementSyntax expressionElementSyntax ? expressionElementSyntax.Expression : null);
172173
}
173174

174175
// Array creation expression
@@ -208,7 +209,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
208209
}
209210

210211
// ReSharper disable once PossibleNullReferenceException
211-
ReportIfValueTypeMismatchDiagnostic(i => arrayCreationExpressionSyntax.Initializer.Expressions[i]);
212+
ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => arrayCreationExpressionSyntax.Initializer.Expressions[i]);
212213
}
213214
}
214215
}
@@ -229,7 +230,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
229230
}
230231

231232
// ReSharper disable once PossibleNullReferenceException
232-
ReportIfValueTypeMismatchDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression);
233+
ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression);
233234
}
234235
else
235236
{
@@ -242,7 +243,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
242243
}
243244

244245
// ReSharper disable once PossibleNullReferenceException
245-
ReportIfValueTypeMismatchDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression);
246+
ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression);
246247
}
247248
}
248249
}
@@ -260,7 +261,7 @@ void ReportMustHaveMatchingValueCountDiagnostic(Location diagnosticLocation, int
260261
valueCount));
261262
}
262263

263-
void ReportIfValueTypeMismatchDiagnostic(Func<int, ExpressionSyntax> valueExpressionSyntaxFunc)
264+
void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(Func<int, ExpressionSyntax> valueExpressionSyntaxFunc)
264265
{
265266
for (var i = 0; i < methodParameterTypeSymbols.Length; i++)
266267
{
@@ -282,23 +283,35 @@ void ReportIfValueTypeMismatchDiagnostic(Func<int, ExpressionSyntax> valueExpres
282283
var conversionSummary = context.Compilation.ClassifyConversion(actualValueTypeSymbol, methodParameterTypeSymbol);
283284
if (!conversionSummary.IsImplicit)
284285
{
285-
ReportMustHaveMatchingValueTypeDiagnostic(valueExpressionSyntax.GetLocation(),
286-
valueExpressionSyntax.ToString(),
287-
methodParameterTypeSymbol.ToString(),
288-
actualValueTypeSymbol.ToString());
286+
if (conversionSummary is { IsExplicit: true, IsEnumeration: false })
287+
{
288+
var constantValue = context.SemanticModel.GetConstantValue(valueExpressionSyntax is CastExpressionSyntax castExpressionSyntax ? castExpressionSyntax.Expression : valueExpressionSyntax);
289+
if (constantValue is { HasValue: true, Value: not null })
290+
{
291+
if (AnalyzerHelper.ValueFitsInType(constantValue.Value, methodParameterTypeSymbol))
292+
{
293+
return;
294+
}
295+
}
296+
}
297+
298+
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(),
299+
valueExpressionSyntax.ToString(),
300+
methodParameterTypeSymbol.ToString(),
301+
actualValueTypeSymbol.ToString());
289302
}
290303
}
291304
else
292305
{
293-
ReportMustHaveMatchingValueTypeDiagnostic(valueExpressionSyntax.GetLocation(),
294-
valueExpressionSyntax.ToString(),
295-
methodParameterTypeSymbol.ToString());
306+
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(),
307+
valueExpressionSyntax.ToString(),
308+
methodParameterTypeSymbol.ToString());
296309
}
297310
}
298311

299312
return;
300313

301-
void ReportMustHaveMatchingValueTypeDiagnostic(Location diagnosticLocation, string value, string expectedType, string actualType = null)
314+
void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLocation, string value, string expectedType, string? actualType = null)
302315
{
303316
context.ReportDiagnostic(Diagnostic.Create(MustHaveMatchingValueTypeRule,
304317
diagnosticLocation,
@@ -309,8 +322,6 @@ void ReportMustHaveMatchingValueTypeDiagnostic(Location diagnosticLocation, stri
309322
}
310323
}
311324

312-
private static INamedTypeSymbol GetArgumentsAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute");
313-
314325
private static int? IndexOfNamedArgument(SeparatedSyntaxList<AttributeArgumentSyntax> attributeArguments)
315326
{
316327
var i = 0;

0 commit comments

Comments
 (0)