1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
7 using System
.Reflection
;
8 using System
.Collections
.Generic
;
9 using System
.Threading
;
13 internal static class TypeNameParser
15 static readonly char[] SPECIAL_CHARS
= {',', '[', ']', '&', '*', '+', '\\'}
;
17 internal static Type
GetType (
19 Func
<AssemblyName
, Assembly
> assemblyResolver
,
20 Func
<Assembly
, string, bool, Type
> typeResolver
,
23 ref StackCrawlMark stackMark
)
26 throw new ArgumentNullException (nameof (typeName
));
28 ParsedName pname
= ParseName (typeName
, false, 0, out int end_pos
);
31 throw new ArgumentException ();
35 return ConstructType (pname
, assemblyResolver
, typeResolver
, throwOnError
, ignoreCase
, ref stackMark
);
38 static Type
ConstructType (
40 Func
<AssemblyName
, Assembly
> assemblyResolver
,
41 Func
<Assembly
, string, bool, Type
> typeResolver
,
44 ref StackCrawlMark stackMark
)
47 Assembly assembly
= null;
48 if (pname
.AssemblyName
!= null) {
49 assembly
= ResolveAssembly (pname
.AssemblyName
, assemblyResolver
, throwOnError
, ref stackMark
);
51 // If throwOnError is true, an exception was already thrown
56 var type
= ResolveType (assembly
, pname
.Names
, typeResolver
, throwOnError
, ignoreCase
, ref stackMark
);
60 // Resolve type arguments
61 if (pname
.TypeArguments
!= null) {
62 var args
= new Type
[pname
.TypeArguments
.Count
];
63 for (int i
= 0; i
< pname
.TypeArguments
.Count
; ++i
) {
64 args
[i
] = ConstructType (pname
.TypeArguments
[i
], assemblyResolver
, typeResolver
, throwOnError
, ignoreCase
, ref stackMark
);
68 type
= type
.MakeGenericType (args
);
72 if (pname
.Modifiers
!= null) {
74 foreach (var mod
in pname
.Modifiers
) {
77 type
= type
.MakeByRefType ();
80 type
= type
.MakePointerType ();
87 type
= type
.MakeArrayType (1);
89 type
= type
.MakeArrayType ();
92 type
= type
.MakeArrayType (mod
);
101 static Assembly
ResolveAssembly (string name
, Func
<AssemblyName
, Assembly
> assemblyResolver
, bool throwOnError
,
102 ref StackCrawlMark stackMark
)
104 var aname
= new AssemblyName (name
);
106 if (assemblyResolver
== null) {
108 return Assembly
.Load (aname
, ref stackMark
, null);
111 return Assembly
.Load (aname
, ref stackMark
, null);
112 } catch (FileNotFoundException
) {
117 var assembly
= assemblyResolver (aname
);
118 if (assembly
== null && throwOnError
)
119 throw new FileNotFoundException (SR
.FileNotFound_ResolveAssembly
, name
);
124 static Type
ResolveType (Assembly assembly
, List
<string> names
, Func
<Assembly
, string, bool, Type
> typeResolver
, bool throwOnError
, bool ignoreCase
, ref StackCrawlMark stackMark
)
128 string name
= EscapeTypeName (names
[0]);
129 // Resolve the top level type.
130 if (typeResolver
!= null) {
131 type
= typeResolver (assembly
, name
, ignoreCase
);
132 if (type
== null && throwOnError
) {
133 if (assembly
== null)
134 throw new TypeLoadException (SR
.Format (SR
.TypeLoad_ResolveType
, name
));
136 throw new TypeLoadException (SR
.Format (SR
.TypeLoad_ResolveTypeFromAssembly
, name
, assembly
.FullName
));
139 if (assembly
== null)
140 type
= RuntimeType
.GetType (name
, throwOnError
, ignoreCase
, false, ref stackMark
);
142 type
= assembly
.GetType (name
, throwOnError
, ignoreCase
);
148 // Resolve nested types.
149 BindingFlags bindingFlags
= BindingFlags
.NonPublic
| BindingFlags
.Public
;
151 bindingFlags
|= BindingFlags
.IgnoreCase
;
153 for (int i
= 1; i
< names
.Count
; ++i
) {
154 type
= type
.GetNestedType (names
[i
], bindingFlags
);
157 throw new TypeLoadException (SR
.Format (SR
.TypeLoad_ResolveNestedType
, names
[i
], names
[i
-1]));
165 static string EscapeTypeName (string name
)
167 if (name
.IndexOfAny (SPECIAL_CHARS
) < 0)
170 var sb
= new StringBuilder ();
171 foreach (char c
in name
) {
172 if (Array
.IndexOf
<char> (SPECIAL_CHARS
, c
) >= 0)
177 return sb
.ToString ();
181 public List
<string> Names
;
182 public List
<ParsedName
> TypeArguments
;
183 public List
<int> Modifiers
;
184 public string AssemblyName
;
187 public override string ToString () {
188 var sb = new StringBuilder ();
189 sb.Append (Names [0]);
190 if (TypeArguments != null) {
192 for (int i = 0; i < TypeArguments.Count; ++i) {
193 if (TypeArguments [i].AssemblyName != null)
195 sb.Append (TypeArguments [i].ToString ());
196 if (TypeArguments [i].AssemblyName != null)
198 if (i < TypeArguments.Count - 1)
203 if (AssemblyName != null)
204 sb.Append ($", {AssemblyName}");
205 return sb.ToString ();
210 // Ported from the C version in mono_reflection_parse_type ()
211 static ParsedName
ParseName (string name
, bool recursed
, int pos
, out int end_pos
)
215 while (pos
< name
.Length
&& name
[pos
] == ' ')
218 var res
= new ParsedName () { Names = new List<string> () }
;
221 int name_start
= pos
;
222 bool in_modifiers
= false;
223 while (pos
< name
.Length
) {
224 switch (name
[pos
]) {
226 res
.Names
.Add (name
.Substring (name_start
, pos
- name_start
));
227 name_start
= pos
+ 1;
247 res
.Names
.Add (name
.Substring (name_start
, pos
- name_start
));
249 bool isbyref
= false;
254 while (pos
< name
.Length
&& !end
) {
255 switch (name
[pos
]) {
262 if (res
.Modifiers
== null)
263 res
.Modifiers
= new List
<int> ();
264 res
.Modifiers
.Add (0);
270 if (res
.Modifiers
== null)
271 res
.Modifiers
= new List
<int> ();
272 res
.Modifiers
.Add (-1);
276 // An array or generic arguments
280 if (pos
== name
.Length
)
283 if (name
[pos
] == ',' || name
[pos
] == '*' || name
[pos
] == ']') {
285 bool bounded
= false;
288 while (pos
< name
.Length
) {
289 if (name
[pos
] == ']')
291 if (name
[pos
] == ',')
293 else if (name
[pos
] == '*') /* '*' means unknown lower bound */
299 if (pos
== name
.Length
)
301 if (name
[pos
] != ']')
304 /* bounded only allowed when rank == 1 */
305 if (bounded
&& rank
> 1)
307 /* n.b. bounded needs both modifiers: -2 == bounded, 1 == rank 1 array */
308 if (res
.Modifiers
== null)
309 res
.Modifiers
= new List
<int> ();
311 res
.Modifiers
.Add (-2);
312 res
.Modifiers
.Add (rank
);
315 if (rank
> 0 || isptr
)
318 res
.TypeArguments
= new List
<ParsedName
> ();
319 while (pos
< name
.Length
) {
320 while (pos
< name
.Length
&& name
[pos
] == ' ')
323 if (pos
< name
.Length
&& name
[pos
] == '[') {
328 var arg
= ParseName (name
, true, pos
, out pos
);
331 res
.TypeArguments
.Add (arg
);
333 /*MS is lenient on [] delimited parameters that aren't fqn - and F# uses them.*/
334 if (fqname
&& pos
< name
.Length
&& name
[pos
] != ']') {
335 if (name
[pos
] != ',')
338 int aname_start
= pos
;
339 while (pos
< name
.Length
&& name
[pos
] != ']')
341 if (pos
== name
.Length
)
343 while (Char
.IsWhiteSpace (name
[aname_start
]))
345 if (aname_start
== pos
)
347 arg
.AssemblyName
= name
.Substring (aname_start
, pos
- aname_start
);
349 } else if (fqname
&& pos
< name
.Length
&& name
[pos
] == ']') {
352 if (pos
< name
.Length
&& name
[pos
] == ']') {
355 } else if (pos
== name
.Length
)
373 while (pos
< name
.Length
&& Char
.IsWhiteSpace (name
[pos
]))
375 if (pos
== name
.Length
)
377 res
.AssemblyName
= name
.Substring (pos
);