Merge pull request #1464 from akoeplinger/fix-portable-target
[mono.git] / mcs / class / corlib / System / TypeSpec.cs
1 //
2 // System.Type.cs
3 //
4 // Author:
5 //   Rodrigo Kumpera <kumpera@gmail.com>
6 //
7 //
8 // Copyright (C) 2010 Novell, Inc (http://www.novell.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System.Collections.Generic;
31 using System.IO;
32 using System.Reflection;
33
34 namespace System {
35         internal class ArraySpec
36         {
37                 int dimensions;
38                 bool bound;
39
40                 internal ArraySpec (int dimensions, bool bound)
41                 {
42                         this.dimensions = dimensions;
43                         this.bound = bound;
44                 }
45
46                 internal Type Resolve (Type type)
47                 {
48                         if (bound)
49                                 return type.MakeArrayType (1);
50                         else if (dimensions == 1)
51                                 return type.MakeArrayType ();
52                         return type.MakeArrayType (dimensions);
53                 }
54
55 #if DEBUG
56                 public override string ToString ()
57                 {
58                         if (bound)
59                                 return "[*]";
60                         string str = "[";
61                         for (int i = 1; i < dimensions; ++i)
62                                 str += ",";
63                         return str + "]";
64                 }
65 #endif
66         }
67
68         internal class TypeSpec
69         {
70                 string name, assembly_name;
71                 List<string> nested;
72                 List<TypeSpec> generic_params;
73                 List<ArraySpec> array_spec;
74                 int pointer_level;
75                 bool is_byref;
76
77                 bool IsArray {
78                         get { return array_spec != null; }
79                 }
80
81 #if DEBUG
82                 public override string ToString () {
83                         string str = name;
84                         if (nested != null) {
85                                 foreach (var n in nested)
86                                         str += "+" + n;
87                         }
88
89                         if (generic_params != null) {
90                                 str += "[";
91                                 for (int i = 0; i < generic_params.Count; ++i) {
92                                         if (i > 0)
93                                                 str += ", ";
94                                         if (generic_params [i].assembly_name != null)
95                                                 str += "[" + generic_params [i] + "]";
96                                         else
97                                                 str += generic_params [i];
98                                 }
99                                 str += "]";
100                         }
101
102                         if (array_spec != null) {
103                                 foreach (var ar in array_spec)
104                                         str += ar;
105                         }
106
107                         for (int i = 0; i < pointer_level; ++i)
108                                 str += "*";
109
110                         if (is_byref)
111                                 str += "&";
112
113                         if (assembly_name != null)
114                                 str += ", " + assembly_name;
115
116                         return str;
117                 }
118 #endif
119
120                 internal static TypeSpec Parse (string typeName)
121                 {
122                         int pos = 0;
123                         if (typeName == null)
124                                 throw new ArgumentNullException ("typeName");
125
126                         TypeSpec res = Parse (typeName, ref pos, false, false);
127                         if (pos < typeName.Length)
128                                 throw new ArgumentException ("Count not parse the whole type name", "typeName");
129                         return res;
130                 }
131
132                 internal Type Resolve (Func<AssemblyName,Assembly> assemblyResolver, Func<Assembly,string,bool,Type> typeResolver, bool throwOnError, bool ignoreCase)
133                 {
134                         Assembly asm = null;
135                         if (assemblyResolver == null && typeResolver == null)
136                                 return Type.GetType (name, throwOnError, ignoreCase);
137
138                         if (assembly_name != null) {
139                                 if (assemblyResolver != null)
140                                         asm = assemblyResolver (new AssemblyName (assembly_name));
141                                 else
142                                         asm = Assembly.Load (assembly_name);
143
144                                 if (asm == null) {
145                                         if (throwOnError)
146                                                 throw new FileNotFoundException ("Could not resolve assembly '" + assembly_name + "'");
147                                         return null;
148                                 }
149                         }
150
151                         Type type = null;
152                         if (typeResolver != null)
153                                 type = typeResolver (asm, name, ignoreCase);
154                         else
155                                 type = asm.GetType (name, false, ignoreCase);
156                         if (type == null) {
157                                 if (throwOnError)
158                                         throw new TypeLoadException ("Could not resolve type '" + name + "'");
159                                 return null;
160                         }
161
162                         if (nested != null) {
163                                 foreach (var n in nested) {
164                                         var tmp = type.GetNestedType (n, BindingFlags.Public | BindingFlags.NonPublic);
165                                         if (tmp == null) {
166                                                 if (throwOnError)
167                                                         throw new TypeLoadException ("Could not resolve type '" + n + "'");
168                                                 return null;
169                                         }
170                                         type = tmp;
171                                 }
172                         }
173
174                         if (generic_params != null) {
175                                 Type[] args = new Type [generic_params.Count];
176                                 for (int i = 0; i < args.Length; ++i) {
177                                         var tmp = generic_params [i].Resolve (assemblyResolver, typeResolver, throwOnError, ignoreCase);
178                                         if (tmp == null) {
179                                                 if (throwOnError)
180                                                         throw new TypeLoadException ("Could not resolve type '" + generic_params [i].name + "'");
181                                                 return null;
182                                         }
183                                         args [i] = tmp;
184                                 }
185                                 type = type.MakeGenericType (args);
186                         }
187
188                         if (array_spec != null) {
189                                 foreach (var arr in array_spec)
190                                         type = arr.Resolve (type);
191                         }
192
193                         for (int i = 0; i < pointer_level; ++i)
194                                 type = type.MakePointerType ();
195
196                         if (is_byref)
197                                 type = type.MakeByRefType ();
198
199                         return type;
200                 }
201
202                 void AddName (string type_name)
203                 {
204                         if (name == null) {
205                                 name = type_name;
206                         } else {
207                                 if (nested == null)
208                                         nested = new List <string> ();
209                                 nested.Add (type_name);
210                         }
211                 }
212
213                 void AddArray (ArraySpec array)
214                 {
215                         if (array_spec == null)
216                                 array_spec = new List<ArraySpec> ();
217                         array_spec.Add (array);
218                 }
219
220                 static void SkipSpace (string name, ref int pos)
221                 {
222                         int p = pos;
223                         while (p < name.Length && Char.IsWhiteSpace (name [p]))
224                                 ++p;
225                         pos = p;
226                 }
227
228                 static TypeSpec Parse (string name, ref int p, bool is_recurse, bool allow_aqn)
229                 {
230                         int pos = p;
231                         int name_start;
232                         bool in_modifiers = false;
233                         TypeSpec data = new TypeSpec ();
234
235                         SkipSpace (name, ref pos);
236
237                         name_start = pos;
238
239                         for (; pos < name.Length; ++pos) {
240                                 switch (name [pos]) {
241                                 case '+':
242                                         data.AddName (name.Substring (name_start, pos - name_start));
243                                         name_start = pos + 1;
244                                         break;
245                                 case ',':
246                                 case ']':
247                                         data.AddName (name.Substring (name_start, pos - name_start));
248                                         name_start = pos + 1;
249                                         in_modifiers = true;
250                                         if (is_recurse && !allow_aqn) {
251                                                 p = pos;
252                                                 return data;
253                                         }
254                                         break;
255                                 case '&':
256                                 case '*':
257                                 case '[':
258                                         if (name [pos] != '[' && is_recurse)
259                                                 throw new ArgumentException ("Generic argument can't be byref or pointer type", "typeName");
260                                         data.AddName (name.Substring (name_start, pos - name_start));
261                                         name_start = pos + 1;
262                                         in_modifiers = true;
263                                         break;
264                                 }
265                                 if (in_modifiers)
266                                         break;
267                         }
268
269                         if (name_start < pos)
270                                 data.AddName (name.Substring (name_start, pos - name_start));           
271
272                         if (in_modifiers) {
273                                 for (; pos < name.Length; ++pos) {
274
275                                         switch (name [pos]) {
276                                         case '&':
277                                                 if (data.is_byref)
278                                                         throw new ArgumentException ("Can't have a byref of a byref", "typeName");
279
280                                                 data.is_byref = true;
281                                                 break;
282                                         case '*':
283                                                 if (data.is_byref)
284                                                         throw new ArgumentException ("Can't have a pointer to a byref type", "typeName");
285                                                 ++data.pointer_level;
286                                                 break;
287                                         case ',':
288                                                 if (is_recurse) {
289                                                         int end = pos;
290                                                         while (end < name.Length && name [end] != ']')
291                                                                 ++end;
292                                                         if (end >= name.Length)
293                                                                 throw new ArgumentException ("Unmatched ']' while parsing generic argument assembly name");
294                                                         data.assembly_name = name.Substring (pos + 1, end - pos - 1).Trim ();
295                                                         p = end + 1;
296                                                         return data;                                            
297                                                 }
298                                                 data.assembly_name = name.Substring (pos + 1).Trim ();
299                                                 pos = name.Length;
300                                                 break;
301                                         case '[':
302                                                 if (data.is_byref)
303                                                         throw new ArgumentException ("Byref qualifier must be the last one of a type", "typeName");
304                                                 ++pos;
305                                                 if (pos >= name.Length)
306                                                                 throw new ArgumentException ("Invalid array/generic spec", "typeName");
307                                                 SkipSpace (name, ref pos);
308
309                                                 if (name [pos] != ',' && name [pos] != '*' && name [pos]  != ']') {//generic args
310                                                         List<TypeSpec> args = new List <TypeSpec> ();
311                                                         if (data.IsArray)
312                                                                 throw new ArgumentException ("generic args after array spec", "typeName");
313
314                                                         while (pos < name.Length) {
315                                                                 SkipSpace (name, ref pos);
316                                                                 bool aqn = name [pos] == '[';
317                                                                 if (aqn)
318                                                                         ++pos; //skip '[' to the start of the type
319                                                                 args.Add (Parse (name, ref pos, true, aqn));
320                                                                 if (pos >= name.Length)
321                                                                         throw new ArgumentException ("Invalid generic arguments spec", "typeName");
322
323                                                                 if (name [pos] == ']')
324                                                                                 break;
325                                                                 if (name [pos] == ',')
326                                                                         ++pos; // skip ',' to the start of the next arg
327                                                                 else
328                                                                         throw new ArgumentException ("Invalid generic arguments separator " + name [pos], "typeName");
329                         
330                                                         }
331                                                         if (pos >= name.Length || name [pos] != ']')
332                                                                 throw new ArgumentException ("Error parsing generic params spec", "typeName");
333                                                         data.generic_params = args;
334                                                 } else { //array spec
335                                                         int dimensions = 1;
336                                                         bool bound = false;
337                                                         while (pos < name.Length && name [pos] != ']') {
338                                                                 if (name [pos] == '*') {
339                                                                         if (bound)
340                                                                                 throw new ArgumentException ("Array spec cannot have 2 bound dimensions", "typeName");
341                                                                         bound = true;
342                                                                 }
343                                                                 else if (name [pos] != ',')
344                                                                         throw new ArgumentException ("Invalid character in array spec " + name [pos], "typeName");
345                                                                 else
346                                                                         ++dimensions;
347
348                                                                 ++pos;
349                                                                 SkipSpace (name, ref pos);
350                                                         }
351                                                         if (name [pos] != ']')
352                                                                 throw new ArgumentException ("Error parsing array spec", "typeName");
353                                                         if (dimensions > 1 && bound)
354                                                                 throw new ArgumentException ("Invalid array spec, multi-dimensional array cannot be bound", "typeName");
355                                                         data.AddArray (new ArraySpec (dimensions, bound));
356                                                 }
357
358                                                 break;
359                                         case ']':
360                                                 if (is_recurse) {
361                                                         p = pos + 1;
362                                                         return data;
363                                                 }
364                                                 throw new ArgumentException ("Unmatched ']'", "typeName");
365                                         default:
366                                                 throw new ArgumentException ("Bad type def, can't handle '" + name [pos]+"'" + " at " + pos, "typeName");
367                                         }
368                                 }
369                         }
370
371                         p = pos;
372                         return data;
373                 }
374         }
375 }
376