Merge pull request #3187 from akoeplinger/mstestsuite-on-prs
[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 interface ModifierSpec {
36                 Type Resolve (Type type);
37                 Text.StringBuilder Append (Text.StringBuilder sb);
38         }
39         internal class ArraySpec : ModifierSpec
40         {
41                 // dimensions == 1 and bound, or dimensions > 1 and !bound
42                 int dimensions;
43                 bool bound;
44
45                 internal ArraySpec (int dimensions, bool bound)
46                 {
47                         this.dimensions = dimensions;
48                         this.bound = bound;
49                 }
50
51                 public Type Resolve (Type type)
52                 {
53                         if (bound)
54                                 return type.MakeArrayType (1);
55                         else if (dimensions == 1)
56                                 return type.MakeArrayType ();
57                         return type.MakeArrayType (dimensions);
58                 }
59
60                 public Text.StringBuilder Append (Text.StringBuilder sb)
61                 {
62                         if (bound)
63                                 return sb.Append ("[*]");
64                         return sb.Append ('[')
65                                 .Append (',', dimensions - 1)
66                                 .Append (']');
67                         
68                 }
69                 public override string ToString ()
70                 {
71                         return Append (new Text.StringBuilder ()).ToString ();
72                 }
73         }
74
75         internal class PointerSpec : ModifierSpec
76         {
77                 int pointer_level;
78
79                 internal PointerSpec (int pointer_level) {
80                         this.pointer_level = pointer_level;
81                 }
82
83                 public Type Resolve (Type type) {
84                         for (int i = 0; i < pointer_level; ++i)
85                                 type = type.MakePointerType ();
86                         return type;
87                 }
88
89                 public Text.StringBuilder Append (Text.StringBuilder sb)
90                 {
91                         return sb.Append ('*', pointer_level);
92                 }
93                 
94                 public override string ToString () {
95                         return Append (new Text.StringBuilder ()).ToString ();
96                 }
97
98         }
99
100         internal class TypeSpec
101         {
102                 TypeIdentifier name;
103                 string assembly_name;
104                 List<TypeIdentifier> nested;
105                 List<TypeSpec> generic_params;
106                 List<ModifierSpec> modifier_spec;
107                 bool is_byref;
108
109                 string display_fullname; // cache
110
111                 internal bool HasModifiers {
112                         get { return modifier_spec != null; }
113                 }
114
115                 internal bool IsNested {
116                         get { return nested != null && nested.Count > 0; }
117                 }
118
119                 internal bool IsByRef {
120                         get { return is_byref; }
121                 }
122
123                 internal TypeName Name {
124                         get { return name; }
125                 }
126
127                 internal IEnumerable<TypeName> Nested {
128                         get {
129                                 if (nested != null)
130                                         return nested;
131                                 else
132                                         return EmptyArray<TypeName>.Value;
133                         }
134                 }
135
136                 internal IEnumerable<ModifierSpec> Modifiers {
137                         get {
138                                 if (modifier_spec != null)
139                                         return modifier_spec;
140                                 else
141                                         return EmptyArray<ModifierSpec>.Value;
142                         }
143                 }
144
145                 [Flags]
146                 internal enum DisplayNameFormat {
147                         Default = 0x0,
148                         WANT_ASSEMBLY = 0x1,
149                         NO_MODIFIERS = 0x2,
150                 }
151 #if DEBUG
152                 public override string ToString () {
153                         return GetDisplayFullName (DisplayNameFormat.WANT_ASSEMBLY);
154                 }
155 #endif
156
157                 string GetDisplayFullName (DisplayNameFormat flags)
158                 {
159                         bool wantAssembly = (flags & DisplayNameFormat.WANT_ASSEMBLY) != 0;
160                         bool wantModifiers = (flags & DisplayNameFormat.NO_MODIFIERS) == 0;
161                         var sb = new Text.StringBuilder(name.DisplayName);
162                         if (nested != null) {
163                                 foreach (var n in nested)
164                                         sb.Append ('+').Append (n.DisplayName);
165                         }
166
167                         if (generic_params != null) {
168                                 sb.Append ('[');
169                                 for (int i = 0; i < generic_params.Count; ++i) {
170                                         if (i > 0)
171                                                 sb.Append (", ");
172                                         if (generic_params [i].assembly_name != null)
173                                                 sb.Append ('[').Append (generic_params [i].DisplayFullName).Append (']');
174                                         else
175                                                 sb.Append (generic_params [i].DisplayFullName);
176                                 }
177                                 sb.Append (']');
178                         }
179
180                         if (wantModifiers)
181                                 GetModifierString (sb);
182
183                         if (assembly_name != null && wantAssembly)
184                                 sb.Append (", ").Append (assembly_name);
185
186                         return sb.ToString();
187                 }
188
189                 internal string ModifierString ()
190                 {
191                         return GetModifierString (new Text.StringBuilder ()).ToString ();
192                 }
193
194                 private Text.StringBuilder GetModifierString (Text.StringBuilder sb)
195                 {
196                         if (modifier_spec != null) {
197                                 foreach (var md in modifier_spec)
198                                         md.Append (sb);
199                         }
200
201                         if (is_byref)
202                                 sb.Append ('&');
203
204                         return sb;
205                 }
206
207                 internal string DisplayFullName {
208                         get {
209                                 if (display_fullname == null)
210                                         display_fullname = GetDisplayFullName (DisplayNameFormat.Default);
211                                 return display_fullname;
212                         }
213                 }
214
215                 internal static TypeSpec Parse (string typeName)
216                 {
217                         int pos = 0;
218                         if (typeName == null)
219                                 throw new ArgumentNullException ("typeName");
220
221                         TypeSpec res = Parse (typeName, ref pos, false, true);
222                         if (pos < typeName.Length)
223                                 throw new ArgumentException ("Count not parse the whole type name", "typeName");
224                         return res;
225                 }
226
227                 internal static string EscapeDisplayName(string internalName)
228                 {
229                         // initial capacity = length of internalName.
230                         // Maybe we won't have to escape anything.
231                         var res = new Text.StringBuilder (internalName.Length);
232                         foreach (char c in internalName)
233                         {
234                                 switch (c) {
235                                         case '+':
236                                         case ',':
237                                         case '[':
238                                         case ']':
239                                         case '*':
240                                         case '&':
241                                         case '\\':
242                                                 res.Append ('\\').Append (c);
243                                                 break;
244                                         default:
245                                                 res.Append (c);
246                                                 break;
247                                 }
248                         }
249                         return res.ToString ();
250                 }
251
252                 internal static string UnescapeInternalName(string displayName)
253                 {
254                         var res = new Text.StringBuilder (displayName.Length);
255                         for (int i = 0; i < displayName.Length; ++i)
256                         {
257                                 char c = displayName[i];
258                                 if (c == '\\')
259                                         if (++i < displayName.Length)
260                                                 c = displayName[i];
261                                 res.Append (c);
262                         }
263                         return res.ToString ();
264                 }
265
266                 internal static bool NeedsEscaping (string internalName)
267                 {
268                         foreach (char c in internalName)
269                         {
270                                 switch (c) {
271                                         case ',':
272                                         case '+':
273                                         case '*':
274                                         case '&':
275                                         case '[':
276                                         case ']':
277                                         case '\\':
278                                                 return true;
279                                         default:
280                                                 break;
281                                 }
282                         }
283                         return false;
284                 }
285
286                 internal Type Resolve (Func<AssemblyName,Assembly> assemblyResolver, Func<Assembly,string,bool,Type> typeResolver, bool throwOnError, bool ignoreCase)
287                 {
288                         Assembly asm = null;
289                         if (assemblyResolver == null && typeResolver == null)
290                                 return Type.GetType (DisplayFullName, throwOnError, ignoreCase);
291
292                         if (assembly_name != null) {
293                                 if (assemblyResolver != null)
294                                         asm = assemblyResolver (new AssemblyName (assembly_name));
295                                 else
296                                         asm = Assembly.Load (assembly_name);
297
298                                 if (asm == null) {
299                                         if (throwOnError)
300                                                 throw new FileNotFoundException ("Could not resolve assembly '" + assembly_name + "'");
301                                         return null;
302                                 }
303                         }
304
305                         Type type = null;
306                         if (typeResolver != null)
307                                 type = typeResolver (asm, name.DisplayName, ignoreCase);
308                         else
309                                 type = asm.GetType (name.DisplayName, false, ignoreCase);
310                         if (type == null) {
311                                 if (throwOnError)
312                                         throw new TypeLoadException ("Could not resolve type '" + name + "'");
313                                 return null;
314                         }
315
316                         if (nested != null) {
317                                 foreach (var n in nested) {
318                                         var tmp = type.GetNestedType (n.DisplayName, BindingFlags.Public | BindingFlags.NonPublic);
319                                         if (tmp == null) {
320                                                 if (throwOnError)
321                                                         throw new TypeLoadException ("Could not resolve type '" + n + "'");
322                                                 return null;
323                                         }
324                                         type = tmp;
325                                 }
326                         }
327
328                         if (generic_params != null) {
329                                 Type[] args = new Type [generic_params.Count];
330                                 for (int i = 0; i < args.Length; ++i) {
331                                         var tmp = generic_params [i].Resolve (assemblyResolver, typeResolver, throwOnError, ignoreCase);
332                                         if (tmp == null) {
333                                                 if (throwOnError)
334                                                         throw new TypeLoadException ("Could not resolve type '" + generic_params [i].name + "'");
335                                                 return null;
336                                         }
337                                         args [i] = tmp;
338                                 }
339                                 type = type.MakeGenericType (args);
340                         }
341
342                         if (modifier_spec != null) {
343                                 foreach (var md in modifier_spec)
344                                         type = md.Resolve (type);
345                         }
346
347                         if (is_byref)
348                                 type = type.MakeByRefType ();
349
350                         return type;
351                 }
352
353                 void AddName (string type_name)
354                 {
355                         if (name == null) {
356                                 name = ParsedTypeIdentifier(type_name);
357                         } else {
358                                 if (nested == null)
359                                         nested = new List <TypeIdentifier> ();
360                                 nested.Add (ParsedTypeIdentifier(type_name));
361                         }
362                 }
363
364                 void AddModifier (ModifierSpec md)
365                 {
366                         if (modifier_spec == null)
367                                 modifier_spec = new List<ModifierSpec> ();
368                         modifier_spec.Add (md);
369                 }
370
371                 static void SkipSpace (string name, ref int pos)
372                 {
373                         int p = pos;
374                         while (p < name.Length && Char.IsWhiteSpace (name [p]))
375                                 ++p;
376                         pos = p;
377                 }
378
379                 static void BoundCheck (int idx, string s)
380                 {
381                         if (idx >= s.Length)
382                                 throw new ArgumentException ("Invalid generic arguments spec", "typeName");
383                 }
384
385                 static TypeIdentifier ParsedTypeIdentifier (string displayName)
386                 {
387                         return TypeIdentifiers.FromDisplay(displayName);
388                 }
389
390                 static TypeSpec Parse (string name, ref int p, bool is_recurse, bool allow_aqn)
391                 {
392                         // Invariants:
393                         //  - On exit p, is updated to pos the current unconsumed character.
394                         //
395                         //  - The callee peeks at but does not consume delimiters following
396                         //    recurisve parse (so for a recursive call like the args of "Foo[P,Q]"
397                         //    we'll return with p either on ',' or on ']'.  If the name was aqn'd
398                         //    "Foo[[P,assmblystuff],Q]" on return p with be on the ']' just
399                         //    after the "assmblystuff")
400                         //
401                         //  - If allow_aqn is True, assembly qualification is optional.
402                         //    If allow_aqn is False, assembly qualification is prohibited.
403                         int pos = p;
404                         int name_start;
405                         bool in_modifiers = false;
406                         TypeSpec data = new TypeSpec ();
407
408                         SkipSpace (name, ref pos);
409
410                         name_start = pos;
411
412                         for (; pos < name.Length; ++pos) {
413                                 switch (name [pos]) {
414                                 case '+':
415                                         data.AddName (name.Substring (name_start, pos - name_start));
416                                         name_start = pos + 1;
417                                         break;
418                                 case ',':
419                                 case ']':
420                                         data.AddName (name.Substring (name_start, pos - name_start));
421                                         name_start = pos + 1;
422                                         in_modifiers = true;
423                                         if (is_recurse && !allow_aqn) {
424                                                 p = pos;
425                                                 return data;
426                                         }
427                                         break;
428                                 case '&':
429                                 case '*':
430                                 case '[':
431                                         if (name [pos] != '[' && is_recurse)
432                                                 throw new ArgumentException ("Generic argument can't be byref or pointer type", "typeName");
433                                         data.AddName (name.Substring (name_start, pos - name_start));
434                                         name_start = pos + 1;
435                                         in_modifiers = true;
436                                         break;
437                                 case '\\':
438                                         pos++;
439                                         break;
440                                 }
441                                 if (in_modifiers)
442                                         break;
443                         }
444
445                         if (name_start < pos)
446                                 data.AddName (name.Substring (name_start, pos - name_start));           
447
448                         if (in_modifiers) {
449                                 for (; pos < name.Length; ++pos) {
450
451                                         switch (name [pos]) {
452                                         case '&':
453                                                 if (data.is_byref)
454                                                         throw new ArgumentException ("Can't have a byref of a byref", "typeName");
455
456                                                 data.is_byref = true;
457                                                 break;
458                                         case '*':
459                                                 if (data.is_byref)
460                                                         throw new ArgumentException ("Can't have a pointer to a byref type", "typeName");
461                                                 // take subsequent '*'s too
462                                                 int pointer_level = 1;
463                                                 while (pos+1 < name.Length && name[pos+1] == '*') {
464                                                         ++pos;
465                                                         ++pointer_level;
466                                                 }
467                                                 data.AddModifier (new PointerSpec(pointer_level));
468                                                 break;
469                                         case ',':
470                                                 if (is_recurse && allow_aqn) {
471                                                         int end = pos;
472                                                         while (end < name.Length && name [end] != ']')
473                                                                 ++end;
474                                                         if (end >= name.Length)
475                                                                 throw new ArgumentException ("Unmatched ']' while parsing generic argument assembly name");
476                                                         data.assembly_name = name.Substring (pos + 1, end - pos - 1).Trim ();
477                                                         p = end;
478                                                         return data;                                            
479                                                 }
480                                                 if (is_recurse) {
481                                                         p = pos;
482                                                         return data;
483                                                 }
484                                                 if (allow_aqn) {
485                                                         data.assembly_name = name.Substring (pos + 1).Trim ();
486                                                         pos = name.Length;
487                                                 }
488                                                 break;
489                                         case '[':
490                                                 if (data.is_byref)
491                                                         throw new ArgumentException ("Byref qualifier must be the last one of a type", "typeName");
492                                                 ++pos;
493                                                 if (pos >= name.Length)
494                                                                 throw new ArgumentException ("Invalid array/generic spec", "typeName");
495                                                 SkipSpace (name, ref pos);
496
497                                                 if (name [pos] != ',' && name [pos] != '*' && name [pos]  != ']') {//generic args
498                                                         List<TypeSpec> args = new List <TypeSpec> ();
499                                                         if (data.HasModifiers)
500                                                                 throw new ArgumentException ("generic args after array spec or pointer type", "typeName");
501
502                                                         while (pos < name.Length) {
503                                                                 SkipSpace (name, ref pos);
504                                                                 bool aqn = name [pos] == '[';
505                                                                 if (aqn)
506                                                                         ++pos; //skip '[' to the start of the type
507                                                                 args.Add (Parse (name, ref pos, true, aqn));
508                                                                 BoundCheck (pos, name);
509                                                                 if (aqn) {
510                                                                         if (name [pos] == ']')
511                                                                                 ++pos;
512                                                                         else
513                                                                                 throw new ArgumentException ("Unclosed assembly-qualified type name at " + name[pos], "typeName");
514                                                                         BoundCheck (pos, name);
515 }
516
517                                                                 if (name [pos] == ']')
518                                                                         break;
519                                                                 if (name [pos] == ',')
520                                                                         ++pos; // skip ',' to the start of the next arg
521                                                                 else
522                                                                         throw new ArgumentException ("Invalid generic arguments separator " + name [pos], "typeName");
523                         
524                                                         }
525                                                         if (pos >= name.Length || name [pos] != ']')
526                                                                 throw new ArgumentException ("Error parsing generic params spec", "typeName");
527                                                         data.generic_params = args;
528                                                 } else { //array spec
529                                                         int dimensions = 1;
530                                                         bool bound = false;
531                                                         while (pos < name.Length && name [pos] != ']') {
532                                                                 if (name [pos] == '*') {
533                                                                         if (bound)
534                                                                                 throw new ArgumentException ("Array spec cannot have 2 bound dimensions", "typeName");
535                                                                         bound = true;
536                                                                 }
537                                                                 else if (name [pos] != ',')
538                                                                         throw new ArgumentException ("Invalid character in array spec " + name [pos], "typeName");
539                                                                 else
540                                                                         ++dimensions;
541
542                                                                 ++pos;
543                                                                 SkipSpace (name, ref pos);
544                                                         }
545                                                         if (pos >= name.Length || name [pos] != ']')
546                                                                 throw new ArgumentException ("Error parsing array spec", "typeName");
547                                                         if (dimensions > 1 && bound)
548                                                                 throw new ArgumentException ("Invalid array spec, multi-dimensional array cannot be bound", "typeName");
549                                                         data.AddModifier (new ArraySpec (dimensions, bound));
550                                                 }
551
552                                                 break;
553                                         case ']':
554                                                 if (is_recurse) {
555                                                         p = pos;
556                                                         return data;
557                                                 }
558                                                 throw new ArgumentException ("Unmatched ']'", "typeName");
559                                         default:
560                                                 throw new ArgumentException ("Bad type def, can't handle '" + name [pos]+"'" + " at " + pos, "typeName");
561                                         }
562                                 }
563                         }
564
565                         p = pos;
566                         return data;
567                 }
568
569                 internal TypeName TypeNameWithoutModifiers ()
570                 {
571                         return new TypeSpecTypeName (this, false);
572                 }
573                 
574                 internal TypeName TypeName {
575                         get { return new TypeSpecTypeName (this, true); }
576                 }
577
578                 private class TypeSpecTypeName : TypeNames.ATypeName, TypeName {
579                         TypeSpec ts;
580                         bool want_modifiers;
581
582                         internal TypeSpecTypeName (TypeSpec ts, bool wantModifiers)
583                         {
584                                 this.ts = ts;
585                                 this.want_modifiers = wantModifiers;
586                         }
587
588                         public override string DisplayName {
589                                 get {
590                                         if (want_modifiers)
591                                                 return ts.DisplayFullName;
592                                         else
593                                                 return ts.GetDisplayFullName (DisplayNameFormat.NO_MODIFIERS);
594                                 }
595                         }
596
597                         public override TypeName NestedName (TypeIdentifier innerName)
598                         {
599                                 return TypeNames.FromDisplay(DisplayName + "+" + innerName.DisplayName);
600                         }
601                 }
602
603         }
604 }
605