33
44using System ;
55using System . Collections . Generic ;
6+ using System . Diagnostics . CodeAnalysis ;
67using System . Linq ;
7- using System . Text ;
88using Microsoft . CodeAnalysis ;
99
1010namespace Microsoft . AspNetCore . OpenApi . SourceGenerators . Xml ;
@@ -14,14 +14,19 @@ internal sealed record MemberKey(
1414 MemberType MemberKind ,
1515 string ? Name ,
1616 string ? ReturnType ,
17- string [ ] ? Parameters ) : IEquatable < MemberKey >
17+ List < string > ? Parameters ) : IEquatable < MemberKey >
1818{
19- private static readonly SymbolDisplayFormat _typeKeyFormat = new (
19+ private static readonly SymbolDisplayFormat _withTypeParametersFormat = new (
2020 globalNamespaceStyle : SymbolDisplayGlobalNamespaceStyle . Included ,
2121 typeQualificationStyle : SymbolDisplayTypeQualificationStyle . NameAndContainingTypesAndNamespaces ,
2222 genericsOptions : SymbolDisplayGenericsOptions . IncludeTypeParameters ) ;
2323
24- public static MemberKey FromMethodSymbol ( IMethodSymbol method , Compilation compilation )
24+ private static readonly SymbolDisplayFormat _sansTypeParametersFormat = new (
25+ globalNamespaceStyle : SymbolDisplayGlobalNamespaceStyle . Included ,
26+ typeQualificationStyle : SymbolDisplayTypeQualificationStyle . NameAndContainingTypesAndNamespaces ,
27+ genericsOptions : SymbolDisplayGenericsOptions . None ) ;
28+
29+ public static MemberKey ? FromMethodSymbol ( IMethodSymbol method , Compilation compilation )
2530 {
2631 string returnType ;
2732 if ( method . ReturnsVoid )
@@ -44,94 +49,193 @@ public static MemberKey FromMethodSymbol(IMethodSymbol method, Compilation compi
4449 }
4550 }
4651
47- returnType = actualReturnType . TypeKind == TypeKind . TypeParameter
48- ? "typeof(object)"
49- : $ "typeof({ ReplaceGenericArguments ( actualReturnType . ToDisplayString ( _typeKeyFormat ) ) } )";
52+ if ( actualReturnType . TypeKind == TypeKind . TypeParameter )
53+ {
54+ returnType = "typeof(object)" ;
55+ }
56+ else if ( TryGetFormattedTypeName ( actualReturnType , out var formattedReturnType ) )
57+ {
58+ returnType = $ "typeof({ formattedReturnType } )";
59+ }
60+ else
61+ {
62+ return null ;
63+ }
5064 }
5165
5266 // Handle extension methods by skipping the 'this' parameter
53- var parameters = method . Parameters
54- . Where ( p => ! p . IsThis )
55- . Select ( p =>
67+ List < string > parameters = [ ] ;
68+ foreach ( var parameter in method . Parameters )
69+ {
70+ if ( parameter . IsThis )
71+ {
72+ continue ;
73+ }
74+
75+ if ( parameter . Type . TypeKind == TypeKind . TypeParameter )
76+ {
77+ parameters . Add ( "typeof(object)" ) ;
78+ }
79+ else if ( parameter . IsParams && parameter . Type is IArrayTypeSymbol arrayType )
5680 {
57- if ( p . Type . TypeKind == TypeKind . TypeParameter )
81+ if ( TryGetFormattedTypeName ( arrayType . ElementType , out var formattedArrayType ) )
5882 {
59- return "typeof(object)" ;
83+ parameters . Add ( $ "typeof({ formattedArrayType } [])" ) ;
6084 }
61-
62- // For params arrays, use the array type
63- if ( p . IsParams && p . Type is IArrayTypeSymbol arrayType )
85+ else
6486 {
65- return $ "typeof( { ReplaceGenericArguments ( arrayType . ToDisplayString ( _typeKeyFormat ) ) } )" ;
87+ return null ;
6688 }
89+ }
90+ else if ( TryGetFormattedTypeName ( parameter . Type , out var formattedParameterType ) )
91+ {
92+ parameters . Add ( $ "typeof({ formattedParameterType } )") ;
93+ }
94+ else
95+ {
96+ return null ;
97+ }
98+ }
6799
68- return $ "typeof({ ReplaceGenericArguments ( p . Type . ToDisplayString ( _typeKeyFormat ) ) } )";
69- } )
70- . ToArray ( ) ;
71-
72- // For generic methods, use the containing type with generic parameters
73- var declaringType = method . ContainingType ;
74- var typeDisplay = declaringType . ToDisplayString ( _typeKeyFormat ) ;
75-
76- // If the method is in a generic type, we need to handle the type parameters
77- if ( declaringType . IsGenericType )
100+ if ( TryGetFormattedTypeName ( method . ContainingType , out var formattedDeclaringType ) )
78101 {
79- typeDisplay = ReplaceGenericArguments ( typeDisplay ) ;
102+ return new MemberKey (
103+ $ "typeof({ formattedDeclaringType } )",
104+ MemberType . Method ,
105+ method . MetadataName , // Use MetadataName to match runtime MethodInfo.Name
106+ returnType ,
107+ parameters ) ;
80108 }
81-
82- return new MemberKey (
83- $ "typeof({ typeDisplay } )",
84- MemberType . Method ,
85- method . MetadataName , // Use MetadataName to match runtime MethodInfo.Name
86- returnType ,
87- parameters ) ;
109+ return null ;
88110 }
89111
90- public static MemberKey FromPropertySymbol ( IPropertySymbol property )
112+ public static MemberKey ? FromPropertySymbol ( IPropertySymbol property )
91113 {
92- return new MemberKey (
93- $ "typeof({ ReplaceGenericArguments ( property . ContainingType . ToDisplayString ( _typeKeyFormat ) ) } )",
94- MemberType . Property ,
95- property . Name ,
96- null ,
97- null ) ;
114+ if ( TryGetFormattedTypeName ( property . ContainingType , out var typeName ) )
115+ {
116+ return new MemberKey (
117+ $ "typeof({ typeName } )",
118+ MemberType . Property ,
119+ property . Name ,
120+ null ,
121+ null ) ;
122+ }
123+ return null ;
98124 }
99125
100- public static MemberKey FromTypeSymbol ( INamedTypeSymbol type )
126+ public static MemberKey ? FromTypeSymbol ( INamedTypeSymbol type )
101127 {
102- return new MemberKey (
103- $ "typeof({ ReplaceGenericArguments ( type . ToDisplayString ( _typeKeyFormat ) ) } )",
104- MemberType . Type ,
105- null ,
106- null ,
107- null ) ;
128+ if ( TryGetFormattedTypeName ( type , out var typeName ) )
129+ {
130+ return new MemberKey (
131+ $ "typeof({ typeName } )",
132+ MemberType . Type ,
133+ null ,
134+ null ,
135+ null ) ;
136+ }
137+ return null ;
108138 }
109139
110140 /// Supports replacing generic type arguments to support use of open
111141 /// generics in `typeof` expressions for the declaring type.
112- private static string ReplaceGenericArguments ( string typeName )
142+ private static bool TryGetFormattedTypeName ( ITypeSymbol typeSymbol , [ NotNullWhen ( true ) ] out string ? typeName , bool isNestedCall = false )
113143 {
114- var stack = new Stack < int > ( ) ;
115- var result = new StringBuilder ( typeName ) ;
116- for ( var i = 0 ; i < result . Length ; i ++ )
144+ if ( typeSymbol is INamedTypeSymbol { OriginalDefinition . SpecialType : SpecialType . System_Nullable_T } nullableType )
117145 {
118- if ( result [ i ] == '<' )
146+ typeName = typeSymbol . ToDisplayString ( _withTypeParametersFormat ) ;
147+ return true ;
148+ }
149+
150+ // Handle tuples specially since they are represented as generic
151+ // ValueTuple types and trigger the logic for handling generics in
152+ // nested values.
153+ if ( typeSymbol is INamedTypeSymbol { IsTupleType : true } namedType )
154+ {
155+ return TryHandleTupleType ( namedType , out typeName ) ;
156+ }
157+
158+ if ( typeSymbol is INamedTypeSymbol { IsGenericType : true } genericType )
159+ {
160+ // If any of the type arguments are type parameters, then they have not
161+ // been substituted for a concrete type and we need to model them as open
162+ // generics if possible to avoid emitting a type with type parameters that
163+ // cannot be used in a typeof expression.
164+ var hasTypeParameters = genericType . TypeArguments . Any ( t => t . TypeKind == TypeKind . TypeParameter ) ;
165+ var baseTypeName = genericType . ToDisplayString ( _sansTypeParametersFormat ) ;
166+
167+ if ( ! hasTypeParameters )
168+ {
169+ var typeArgStrings = new List < string > ( ) ;
170+ var allArgumentsResolved = true ;
171+
172+ // Loop through each type argument to handle nested generics.
173+ foreach ( var typeArg in genericType . TypeArguments )
174+ {
175+ if ( TryGetFormattedTypeName ( typeArg , out var argTypeName , isNestedCall : true ) )
176+ {
177+ typeArgStrings . Add ( argTypeName ) ;
178+ }
179+ else
180+ {
181+ typeName = null ;
182+ return false ;
183+ }
184+ }
185+
186+ if ( allArgumentsResolved )
187+ {
188+ typeName = $ "{ baseTypeName } <{ string . Join ( ", " , typeArgStrings ) } >";
189+ return true ;
190+ }
191+ }
192+ else
119193 {
120- stack . Push ( i ) ;
194+ if ( isNestedCall )
195+ {
196+ // If this is a nested call, we can't use open generics so there's no way
197+ // for us to emit a member key. Return false and skip over this type in the code
198+ // generation.
199+ typeName = null ;
200+ return false ;
201+ }
202+
203+ // If we got here, we can successfully emit a member key for the open generic type.
204+ var genericArgumentsCount = genericType . TypeArguments . Length ;
205+ var openGenericsPlaceholder = "<" + new string ( ',' , genericArgumentsCount - 1 ) + ">" ;
206+
207+ typeName = baseTypeName + openGenericsPlaceholder ;
208+ return true ;
121209 }
122- else if ( result [ i ] == '>' && stack . Count > 0 )
210+ }
211+
212+ typeName = typeSymbol . ToDisplayString ( _withTypeParametersFormat ) ;
213+ return true ;
214+ }
215+
216+ private static bool TryHandleTupleType ( INamedTypeSymbol tupleType , [ NotNullWhen ( true ) ] out string ? typeName )
217+ {
218+ List < string > elementTypes = [ ] ;
219+ foreach ( var element in tupleType . TupleElements )
220+ {
221+ if ( element . Type . TypeKind == TypeKind . TypeParameter )
123222 {
124- var start = stack . Pop ( ) ;
125- // Replace everything between < and > with empty strings separated by commas
126- var segment = result . ToString ( start + 1 , i - start - 1 ) ;
127- var commaCount = segment . Count ( c => c == ',' ) ;
128- var replacement = new string ( ',' , commaCount ) ;
129- result . Remove ( start + 1 , i - start - 1 ) ;
130- result . Insert ( start + 1 , replacement ) ;
131- i = start + replacement . Length + 1 ;
223+ elementTypes . Add ( "object" ) ;
224+ }
225+ else
226+ {
227+ // Process each tuple element and handle nested generics
228+ if ( ! TryGetFormattedTypeName ( element . Type , out var elementTypeName , isNestedCall : true ) )
229+ {
230+ typeName = null ;
231+ return false ;
232+ }
233+ elementTypes . Add ( elementTypeName ) ;
132234 }
133235 }
134- return result . ToString ( ) ;
236+
237+ typeName = $ "global::System.ValueTuple<{ string . Join ( ", " , elementTypes ) } >";
238+ return true ;
135239 }
136240}
137241
0 commit comments