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