Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 31 additions & 16 deletions src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace BenchmarkDotNet.Analyzers;

internal static class AnalyzerHelper
{
internal const string InterceptorsNamespaces = "InterceptorsNamespaces";

public static LocalizableResourceString GetResourceString(string name)
=> new(name, BenchmarkDotNetAnalyzerResources.ResourceManager, typeof(BenchmarkDotNetAnalyzerResources));

Expand Down Expand Up @@ -143,48 +145,58 @@ public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol)
return typeName;
}

public static bool IsAssignableToField(Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional<object?> constantValue, string? valueType)
public static bool IsAssignableToField(Compilation compilation, LanguageVersion languageVersion, string? valueTypeContainingNamespace, ITypeSymbol targetType, string valueExpression, Optional<object?> constantValue, string? valueType)
{
const string codeTemplate1 = """
{0}

file static class Internal {{
static readonly {0} x = {1};
static readonly {1} x = {2};
}}
""";

const string codeTemplate2 = """
{0}

file static class Internal {{
static readonly {0} x = ({1}){2};
static readonly {1} x = ({2}){3};
}}
""";

return IsAssignableTo(codeTemplate1, codeTemplate2, compilation, targetType, valueExpression, constantValue, valueType);
return IsAssignableTo(codeTemplate1, codeTemplate2, compilation, languageVersion, valueTypeContainingNamespace, targetType, valueExpression, constantValue, valueType);
}

public static bool IsAssignableToLocal(Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional<object?> constantValue, string? valueType)
public static bool IsAssignableToLocal(Compilation compilation, LanguageVersion languageVersion, string? valueTypeContainingNamespace, ITypeSymbol targetType, string valueExpression, Optional<object?> constantValue, string? valueType)
{
const string codeTemplate1 = """
{0}

file static class Internal {{
static void Method() {{
{0} x = {1};
{1} x = {2};
}}
}}
""";

const string codeTemplate2 = """
{0}

file static class Internal {{
static void Method() {{
{0} x = ({1}){2};
{1} x = ({2}){3};
}}
}}
""";

return IsAssignableTo(codeTemplate1, codeTemplate2, compilation, targetType, valueExpression, constantValue, valueType);
return IsAssignableTo(codeTemplate1, codeTemplate2, compilation, languageVersion, valueTypeContainingNamespace, targetType, valueExpression, constantValue, valueType);
}

private static bool IsAssignableTo(string codeTemplate1, string codeTemplate2, Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional<object?> constantValue, string? valueType)
private static bool IsAssignableTo(string codeTemplate1, string codeTemplate2, Compilation compilation, LanguageVersion languageVersion, string? valueTypeContainingNamespace, ITypeSymbol targetType, string valueExpression, Optional<object?> constantValue, string? valueType)
{
var hasCompilerDiagnostics = HasNoCompilerDiagnostics(string.Format(codeTemplate1, targetType, valueExpression), compilation);
if (hasCompilerDiagnostics)
var usingDirective = valueTypeContainingNamespace != null ? $"using {valueTypeContainingNamespace};" : "";

var hasNoCompilerDiagnostics = HasNoCompilerDiagnostics(string.Format(codeTemplate1, usingDirective, targetType, valueExpression), compilation, languageVersion);
if (hasNoCompilerDiagnostics)
{
return true;
}
Expand All @@ -200,16 +212,19 @@ private static bool IsAssignableTo(string codeTemplate1, string codeTemplate2, C
return false;
}

return HasNoCompilerDiagnostics(string.Format(codeTemplate2, targetType, valueType, constantLiteral), compilation);
return HasNoCompilerDiagnostics(string.Format(codeTemplate2, usingDirective, targetType, valueType, constantLiteral), compilation, languageVersion);
}

