@@ -39,7 +39,8 @@ public static class TypeExtensions
39
39
[ typeof ( double ) ] = "double" ,
40
40
[ typeof ( decimal ) ] = "decimal" ,
41
41
[ typeof ( object ) ] = "object" ,
42
- [ typeof ( string ) ] = "string"
42
+ [ typeof ( string ) ] = "string" ,
43
+ [ typeof ( void ) ] = "void"
43
44
} ;
44
45
45
46
/// <summary>
@@ -56,70 +57,146 @@ public static class TypeExtensions
56
57
public static string ToTypeString ( this Type type )
57
58
{
58
59
// Local function to create the formatted string for a given type
59
- static string FormatDisplayString ( Type type )
60
+ static string FormatDisplayString ( Type type , int genericTypeOffset , ReadOnlySpan < Type > typeArguments )
60
61
{
61
62
// Primitive types use the keyword name
62
63
if ( BuiltInTypesMap . TryGetValue ( type , out string ? typeName ) )
63
64
{
64
65
return typeName ! ;
65
66
}
66
67
67
- // Generic types
68
- if (
69
- #if NETSTANDARD1_4
70
- type . GetTypeInfo ( ) . IsGenericType &&
71
- #else
72
- type . IsGenericType &&
73
- #endif
74
- type. FullName is { } fullName &&
75
- fullName . Split ( '`' ) is { } tokens &&
76
- tokens . Length > 0 &&
77
- tokens [ 0 ] is { } genericName &&
78
- genericName . Length > 0 )
68
+ // Array types are displayed as Foo[]
69
+ if ( type . IsArray )
79
70
{
80
- var typeArguments = type. GetGenericArguments( ) . Select( FormatDisplayString ) ;
71
+ var elementType = type . GetElementType ( ) ! ;
72
+ var rank = type . GetArrayRank ( ) ;
73
+
74
+ return $ "{ FormatDisplayString ( elementType , 0 , elementType . GetGenericArguments ( ) ) } [{ new string ( ',' , rank - 1 ) } ]";
75
+ }
76
+
77
+ // By checking generic types here we are only interested in specific cases,
78
+ // ie. nullable value types or value typles. We have a separate path for custom
79
+ // generic types, as we can't rely on this API in that case, as it doesn't show
80
+ // a difference between nested types that are themselves generic, or nested simple
81
+ // types from a generic declaring type. To deal with that, we need to manually track
82
+ // the offset within the array of generic arguments for the whole constructed type.
83
+ if ( type . IsGenericType ( ) )
84
+ {
85
+ var genericTypeDefinition = type . GetGenericTypeDefinition ( ) ;
81
86
82
87
// Nullable<T> types are displayed as T?
83
- var genericType = type. GetGenericTypeDefinition( ) ;
84
- if ( genericType = = typeof ( Nullable < > ) )
88
+ if ( genericTypeDefinition == typeof ( Nullable < > ) )
85
89
{
86
- return $"{ typeArguments . First ( ) } ? ";
90
+ var nullableArguments = type . GetGenericArguments ( ) ;
91
+
92
+ return $ "{ FormatDisplayString ( nullableArguments [ 0 ] , 0 , nullableArguments ) } ?";
87
93
}
88
94
89
95
// ValueTuple<T1, T2> types are displayed as (T1, T2)
90
- if ( genericType == typeof ( ValueTuple < > ) ||
91
- genericType == typeof ( ValueTuple < , > ) ||
92
- genericType == typeof ( ValueTuple < , , > ) ||
93
- genericType == typeof ( ValueTuple < , , , > ) ||
94
- genericType == typeof ( ValueTuple < , , , , > ) ||
95
- genericType == typeof ( ValueTuple < , , , , , > ) ||
96
- genericType == typeof ( ValueTuple < , , , , , , > ) ||
97
- genericType == typeof ( ValueTuple < , , , , , , , > ) )
96
+ if ( genericTypeDefinition == typeof ( ValueTuple < > ) ||
97
+ genericTypeDefinition == typeof ( ValueTuple < , > ) ||
98
+ genericTypeDefinition == typeof ( ValueTuple < , , > ) ||
99
+ genericTypeDefinition == typeof ( ValueTuple < , , , > ) ||
100
+ genericTypeDefinition == typeof ( ValueTuple < , , , , > ) ||
101
+ genericTypeDefinition == typeof ( ValueTuple < , , , , , > ) ||
102
+ genericTypeDefinition == typeof ( ValueTuple < , , , , , , > ) ||
103
+ genericTypeDefinition == typeof ( ValueTuple < , , , , , , , > ) )
98
104
{
99
- return $"( { string . Join ( ", " , typeArguments ) } ) ";
105
+ var formattedTypes = type . GetGenericArguments ( ) . Select ( t => FormatDisplayString ( t , 0 , t . GetGenericArguments ( ) ) ) ;
106
+
107
+ return $ "({ string . Join ( ", " , formattedTypes ) } )";
100
108
}
109
+ }
110
+
111
+ string displayName ;
112
+
113
+ // Generic types
114
+ if ( type . Name . Contains ( '`' ) )
115
+ {
116
+ // Retrieve the current generic arguments for the current type (leaf or not)
117
+ var tokens = type . Name . Split ( '`' ) ;
118
+ var genericArgumentsCount = int . Parse ( tokens [ 1 ] ) ;
119
+ var typeArgumentsOffset = typeArguments . Length - genericTypeOffset - genericArgumentsCount ;
120
+ var currentTypeArguments = typeArguments . Slice ( typeArgumentsOffset , genericArgumentsCount ) . ToArray ( ) ;
121
+ var formattedTypes = currentTypeArguments . Select ( t => FormatDisplayString ( t , 0 , t . GetGenericArguments ( ) ) ) ;
101
122
102
123
// Standard generic types are displayed as Foo<T>
103
- return $"{genericName}<{string.Join(" , ", typeArguments)}>" ;
124
+ displayName = $ "{ tokens [ 0 ] } <{ string . Join ( ", " , formattedTypes ) } >";
125
+
126
+ // Track the current offset for the shared generic arguments list
127
+ genericTypeOffset += genericArgumentsCount ;
128
+ }
129
+ else
130
+ {
131
+ // Simple custom types
132
+ displayName = type . Name ;
104
133
}
105
134
106
- // Array types are displayed as Foo[]
107
- if ( type . IsArray )
135
+ // If the type is nested, recursively format the hierarchy as well
136
+ if ( type . IsNested )
108
137
{
109
- var elementType = type. GetElementType ( ) ;
110
- var rank = type. GetArrayRank ( ) ;
138
+ var openDeclaringType = type . DeclaringType ! ;
139
+ var rootGenericArguments = typeArguments . Slice ( 0 , typeArguments . Length - genericTypeOffset ) . ToArray ( ) ;
140
+
141
+ // If the declaring type is generic, we need to reconstruct the closed type
142
+ // manually, as the declaring type instance doesn't retain type information.
143
+ if ( rootGenericArguments . Length > 0 )
144
+ {
145
+ var closedDeclaringType = openDeclaringType . GetGenericTypeDefinition ( ) . MakeGenericType ( rootGenericArguments ) ;
111
146
112
- return $"{ FormatDisplayString( elementType ) } [ { new string ( ',' , rank - 1 ) } ] ";
147
+ return $ "{ FormatDisplayString ( closedDeclaringType , genericTypeOffset , typeArguments ) } .{ displayName } ";
148
+ }
149
+
150
+ return $ "{ FormatDisplayString ( openDeclaringType , genericTypeOffset , typeArguments ) } .{ displayName } ";
113
151
}
114
152
115
- return type. ToString ( ) ;
153
+ return $ " { type . Namespace } . { displayName } " ;
116
154
}
117
155
118
156
// Atomically get or build the display string for the current type.
119
- // Manually create a static lambda here to enable caching of the generated closure.
120
- // This is a workaround for the missing caching for method group conversions, and should
121
- // be removed once this issue is resolved: https://github.com/dotnet/roslyn/issues/5835.
122
- return DisplayNames. GetValue ( type , t => FormatDisplayString ( t ) ) ;
157
+ return DisplayNames . GetValue ( type , t =>
158
+ {
159
+ // By-ref types are displayed as T&
160
+ if ( t . IsByRef )
161
+ {
162
+ t = t . GetElementType ( ) ! ;
163
+
164
+ return $ "{ FormatDisplayString ( t , 0 , t . GetGenericArguments ( ) ) } &";
165
+ }
166
+
167
+ // Pointer types are displayed as T*
168
+ if ( t . IsPointer )
169
+ {
170
+ int depth = 0 ;
171
+
172
+ // Calculate the pointer indirection level
173
+ while ( t . IsPointer )
174
+ {
175
+ depth ++ ;
176
+ t = t . GetElementType ( ) ! ;
177
+ }
178
+
179
+ return $ "{ FormatDisplayString ( t , 0 , t . GetGenericArguments ( ) ) } { new string ( '*' , depth ) } ";
180
+ }
181
+
182
+ // Standard path for concrete types
183
+ return FormatDisplayString ( t , 0 , t . GetGenericArguments ( ) ) ;
184
+ } ) ;
185
+ }
186
+
187
+ /// <summary>
188
+ /// Returns whether or not a given type is generic.
189
+ /// </summary>
190
+ /// <param name="type">The input type.</param>
191
+ /// <returns>Whether or not the input type is generic.</returns>
192
+ [ Pure ]
193
+ private static bool IsGenericType ( this Type type )
194
+ {
195
+ #if NETSTANDARD1_4
196
+ return type . GetTypeInfo ( ) . IsGenericType ;
197
+ #else
198
+ return type . IsGenericType ;
199
+ #endif
123
200
}
124
201
125
202
#if NETSTANDARD1_4
@@ -128,6 +205,7 @@ tokens[0] is { } genericName &&
128
205
/// </summary>
129
206
/// <param name="type">The input type.</param>
130
207
/// <returns>An array of types representing the generic arguments.</returns>
208
+ [ Pure ]
131
209
private static Type [ ] GetGenericArguments ( this Type type )
132
210
{
133
211
return type . GetTypeInfo ( ) . GenericTypeParameters ;
@@ -139,6 +217,7 @@ private static Type[] GetGenericArguments(this Type type)
139
217
/// <param name="type">The input type.</param>
140
218
/// <param name="value">The type to check against.</param>
141
219
/// <returns><see langword="true"/> if <paramref name="type"/> is an instance of <paramref name="value"/>, <see langword="false"/> otherwise.</returns>
220
+ [ Pure ]
142
221
internal static bool IsInstanceOfType ( this Type type , object value )
143
222
{
144
223
return type . GetTypeInfo ( ) . IsAssignableFrom ( value . GetType ( ) . GetTypeInfo ( ) ) ;
0 commit comments