Make TypeNameParser consistently use tabs
[mono-project.git] / netcore / System.Private.CoreLib / src / System / TypeNameParser.cs
blob0dff68d948e58f896f97fba73487a371e53bea23
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.
5 using System.Text;
6 using System.IO;
7 using System.Reflection;
8 using System.Collections.Generic;
9 using System.Threading;
11 namespace System
13 internal static class TypeNameParser
15 static readonly char[] SPECIAL_CHARS = {',', '[', ']', '&', '*', '+', '\\'};
17 internal static Type GetType (
18 string typeName,
19 Func<AssemblyName, Assembly> assemblyResolver,
20 Func<Assembly, string, bool, Type> typeResolver,
21 bool throwOnError,
22 bool ignoreCase,
23 ref StackCrawlMark stackMark)
25 if (typeName == null)
26 throw new ArgumentNullException (nameof (typeName));
28 ParsedName pname = ParseName (typeName, false, 0, out int end_pos);
29 if (pname == null) {
30 if (throwOnError)
31 throw new ArgumentException ();
32 return null;
35 return ConstructType (pname, assemblyResolver, typeResolver, throwOnError, ignoreCase, ref stackMark);
38 static Type ConstructType (
39 ParsedName pname,
40 Func<AssemblyName, Assembly> assemblyResolver,
41 Func<Assembly, string, bool, Type> typeResolver,
42 bool throwOnError,
43 bool ignoreCase,
44 ref StackCrawlMark stackMark)
46 // Resolve assembly
47 Assembly assembly = null;
48 if (pname.AssemblyName != null) {
49 assembly = ResolveAssembly (pname.AssemblyName, assemblyResolver, throwOnError, ref stackMark);
50 if (assembly == null)
51 // If throwOnError is true, an exception was already thrown
52 return null;
55 // Resolve base type
56 var type = ResolveType (assembly, pname.Names, typeResolver, throwOnError, ignoreCase, ref stackMark);
57 if (type == null)
58 return null;
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);
65 if (args [i] == null)
66 return null;
68 type = type.MakeGenericType (args);
71 // Resolve modifiers
72 if (pname.Modifiers != null) {
73 bool bounded = false;
74 foreach (var mod in pname.Modifiers) {
75 switch (mod) {
76 case 0:
77 type = type.MakeByRefType ();
78 break;
79 case -1:
80 type = type.MakePointerType ();
81 break;
82 case -2:
83 bounded = true;
84 break;
85 case 1:
86 if (bounded)
87 type = type.MakeArrayType (1);
88 else
89 type = type.MakeArrayType ();
90 break;
91 default:
92 type = type.MakeArrayType (mod);
93 break;
98 return type;
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) {
107 if (throwOnError) {
108 return Assembly.Load (aname, ref stackMark, null);
109 } else {
110 try {
111 return Assembly.Load (aname, ref stackMark, null);
112 } catch (FileNotFoundException) {
113 return null;
116 } else {
117 var assembly = assemblyResolver (aname);
118 if (assembly == null && throwOnError)
119 throw new FileNotFoundException (SR.FileNotFound_ResolveAssembly, name);
120 return assembly;
124 static Type ResolveType (Assembly assembly, List<string> names, Func<Assembly, string, bool, Type> typeResolver, bool throwOnError, bool ignoreCase, ref StackCrawlMark stackMark)
126 Type type = null;
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));
135 else
136 throw new TypeLoadException (SR.Format (SR.TypeLoad_ResolveTypeFromAssembly, name, assembly.FullName));
138 } else {
139 if (assembly == null)
140 type = RuntimeType.GetType (name, throwOnError, ignoreCase, false, ref stackMark);
141 else
142 type = assembly.GetType (name, throwOnError, ignoreCase);
145 if (type == null)
146 return null;
148 // Resolve nested types.
149 BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public;
150 if (ignoreCase)
151 bindingFlags |= BindingFlags.IgnoreCase;
153 for (int i = 1; i < names.Count; ++i) {
154 type = type.GetNestedType (names[i], bindingFlags);
155 if (type == null) {
156 if (throwOnError)
157 throw new TypeLoadException (SR.Format (SR.TypeLoad_ResolveNestedType, names[i], names[i-1]));
158 else
159 break;
162 return type;
165 static string EscapeTypeName (string name)
167 if (name.IndexOfAny (SPECIAL_CHARS) < 0)
168 return name;
170 var sb = new StringBuilder ();
171 foreach (char c in name) {
172 if (Array.IndexOf<char> (SPECIAL_CHARS, c) >= 0)
173 sb.Append ('\\');
174 sb.Append (c);
177 return sb.ToString ();
180 class ParsedName {
181 public List<string> Names;
182 public List<ParsedName> TypeArguments;
183 public List<int> Modifiers;
184 public string AssemblyName;
186 /* For debugging
187 public override string ToString () {
188 var sb = new StringBuilder ();
189 sb.Append (Names [0]);
190 if (TypeArguments != null) {
191 sb.Append ("[");
192 for (int i = 0; i < TypeArguments.Count; ++i) {
193 if (TypeArguments [i].AssemblyName != null)
194 sb.Append ('[');
195 sb.Append (TypeArguments [i].ToString ());
196 if (TypeArguments [i].AssemblyName != null)
197 sb.Append (']');
198 if (i < TypeArguments.Count - 1)
199 sb.Append (", ");
201 sb.Append ("]");
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)
213 end_pos = 0;
215 while (pos < name.Length && name [pos] == ' ')
216 pos ++;
218 var res = new ParsedName () { Names = new List<string> () };
220 int start = pos;
221 int name_start = pos;
222 bool in_modifiers = false;
223 while (pos < name.Length) {
224 switch (name [pos]) {
225 case '+':
226 res.Names.Add (name.Substring (name_start, pos - name_start));
227 name_start = pos + 1;
228 break;
229 case '\\':
230 pos ++;
231 break;
232 case '&':
233 case '*':
234 case '[':
235 case ',':
236 case ']':
237 in_modifiers = true;
238 break;
239 default:
240 break;
242 if (in_modifiers)
243 break;
244 pos ++;
247 res.Names.Add (name.Substring (name_start, pos - name_start));
249 bool isbyref = false;
250 bool isptr = false;
251 int rank = -1;
253 bool end = false;
254 while (pos < name.Length && !end) {
255 switch (name [pos]) {
256 case '&':
257 if (isbyref)
258 return null;
259 pos ++;
260 isbyref = true;
261 isptr = false;
262 if (res.Modifiers == null)
263 res.Modifiers = new List<int> ();
264 res.Modifiers.Add (0);
265 break;
266 case '*':
267 if (isbyref)
268 return null;
269 pos ++;
270 if (res.Modifiers == null)
271 res.Modifiers = new List<int> ();
272 res.Modifiers.Add (-1);
273 isptr = true;
274 break;
275 case '[':
276 // An array or generic arguments
277 if (isbyref)
278 return null;
279 pos ++;
280 if (pos == name.Length)
281 return null;
283 if (name [pos] == ',' || name [pos] == '*' || name [pos] == ']') {
284 // Array
285 bool bounded = false;
286 isptr = false;
287 rank = 1;
288 while (pos < name.Length) {
289 if (name [pos] == ']')
290 break;
291 if (name [pos] == ',')
292 rank ++;
293 else if (name [pos] == '*') /* '*' means unknown lower bound */
294 bounded = true;
295 else
296 return null;
297 pos ++;
299 if (pos == name.Length)
300 return null;
301 if (name [pos] != ']')
302 return null;
303 pos ++;
304 /* bounded only allowed when rank == 1 */
305 if (bounded && rank > 1)
306 return null;
307 /* n.b. bounded needs both modifiers: -2 == bounded, 1 == rank 1 array */
308 if (res.Modifiers == null)
309 res.Modifiers = new List<int> ();
310 if (bounded)
311 res.Modifiers.Add (-2);
312 res.Modifiers.Add (rank);
313 } else {
314 // Generic args
315 if (rank > 0 || isptr)
316 return null;
317 isptr = false;
318 res.TypeArguments = new List<ParsedName> ();
319 while (pos < name.Length) {
320 while (pos < name.Length && name [pos] == ' ')
321 pos ++;
322 bool fqname = false;
323 if (pos < name.Length && name [pos] == '[') {
324 pos ++;
325 fqname = true;
328 var arg = ParseName (name, true, pos, out pos);
329 if (arg == null)
330 return null;
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] != ',')
336 return null;
337 pos ++;
338 int aname_start = pos;
339 while (pos < name.Length && name [pos] != ']')
340 pos ++;
341 if (pos == name.Length)
342 return null;
343 while (Char.IsWhiteSpace (name [aname_start]))
344 aname_start ++;
345 if (aname_start == pos)
346 return null;
347 arg.AssemblyName = name.Substring (aname_start, pos - aname_start);
348 pos ++;
349 } else if (fqname && pos < name.Length && name [pos] == ']') {
350 pos ++;
352 if (pos < name.Length && name [pos] == ']') {
353 pos ++;
354 break;
355 } else if (pos == name.Length)
356 return null;
357 pos ++;
360 break;
361 case ']':
362 if (recursed) {
363 end = true;
364 break;
366 return null;
367 case ',':
368 if (recursed) {
369 end = true;
370 break;
372 pos ++;
373 while (pos < name.Length && Char.IsWhiteSpace (name [pos]))
374 pos ++;
375 if (pos == name.Length)
376 return null;
377 res.AssemblyName = name.Substring (pos);
378 end = true;
379 break;
380 default:
381 return null;
383 if (end)
384 break;
387 end_pos = pos;
388 return res;