private static bool HasNoCompilerDiagnostics(string code, Compilation compilation)
private static bool HasNoCompilerDiagnostics(string code, Compilation compilation, LanguageVersion languageVersion)
{
var syntaxTree = CSharpSyntaxTree.ParseText(code);
var compilationTestSyntaxTree = CSharpSyntaxTree.ParseText(code, new CSharpParseOptions(languageVersion));

var syntaxTreesWithInterceptorsNamespaces = compilation.SyntaxTrees.Where(st => st.Options.Features.ContainsKey(InterceptorsNamespaces));

var compilerDiagnostics = compilation
.AddSyntaxTrees(syntaxTree)
.GetSemanticModel(syntaxTree)
.RemoveSyntaxTrees(syntaxTreesWithInterceptorsNamespaces)
.AddSyntaxTrees(compilationTestSyntaxTree)
.GetSemanticModel(compilationTestSyntaxTree)
.GetMethodBodyDiagnostics()
.Where(d => d.DefaultSeverity == DiagnosticSeverity.Error)
.ToList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,28 +281,109 @@ void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(Func<int, ExpressionSyn

var constantValue = context.SemanticModel.GetConstantValue(valueExpressionSyntax);

var expectedValueTypeString = methodParameterTypeSymbol.ToString();
var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntax).Type;
if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error)

