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.
4 
5 using System;
6 using System.Reflection;
7 using System.Diagnostics;
8 using System.Reflection.Runtime.TypeInfos;
9 
10 using Internal.Reflection.Core;
11 
12 namespace System.Reflection.Runtime.General
13 {
14     internal static class Assignability
15     {
IsAssignableFrom(Type toTypeInfo, Type fromTypeInfo)16         public static bool IsAssignableFrom(Type toTypeInfo, Type fromTypeInfo)
17         {
18             if (toTypeInfo == null)
19                 throw new NullReferenceException();
20             if (fromTypeInfo == null)
21                 return false;   // It would be more appropriate to throw ArgumentNullException here, but returning "false" is the desktop-compat behavior.
22 
23             if (fromTypeInfo.Equals(toTypeInfo))
24                 return true;
25 
26             if (toTypeInfo.IsGenericTypeDefinition)
27             {
28                 // Asking whether something can cast to a generic type definition is arguably meaningless. The desktop CLR Reflection layer converts all
29                 // generic type definitions to generic type instantiations closed over the formal generic type parameters. The .NET Native framework
30                 // keeps the two separate. Fortunately, under either interpretation, returning "false" unless the two types are identical is still a
31                 // defensible behavior. To avoid having the rest of the code deal with the differing interpretations, we'll short-circuit this now.
32                 return false;
33             }
34 
35             if (fromTypeInfo.IsGenericTypeDefinition)
36             {
37                 // The desktop CLR Reflection layer converts all generic type definitions to generic type instantiations closed over the formal
38                 // generic type parameters. The .NET Native framework keeps the two separate. For the purpose of IsAssignableFrom(),
39                 // it makes sense to unify the two for the sake of backward compat. We'll just make the transform here so that the rest of code
40                 // doesn't need to know about this quirk.
41                 fromTypeInfo = fromTypeInfo.GetGenericTypeDefinition().MakeGenericType(fromTypeInfo.GetGenericTypeParameters());
42             }
43 
44             if (fromTypeInfo.CanCastTo(toTypeInfo))
45                 return true;
46 
47             // Desktop compat: IsAssignableFrom() considers T as assignable to Nullable<T> (but does not check if T is a generic parameter.)
48             if (!fromTypeInfo.IsGenericParameter)
49             {
50                 Type nullableUnderlyingType = Nullable.GetUnderlyingType(toTypeInfo);
51                 if (nullableUnderlyingType != null && nullableUnderlyingType.Equals(fromTypeInfo))
52                     return true;
53             }
54             return false;
55         }
56 
CanCastTo(this Type fromTypeInfo, Type toTypeInfo)57         private static bool CanCastTo(this Type fromTypeInfo, Type toTypeInfo)
58         {
59             if (fromTypeInfo.Equals(toTypeInfo))
60                 return true;
61 
62             if (fromTypeInfo.IsArray)
63             {
64                 if (toTypeInfo.IsInterface)
65                     return fromTypeInfo.CanCastArrayToInterface(toTypeInfo);
66 
67                 if (fromTypeInfo.IsSubclassOf(toTypeInfo))
68                     return true;  // T[] is castable to Array or Object.
69 
70                 if (!toTypeInfo.IsArray)
71                     return false;
72 
73                 int rank = fromTypeInfo.GetArrayRank();
74                 if (rank != toTypeInfo.GetArrayRank())
75                     return false;
76 
77                 bool fromTypeIsSzArray = fromTypeInfo.IsSZArray;
78                 bool toTypeIsSzArray = toTypeInfo.IsSZArray;
79                 if (fromTypeIsSzArray != toTypeIsSzArray)
80                 {
81                     // T[] is assignable to T[*] but not vice-versa.
82                     if (!(rank == 1 && !toTypeIsSzArray))
83                     {
84                         return false; // T[*] is not castable to T[]
85                     }
86                 }
87 
88                 Type toElementTypeInfo = toTypeInfo.GetElementType();
89                 Type fromElementTypeInfo = fromTypeInfo.GetElementType();
90                 return fromElementTypeInfo.IsElementTypeCompatibleWith(toElementTypeInfo);
91             }
92 
93             if (fromTypeInfo.IsByRef)
94             {
95                 if (!toTypeInfo.IsByRef)
96                     return false;
97 
98                 Type toElementTypeInfo = toTypeInfo.GetElementType();
99                 Type fromElementTypeInfo = fromTypeInfo.GetElementType();
100                 return fromElementTypeInfo.IsElementTypeCompatibleWith(toElementTypeInfo);
101             }
102 
103             if (fromTypeInfo.IsPointer)
104             {
105                 if (toTypeInfo.Equals(CommonRuntimeTypes.Object))
106                     return true;  // T* is castable to Object.
107 
108                 if (toTypeInfo.Equals(CommonRuntimeTypes.UIntPtr))
109                     return true;  // T* is castable to UIntPtr (but not IntPtr)
110 
111                 if (!toTypeInfo.IsPointer)
112                     return false;
113 
114                 Type toElementTypeInfo = toTypeInfo.GetElementType();
115                 Type fromElementTypeInfo = fromTypeInfo.GetElementType();
116                 return fromElementTypeInfo.IsElementTypeCompatibleWith(toElementTypeInfo);
117             }
118 
119             if (fromTypeInfo.IsGenericParameter)
120             {
121                 //
122                 // A generic parameter can be cast to any of its constraints, or object, if none are specified, or ValueType if the "struct" constraint is
123                 // specified.
124                 //
125                 // This has to be coded as its own case as TypeInfo.BaseType on a generic parameter doesn't always return what you'd expect.
126                 //
127                 if (toTypeInfo.Equals(CommonRuntimeTypes.Object))
128                     return true;
129 
130                 if (toTypeInfo.Equals(CommonRuntimeTypes.ValueType))
131                 {
132                     GenericParameterAttributes attributes = fromTypeInfo.GenericParameterAttributes;
133                     if ((attributes & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0)
134                         return true;
135                 }
136 
137                 foreach (Type constraintType in fromTypeInfo.GetGenericParameterConstraints())
138                 {
139                     if (constraintType.CanCastTo(toTypeInfo))
140                         return true;
141                 }
142 
143                 return false;
144             }
145 
146             if (toTypeInfo.IsArray || toTypeInfo.IsByRef || toTypeInfo.IsPointer || toTypeInfo.IsGenericParameter)
147                 return false;
148 
149             if (fromTypeInfo.MatchesWithVariance(toTypeInfo))
150                 return true;
151 
152             if (toTypeInfo.IsInterface)
153             {
154                 foreach (Type ifc in fromTypeInfo.GetInterfaces())
155                 {
156                     if (ifc.MatchesWithVariance(toTypeInfo))
157                         return true;
158                 }
159                 return false;
160             }
161             else
162             {
163                 // Interfaces are always castable to System.Object. The code below will not catch this as interfaces report their BaseType as null.
164                 if (toTypeInfo.Equals(CommonRuntimeTypes.Object) && fromTypeInfo.IsInterface)
165                     return true;
166 
167                 Type walk = fromTypeInfo;
168                 for (;;)
169                 {
170                     Type baseType = walk.BaseType;
171                     if (baseType == null)
172                         return false;
173                     walk = baseType;
174                     if (walk.MatchesWithVariance(toTypeInfo))
175                         return true;
176                 }
177             }
178         }
179 
180         //
181         // Check a base type or implemented interface type for equivalence (taking into account variance for generic instantiations.)
182         // Does not check ancestors recursively.
183         //
MatchesWithVariance(this Type fromTypeInfo, Type toTypeInfo)184         private static bool MatchesWithVariance(this Type fromTypeInfo, Type toTypeInfo)
185         {
186             Debug.Assert(!(fromTypeInfo.IsArray || fromTypeInfo.IsByRef || fromTypeInfo.IsPointer || fromTypeInfo.IsGenericParameter));
187             Debug.Assert(!(toTypeInfo.IsArray || toTypeInfo.IsByRef || toTypeInfo.IsPointer || toTypeInfo.IsGenericParameter));
188 
189             if (fromTypeInfo.Equals(toTypeInfo))
190                 return true;
191 
192             if (!(fromTypeInfo.IsConstructedGenericType && toTypeInfo.IsConstructedGenericType))
193                 return false;
194 
195             Type genericTypeDefinition = fromTypeInfo.GetGenericTypeDefinition();
196             if (!genericTypeDefinition.Equals(toTypeInfo.GetGenericTypeDefinition()))
197                 return false;
198 
199             Type[] fromTypeArguments = fromTypeInfo.GenericTypeArguments;
200             Type[] toTypeArguments = toTypeInfo.GenericTypeArguments;
201             Type[] genericTypeParameters = genericTypeDefinition.GetGenericTypeParameters();
202             for (int i = 0; i < genericTypeParameters.Length; i++)
203             {
204                 Type fromTypeArgumentInfo = fromTypeArguments[i];
205                 Type toTypeArgumentInfo = toTypeArguments[i];
206 
207                 GenericParameterAttributes attributes = genericTypeParameters[i].GenericParameterAttributes;
208                 switch (attributes & GenericParameterAttributes.VarianceMask)
209                 {
210                     case GenericParameterAttributes.Covariant:
211                         if (!(fromTypeArgumentInfo.IsGcReferenceTypeAndCastableTo(toTypeArgumentInfo)))
212                             return false;
213                         break;
214 
215                     case GenericParameterAttributes.Contravariant:
216                         if (!(toTypeArgumentInfo.IsGcReferenceTypeAndCastableTo(fromTypeArgumentInfo)))
217                             return false;
218                         break;
219 
220                     case GenericParameterAttributes.None:
221                         if (!(fromTypeArgumentInfo.Equals(toTypeArgumentInfo)))
222                             return false;
223                         break;
224 
225                     default:
226                         throw new BadImageFormatException();  // Unexpected variance value in metadata.
227                 }
228             }
229             return true;
230         }
231 
232         //
233         // A[] can cast to B[] if one of the following are true:
234         //
235         //    A can cast to B under variance rules.
236         //
237         //    A and B are both integers or enums and have the same reduced type (i.e. represent the same-sized integer, ignoring signed/unsigned differences.)
238         //        "char" is not interchangable with short/ushort. "bool" is not interchangable with byte/sbyte.
239         //
240         // For desktop compat, A& and A* follow the same rules.
241         //
IsElementTypeCompatibleWith(this Type fromTypeInfo, Type toTypeInfo)242         private static bool IsElementTypeCompatibleWith(this Type fromTypeInfo, Type toTypeInfo)
243         {
244             if (fromTypeInfo.IsGcReferenceTypeAndCastableTo(toTypeInfo))
245                 return true;
246 
247             Type reducedFromType = fromTypeInfo.ReducedType();
248             Type reducedToType = toTypeInfo.ReducedType();
249             if (reducedFromType.Equals(reducedToType))
250                 return true;
251 
252             return false;
253         }
254 
ReducedType(this Type t)255         private static Type ReducedType(this Type t)
256         {
257             if (t.IsEnum)
258                 t = Enum.GetUnderlyingType(t);
259 
260             if (t.Equals(CommonRuntimeTypes.Byte))
261                 return CommonRuntimeTypes.SByte;
262 
263             if (t.Equals(CommonRuntimeTypes.UInt16))
264                 return CommonRuntimeTypes.Int16;
265 
266             if (t.Equals(CommonRuntimeTypes.UInt32))
267                 return CommonRuntimeTypes.Int32;
268 
269             if (t.Equals(CommonRuntimeTypes.UInt64))
270                 return CommonRuntimeTypes.Int64;
271 
272             if (t.Equals(CommonRuntimeTypes.UIntPtr) || t.Equals(CommonRuntimeTypes.IntPtr))
273             {
274 #if WIN64
275                 return CommonRuntimeTypes.Int64;
276 #else
277                 return CommonRuntimeTypes.Int32;
278 #endif
279             }
280 
281             return t;
282         }
283 
284         //
285         // Contra/CoVariance.
286         //
287         // IEnumerable<D> can cast to IEnumerable<B> if D can cast to B and if there's no possibility that D is a value type.
288         //
IsGcReferenceTypeAndCastableTo(this Type fromTypeInfo, Type toTypeInfo)289         private static bool IsGcReferenceTypeAndCastableTo(this Type fromTypeInfo, Type toTypeInfo)
290         {
291             if (fromTypeInfo.Equals(toTypeInfo))
292                 return true;
293 
294             if (fromTypeInfo.ProvablyAGcReferenceType())
295                 return fromTypeInfo.CanCastTo(toTypeInfo);
296 
297             return false;
298         }
299 
300         //
301         // A true result indicates that a type can never be a value type. This is important when testing variance-compatibility.
302         //
ProvablyAGcReferenceType(this Type t)303         private static bool ProvablyAGcReferenceType(this Type t)
304         {
305             if (t.IsGenericParameter)
306             {
307                 GenericParameterAttributes attributes = t.GenericParameterAttributes;
308                 if ((attributes & GenericParameterAttributes.ReferenceTypeConstraint) != 0)
309                     return true;   // generic parameter with a "class" constraint.
310             }
311 
312             return t.ProvablyAGcReferenceTypeHelper();
313         }
314 
ProvablyAGcReferenceTypeHelper(this Type t)315         private static bool ProvablyAGcReferenceTypeHelper(this Type t)
316         {
317             if (t.IsArray)
318                 return true;
319 
320             if (t.IsByRef || t.IsPointer)
321                 return false;
322 
323             if (t.IsGenericParameter)
324             {
325                 // We intentionally do not check for a "class" constraint on generic parameter ancestors.
326                 // That's because this property does not propagate up the constraining hierarchy.
327                 // (e.g. "class A<S, T> where S : T, where T : class" does not guarantee that S is a class.)
328 
329                 foreach (Type constraintType in t.GetGenericParameterConstraints())
330                 {
331                     if (constraintType.ProvablyAGcReferenceTypeHelper())
332                         return true;
333                 }
334                 return false;
335             }
336 
337             return t.IsClass && !t.Equals(CommonRuntimeTypes.Object) && !t.Equals(CommonRuntimeTypes.ValueType) && !t.Equals(CommonRuntimeTypes.Enum);
338         }
339 
340         //
341         // T[] casts to IList<T>. This could be handled by the normal ancestor-walking code
342         // but for one complication: T[] also casts to IList<U> if T[] casts to U[].
343         //
CanCastArrayToInterface(this Type fromTypeInfo, Type toTypeInfo)344         private static bool CanCastArrayToInterface(this Type fromTypeInfo, Type toTypeInfo)
345         {
346             Debug.Assert(fromTypeInfo.IsArray);
347             Debug.Assert(toTypeInfo.IsInterface);
348 
349             if (toTypeInfo.IsConstructedGenericType)
350             {
351                 Type[] toTypeGenericTypeArguments = toTypeInfo.GenericTypeArguments;
352                 if (toTypeGenericTypeArguments.Length != 1)
353                     return false;
354                 Type toElementTypeInfo = toTypeGenericTypeArguments[0];
355 
356                 Type toTypeGenericTypeDefinition = toTypeInfo.GetGenericTypeDefinition();
357                 Type fromElementTypeInfo = fromTypeInfo.GetElementType();
358                 foreach (Type ifc in fromTypeInfo.GetInterfaces())
359                 {
360                     if (ifc.IsConstructedGenericType)
361                     {
362                         Type ifcGenericTypeDefinition = ifc.GetGenericTypeDefinition();
363                         if (ifcGenericTypeDefinition.Equals(toTypeGenericTypeDefinition))
364                         {
365                             if (fromElementTypeInfo.IsElementTypeCompatibleWith(toElementTypeInfo))
366                                 return true;
367                         }
368                     }
369                 }
370                 return false;
371             }
372             else
373             {
374                 foreach (Type ifc in fromTypeInfo.GetInterfaces())
375                 {
376                     if (ifc.Equals(toTypeInfo))
377                         return true;
378                 }
379                 return false;
380             }
381         }
382     }
383 }
384