33using Microsoft . CodeAnalysis . CSharp . Syntax ;
44using System . Collections . Generic ;
55using System . Collections . Immutable ;
6+ using System . Diagnostics ;
67using System . Globalization ;
78using System . Linq ;
89
@@ -18,9 +19,6 @@ public static LocalizableResourceString GetResourceString(string name)
1819 public static INamedTypeSymbol ? GetBenchmarkAttributeTypeSymbol ( Compilation compilation )
1920 => compilation . GetTypeByMetadataName ( "BenchmarkDotNet.Attributes.BenchmarkAttribute" ) ;
2021
21- public static bool AttributeListsContainAttribute ( string attributeName , Compilation compilation , SyntaxList < AttributeListSyntax > attributeLists , SemanticModel semanticModel )
22- => AttributeListsContainAttribute ( compilation . GetTypeByMetadataName ( attributeName ) , attributeLists , semanticModel ) ;
23-
2422 public static bool AttributeListsContainAttribute ( INamedTypeSymbol ? attributeTypeSymbol , SyntaxList < AttributeListSyntax > attributeLists , SemanticModel semanticModel )
2523 {
2624 if ( attributeTypeSymbol == null || attributeTypeSymbol . TypeKind == TypeKind . Error )
@@ -38,7 +36,7 @@ public static bool AttributeListsContainAttribute(INamedTypeSymbol? attributeTyp
3836 continue ;
3937 }
4038
41- if ( attributeSyntaxTypeSymbol . Equals ( attributeTypeSymbol , SymbolEqualityComparer . Default ) )
39+ if ( attributeSyntaxTypeSymbol . Equals ( attributeTypeSymbol ) )
4240 {
4341 return true ;
4442 }
@@ -58,7 +56,7 @@ public static bool AttributeListContainsAttribute(INamedTypeSymbol? attributeTyp
5856 return false ;
5957 }
6058
61- return attributeList . Any ( ad => ad . AttributeClass != null && ad . AttributeClass . Equals ( attributeTypeSymbol , SymbolEqualityComparer . Default ) ) ;
59+ return attributeList . Any ( ad => ad . AttributeClass != null && ad . AttributeClass . Equals ( attributeTypeSymbol ) ) ;
6260 }
6361
6462 public static ImmutableArray < AttributeSyntax > GetAttributes ( string attributeName , Compilation compilation , SyntaxList < AttributeListSyntax > attributeLists , SemanticModel semanticModel )
@@ -83,7 +81,7 @@ public static ImmutableArray<AttributeSyntax> GetAttributes(INamedTypeSymbol? at
8381 continue ;
8482 }
8583
86- if ( attributeSyntaxTypeSymbol . Equals ( attributeTypeSymbol , SymbolEqualityComparer . Default ) )
84+ if ( attributeSyntaxTypeSymbol . Equals ( attributeTypeSymbol ) )
8785 {
8886 attributesBuilder . Add ( attributeSyntax ) ;
8987 }
@@ -93,38 +91,6 @@ public static ImmutableArray<AttributeSyntax> GetAttributes(INamedTypeSymbol? at
9391 return attributesBuilder . ToImmutable ( ) ;
9492 }
9593
96- public static int GetAttributeUsageCount ( string attributeName , Compilation compilation , SyntaxList < AttributeListSyntax > attributeLists , SemanticModel semanticModel )
97- => GetAttributeUsageCount ( compilation . GetTypeByMetadataName ( attributeName ) , attributeLists , semanticModel ) ;
98-
99- public static int GetAttributeUsageCount ( INamedTypeSymbol ? attributeTypeSymbol , SyntaxList < AttributeListSyntax > attributeLists , SemanticModel semanticModel )
100- {
101- var attributeUsageCount = 0 ;
102-
103- if ( attributeTypeSymbol == null )
104- {
105- return 0 ;
106- }
107-
108- foreach ( var attributeListSyntax in attributeLists )
109- {
110- foreach ( var attributeSyntax in attributeListSyntax . Attributes )
111- {
112- var attributeSyntaxTypeSymbol = semanticModel . GetTypeInfo ( attributeSyntax ) . Type ;
113- if ( attributeSyntaxTypeSymbol == null )
114- {
115- continue ;
116- }
117-
118- if ( attributeSyntaxTypeSymbol . Equals ( attributeTypeSymbol , SymbolEqualityComparer . Default ) )
119- {
120- attributeUsageCount ++ ;
121- }
122- }
123- }
124-
125- return attributeUsageCount ;
126- }
127-
12894 public static string NormalizeTypeName ( INamedTypeSymbol namedTypeSymbol )
12995 {
13096 string typeName ;
@@ -145,119 +111,86 @@ public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol)
145111 return typeName ;
146112 }
147113
148- public static bool IsAssignableToField ( Compilation compilation , LanguageVersion languageVersion , string ? valueTypeContainingNamespace , ITypeSymbol targetType , string valueExpression , Optional < object ? > constantValue , string ? valueType )
114+ public static void Deconstruct < T1 , T2 > ( this KeyValuePair < T1 , T2 > tuple , out T1 key , out T2 value )
149115 {
150- const string codeTemplate1 = """
151- {0}
152-
153- file static class Internal {{
154- static readonly {1} x = {2};
155- }}
156- """ ;
157-
158- const string codeTemplate2 = """
159- {0}
160-
161- file static class Internal {{
162- static readonly {1} x = ({2}){3};
163- }}
164- """ ;
165-
166- return IsAssignableTo ( codeTemplate1 , codeTemplate2 , compilation , languageVersion , valueTypeContainingNamespace , targetType , valueExpression , constantValue , valueType ) ;
116+ key = tuple . Key ;
117+ value = tuple . Value ;
167118 }
168119
169- public static bool IsAssignableToLocal ( Compilation compilation , LanguageVersion languageVersion , string ? valueTypeContainingNamespace , ITypeSymbol targetType , string valueExpression , Optional < object ? > constantValue , string ? valueType )
170- {
171- const string codeTemplate1 = """
172- {0}
173-
174- file static class Internal {{
175- static void Method() {{
176- {1} x = {2};
177- }}
178- }}
179- """ ;
180-
181- const string codeTemplate2 = """
182- {0}
183-
184- file static class Internal {{
185- static void Method() {{
186- {1} x = ({2}){3};
187- }}
188- }}
189- """ ;
120+ public static Location GetLocation ( this AttributeData attributeData )
121+ => attributeData . ApplicationSyntaxReference . SyntaxTree . GetLocation ( attributeData . ApplicationSyntaxReference . Span ) ;
190122
191- return IsAssignableTo ( codeTemplate1 , codeTemplate2 , compilation , languageVersion , valueTypeContainingNamespace , targetType , valueExpression , constantValue , valueType ) ;
192- }
193-
194- private static bool IsAssignableTo ( string codeTemplate1 , string codeTemplate2 , Compilation compilation , LanguageVersion languageVersion , string ? valueTypeContainingNamespace , ITypeSymbol targetType , string valueExpression , Optional < object ? > constantValue , string ? valueType )
123+ public static bool IsAssignable ( TypedConstant constant , ExpressionSyntax expression , ITypeSymbol targetType , Compilation compilation )
195124 {
196- var usingDirective = valueTypeContainingNamespace != null ? $ "using { valueTypeContainingNamespace } ;" : "" ;
197-
198- var hasNoCompilerDiagnostics = HasNoCompilerDiagnostics ( string . Format ( codeTemplate1 , usingDirective , targetType , valueExpression ) , compilation , languageVersion ) ;
199- if ( hasNoCompilerDiagnostics )
125+ if ( constant . IsNull )
200126 {
201- return true ;
127+ // Check if targetType is a reference type or nullable.
128+ return targetType . IsReferenceType || targetType . OriginalDefinition . SpecialType == SpecialType . System_Nullable_T ;
202129 }
203130
204- if ( ! constantValue . HasValue || valueType == null )
131+ var sourceType = constant . Type ;
132+ if ( sourceType == null )
205133 {
206134 return false ;
207135 }
208136
209- var constantLiteral = FormatLiteral ( constantValue . Value ) ;
210- if ( constantLiteral == null )
137+ // Test if the constant type is implicitly assignable.
138+ var conversion = compilation . ClassifyConversion ( sourceType , targetType ) ;
139+ if ( conversion . IsImplicit )
211140 {
212- return false ;
141+ return true ;
213142 }
214143
215- return HasNoCompilerDiagnostics ( string . Format ( codeTemplate2 , usingDirective , targetType , valueType , constantLiteral ) , compilation , languageVersion ) ;
144+ // Int32 values fail the test to smaller types, but it's still valid in the generated code to assign the literal to a smaller integer type,
145+ // so test if the expression is implicitly assignable.
146+ var semanticModel = compilation . GetSemanticModel ( expression . SyntaxTree ) ;
147+ // Only enums use explicit casting, so we test with explicit cast only for enums. See BenchmarkConverter.Map(...).
148+ bool isEnum = targetType . TypeKind == TypeKind . Enum ;
149+ // The existing implementation only checks for direct enum type, not Nullable<TEnum>, so we won't check it here either unless BenchmarkConverter gets updated to handle it.
150+ //bool isNullableEnum =
151+ // targetType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T &&
152+ // targetType is INamedTypeSymbol named &&
153+ // named.TypeArguments.Length == 1 &&
154+ // named.TypeArguments[0].TypeKind == TypeKind.Enum;
155+ conversion = semanticModel . ClassifyConversion ( expression , targetType , isEnum ) ;
156+ if ( conversion . IsImplicit )
157+ {
158+ return true ;
159+ }
160+ return isEnum && conversion . IsExplicit ;
216161 }
217162
218- private static bool HasNoCompilerDiagnostics ( string code , Compilation compilation , LanguageVersion languageVersion )
163+ // Assumes a single `params object[] values` constructor
164+ public static ExpressionSyntax GetAttributeParamsArgumentExpression ( this AttributeData attributeData , int index )
219165 {
220- var compilationTestSyntaxTree = CSharpSyntaxTree . ParseText ( code , new CSharpParseOptions ( languageVersion ) ) ;
221-
222- var syntaxTreesWithInterceptorsNamespaces = compilation . SyntaxTrees . Where ( st => st . Options . Features . ContainsKey ( InterceptorsNamespaces ) ) ;
223-
224- var compilerDiagnostics = compilation
225- . RemoveSyntaxTrees ( syntaxTreesWithInterceptorsNamespaces )
226- . AddSyntaxTrees ( compilationTestSyntaxTree )
227- . GetSemanticModel ( compilationTestSyntaxTree )
228- . GetMethodBodyDiagnostics ( )
229- . Where ( d => d . DefaultSeverity == DiagnosticSeverity . Error )
230- . ToList ( ) ;
231-
232- return compilerDiagnostics . Count == 0 ;
233- }
166+ Debug . Assert ( index >= 0 ) ;
167+ // Properties must come after constructor arguments, so we don't need to worry about it here.
168+ var attrSyntax = ( AttributeSyntax ) attributeData . ApplicationSyntaxReference . GetSyntax ( ) ;
169+ var args = attrSyntax . ArgumentList . Arguments ;
170+ Debug . Assert ( args is { Count : > 0 } ) ;
171+ var maybeArrayExpression = args [ 0 ] . Expression ;
172+
173+ #if CODE_ANALYSIS_4_8
174+ if ( maybeArrayExpression is CollectionExpressionSyntax collectionExpressionSyntax )
175+ {
176+ Debug . Assert ( index < collectionExpressionSyntax . Elements . Count ) ;
177+ return ( ( ExpressionElementSyntax ) collectionExpressionSyntax . Elements [ index ] ) . Expression ;
178+ }
179+ #endif
234180
235- private static string ? FormatLiteral ( object ? value )
236- {
237- return value switch
181+ if ( maybeArrayExpression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax )
238182 {
239- byte b => b . ToString ( ) ,
240- sbyte sb => sb . ToString ( ) ,
241- short s => s . ToString ( ) ,
242- ushort us => us . ToString ( ) ,
243- int i => i . ToString ( ) ,
244- uint ui => $ "{ ui } U",
245- long l => $ "{ l } L",
246- ulong ul => $ "{ ul } UL",
247- float f => $ "{ f . ToString ( CultureInfo . InvariantCulture ) } F",
248- double d => $ "{ d . ToString ( CultureInfo . InvariantCulture ) } D",
249- decimal m => $ "{ m . ToString ( CultureInfo . InvariantCulture ) } M",
250- char c => $ "'{ c } '",
251- bool b => b ? "true" : "false" ,
252- string s => $ "\" { s } \" ",
253- null => "null" ,
254- _ => null
255- } ;
256- }
183+ if ( arrayCreationExpressionSyntax . Initializer == null )
184+ {
185+ return maybeArrayExpression ;
186+ }
187+ Debug . Assert ( index < arrayCreationExpressionSyntax . Initializer . Expressions . Count ) ;
188+ return arrayCreationExpressionSyntax . Initializer . Expressions [ index ] ;
189+ }
257190
258- public static void Deconstruct < T1 , T2 > ( this KeyValuePair < T1 , T2 > tuple , out T1 key , out T2 value )
259- {
260- key = tuple . Key ;
261- value = tuple . Value ;
191+ // Params values
192+ Debug . Assert ( index < args . Count ) ;
193+ Debug . Assert ( args [ index ] . NameEquals is null ) ;
194+ return args [ index ] . Expression ;
262195 }
263196}
0 commit comments