if (actualValueTypeSymbol is
{ TypeKind: TypeKind.Array
or TypeKind.Class
or TypeKind.Struct
or TypeKind.Enum
})
{
if (!AnalyzerHelper.IsAssignableToLocal(context.Compilation, methodParameterTypeSymbol, valueExpressionString, constantValue, actualValueTypeSymbol.ToString()))
var actualValueTypeString = actualValueTypeSymbol.ToString();

var typeTypeSymbol = context.Compilation.GetTypeByMetadataName("System.Type");

if (methodParameterTypeSymbol.Equals(typeTypeSymbol, SymbolEqualityComparer.Default))
{
if (!actualValueTypeSymbol.Equals(typeTypeSymbol, SymbolEqualityComparer.Default))
{
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(
valueExpressionSyntax.GetLocation(),
valueExpressionString,
expectedValueTypeString,
actualValueTypeString
);
}

continue;
}

string? valueTypeContainingNamespace = null;

if (actualValueTypeSymbol.TypeKind == TypeKind.Enum && !actualValueTypeSymbol.ContainingNamespace.IsGlobalNamespace)
{
valueTypeContainingNamespace = actualValueTypeSymbol.ContainingNamespace.ToString();
}

if (actualValueTypeSymbol is IArrayTypeSymbol actualValueArrayTypeSymbol)
{
if (methodParameterTypeSymbol is IArrayTypeSymbol expectedValueArrayTypeSymbol && expectedValueArrayTypeSymbol.ElementType.Equals(typeTypeSymbol, SymbolEqualityComparer.Default))
{
if (!actualValueArrayTypeSymbol.ElementType.Equals(typeTypeSymbol, SymbolEqualityComparer.Default))
{
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(
valueExpressionSyntax.GetLocation(),
valueExpressionString,
expectedValueTypeString,
actualValueTypeString
);
}

continue;
}

if (actualValueArrayTypeSymbol.ElementType.TypeKind == TypeKind.Enum)
{
if (!actualValueArrayTypeSymbol.ElementType.ContainingNamespace.IsGlobalNamespace)
{
valueTypeContainingNamespace = actualValueArrayTypeSymbol.ElementType.ContainingNamespace.ToString();
}
}
else if (actualValueArrayTypeSymbol.ElementType.TypeKind is TypeKind.Struct)
{
if (actualValueArrayTypeSymbol.ElementType.NullableAnnotation == NullableAnnotation.Annotated)
{
continue;
}
}
else if (actualValueArrayTypeSymbol.ElementType.TypeKind is not TypeKind.Class)
{
continue;
}
}

if (!AnalyzerHelper.IsAssignableToLocal(context.Compilation,
(context.FilterTree.Options as CSharpParseOptions)!.LanguageVersion,
valueTypeContainingNamespace,
methodParameterTypeSymbol,
valueExpressionString,
constantValue,
actualValueTypeString))
{
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(
valueExpressionSyntax.GetLocation(),
valueExpressionSyntax.ToString(),
valueExpressionString,
methodParameterTypeSymbol.ToString(),
actualValueTypeSymbol.ToString()
actualValueTypeString
);
}
}
else
else if (constantValue is { HasValue: true, Value: null })
{
if (constantValue is { HasValue: true, Value: null }
&& !AnalyzerHelper.IsAssignableToLocal(context.Compilation, methodParameterTypeSymbol, valueExpressionString, constantValue, null))
if (!AnalyzerHelper.IsAssignableToField(context.Compilation,
(context.FilterTree.Options as CSharpParseOptions)!.LanguageVersion,
null,
methodParameterTypeSymbol,
valueExpressionString,
constantValue,
null))
{
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(
valueExpressionSyntax.GetLocation(),
valueExpressionString,
methodParameterTypeSymbol.ToString(),
expectedValueTypeString,
"null"
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,27 +231,109 @@ void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(ExpressionSyntax valueE

var valueExpressionString = valueExpressionSyntax.ToString();

var expectedValueTypeString = expectedValueTypeSymbol.ToString();
var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntax).Type;
if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error)

if (actualValueTypeSymbol is
{ TypeKind: TypeKind.Array
or TypeKind.Class
or TypeKind.Struct
or TypeKind.Enum
})
{
if (!AnalyzerHelper.IsAssignableToField(context.Compilation, expectedValueTypeSymbol, valueExpressionString, constantValue, actualValueTypeSymbol.ToString()))
var actualValueTypeString = actualValueTypeSymbol.ToString();

var typeTypeSymbol = context.Compilation.GetTypeByMetadataName("System.Type");

if (expectedValueTypeSymbol.Equals(typeTypeSymbol, SymbolEqualityComparer.Default))
{
if (!actualValueTypeSymbol.Equals(typeTypeSymbol, SymbolEqualityComparer.Default))
{
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(
valueExpressionSyntax.GetLocation(),
valueExpressionString,
expectedValueTypeString,
actualValueTypeString
);
}

return;
}

string? valueTypeContainingNamespace = null;

if (actualValueTypeSymbol.TypeKind == TypeKind.Enum && !actualValueTypeSymbol.ContainingNamespace.IsGlobalNamespace)
{
valueTypeContainingNamespace = actualValueTypeSymbol.ContainingNamespace.ToString();
}

if (actualValueTypeSymbol is IArrayTypeSymbol actualValueArrayTypeSymbol)
{
if (expectedValueTypeSymbol is IArrayTypeSymbol expectedValueArrayTypeSymbol && expectedValueArrayTypeSymbol.ElementType.Equals(typeTypeSymbol, SymbolEqualityComparer.Default))
{
if (!actualValueArrayTypeSymbol.ElementType.Equals(typeTypeSymbol, SymbolEqualityComparer.Default))
{
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(
valueExpressionSyntax.GetLocation(),
valueExpressionString,
expectedValueTypeString,
actualValueTypeString
);
}

return;
}

if (actualValueArrayTypeSymbol.ElementType.TypeKind == TypeKind.Enum)
{
if (!actualValueArrayTypeSymbol.ElementType.ContainingNamespace.IsGlobalNamespace)
{
valueTypeContainingNamespace = actualValueArrayTypeSymbol.ElementType.ContainingNamespace.ToString();
}
}
else if (actualValueArrayTypeSymbol.ElementType.TypeKind is TypeKind.Struct)
{
if (actualValueArrayTypeSymbol.ElementType.NullableAnnotation == NullableAnnotation.Annotated)
{
return;
}
}
else if (actualValueArrayTypeSymbol.ElementType.TypeKind is not TypeKind.Class)
{
return;
}
}

if (!AnalyzerHelper.IsAssignableToField(context.Compilation,
(context.FilterTree.Options as CSharpParseOptions)!.LanguageVersion,
valueTypeContainingNamespace,
expectedValueTypeSymbol,
valueExpressionString,
constantValue,
actualValueTypeString))
{
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(
valueExpressionSyntax.GetLocation(),
valueExpressionString,
fieldOrPropertyTypeSyntax.ToString(),
actualValueTypeSymbol.ToString()
expectedValueTypeString,
actualValueTypeString
);
}
}
else if (constantValue is { HasValue: true, Value: null })
{
if (!AnalyzerHelper.IsAssignableToField(context.Compilation, expectedValueTypeSymbol, valueExpressionString, constantValue, null))
if (!AnalyzerHelper.IsAssignableToField(context.Compilation,
(context.FilterTree.Options as CSharpParseOptions)!.LanguageVersion,
null,
expectedValueTypeSymbol,
valueExpressionString,
constantValue,
null))
{
ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(
valueExpressionSyntax.GetLocation(),
valueExpressionString,
fieldOrPropertyTypeSyntax.ToString(),
expectedValueTypeString,
"null"
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<NoWarn>$(NoWarn);CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0-2.final" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
Expand Down
Loading