33
44using System . Diagnostics ;
55using System . Diagnostics . CodeAnalysis ;
6+ using System . Globalization ;
67using System . IO ;
78using System . Reflection . Metadata ;
89using System . Runtime . CompilerServices ;
@@ -21,9 +22,12 @@ internal partial struct TypeNameResolver
2122 private bool _extensibleParser ;
2223 private bool _requireAssemblyQualifiedName ;
2324 private bool _suppressContextualReflectionContext ;
25+ private IntPtr _unsafeAccessorMethod ;
2426 private Assembly ? _requestingAssembly ;
2527 private Assembly ? _topLevelAssembly ;
2628
29+ private bool SupportsUnboundGenerics { get => _unsafeAccessorMethod != IntPtr . Zero ; }
30+
2731 [ RequiresUnreferencedCode ( "The type might be removed" ) ]
2832 internal static Type ? GetType (
2933 string typeName ,
@@ -128,14 +132,14 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName,
128132
129133 // Used by VM
130134 internal static unsafe RuntimeType ? GetTypeHelper ( char * pTypeName , RuntimeAssembly ? requestingAssembly ,
131- bool throwOnError , bool requireAssemblyQualifiedName )
135+ bool throwOnError , bool requireAssemblyQualifiedName , IntPtr unsafeAccessorMethod )
132136 {
133137 ReadOnlySpan < char > typeName = MemoryMarshal . CreateReadOnlySpanFromNullTerminated ( pTypeName ) ;
134- return GetTypeHelper ( typeName , requestingAssembly , throwOnError , requireAssemblyQualifiedName ) ;
138+ return GetTypeHelper ( typeName , requestingAssembly , throwOnError , requireAssemblyQualifiedName , unsafeAccessorMethod ) ;
135139 }
136140
137141 internal static unsafe RuntimeType ? GetTypeHelper ( ReadOnlySpan < char > typeName , RuntimeAssembly ? requestingAssembly ,
138- bool throwOnError , bool requireAssemblyQualifiedName )
142+ bool throwOnError , bool requireAssemblyQualifiedName , IntPtr unsafeAccessorMethod = 0 )
139143 {
140144 // Compat: Empty name throws TypeLoadException instead of
141145 // the natural ArgumentException
@@ -158,6 +162,7 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName,
158162 _throwOnError = throwOnError ,
159163 _suppressContextualReflectionContext = true ,
160164 _requireAssemblyQualifiedName = requireAssemblyQualifiedName ,
165+ _unsafeAccessorMethod = unsafeAccessorMethod ,
161166 } . Resolve ( parsed ) ;
162167
163168 if ( type != null )
@@ -186,6 +191,9 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName,
186191 return assembly ;
187192 }
188193
194+ [ LibraryImport ( RuntimeHelpers . QCall , EntryPoint = "UnsafeAccessors_ResolveGenericParamToTypeHandle" ) ]
195+ private static partial IntPtr ResolveGenericParamToTypeHandle ( IntPtr unsafeAccessorMethod , [ MarshalAs ( UnmanagedType . Bool ) ] bool isMethodParam , uint paramIndex ) ;
196+
189197 [ UnconditionalSuppressMessage ( "ReflectionAnalysis" , "IL2026:RequiresUnreferencedCode" ,
190198 Justification = "TypeNameResolver.GetType is marked as RequiresUnreferencedCode." ) ]
191199 [ UnconditionalSuppressMessage ( "ReflectionAnalysis" , "IL2075:UnrecognizedReflectionPattern" ,
@@ -228,6 +236,42 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName,
228236 {
229237 if ( assembly is null )
230238 {
239+ if ( SupportsUnboundGenerics
240+ && ! string . IsNullOrEmpty ( escapedTypeName )
241+ && escapedTypeName [ 0 ] == '!' )
242+ {
243+ Debug . Assert ( _throwOnError ) ; // Unbound generic support currently always throws.
244+
245+ // Parse the type as an unbound generic parameter. Following the common VAR/MVAR IL syntax:
246+ // !<Number> - Represents a zero-based index into the type's generic parameters.
247+ // !!<Number> - Represents a zero-based index into the method's generic parameters.
248+
249+ // Confirm we have at least one more character
250+ if ( escapedTypeName . Length == 1 )
251+ {
252+ throw new TypeLoadException ( SR . Format ( SR . TypeLoad_ResolveType , escapedTypeName ) , typeName : escapedTypeName ) ;
253+ }
254+
255+ // At this point we expect either another '!' and then a number or a number.
256+ bool isMethodParam = escapedTypeName [ 1 ] == '!' ;
257+ ReadOnlySpan < char > toParse = isMethodParam
258+ ? escapedTypeName . AsSpan ( 2 ) // Skip over "!!"
259+ : escapedTypeName . AsSpan ( 1 ) ; // Skip over "!"
260+ if ( ! uint . TryParse ( toParse , NumberStyles . None , null , out uint paramIndex ) )
261+ {
262+ throw new TypeLoadException ( SR . Format ( SR . TypeLoad_ResolveType , escapedTypeName ) , typeName : escapedTypeName ) ;
263+ }
264+
265+ Debug . Assert ( _unsafeAccessorMethod != IntPtr . Zero ) ;
266+ IntPtr typeHandle = ResolveGenericParamToTypeHandle ( _unsafeAccessorMethod , isMethodParam , paramIndex ) ;
267+ if ( typeHandle == IntPtr . Zero )
268+ {
269+ throw new TypeLoadException ( SR . Format ( SR . TypeLoad_ResolveType , escapedTypeName ) , typeName : escapedTypeName ) ;
270+ }
271+
272+ return RuntimeTypeHandle . GetRuntimeTypeFromHandle ( typeHandle ) ;
273+ }
274+
231275 if ( _requireAssemblyQualifiedName )
232276 {
233277 if ( _throwOnError )
0 commit comments