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.Diagnostics; 6 using Microsoft.CSharp.RuntimeBinder.Syntax; 7 8 namespace Microsoft.CSharp.RuntimeBinder.Semantics 9 { 10 // Encapsulates all logic about convertibility between types. 11 // 12 // WARNING: These methods do not precisely match the spec. 13 // WARNING: For example most also return true for identity conversions, 14 // WARNING: FExpRefConv includes all Implicit and Explicit reference conversions. 15 16 internal static class CConversions 17 { 18 // WARNING: These methods do not precisely match the spec. 19 // WARNING: For example most also return true for identity conversions, 20 // WARNING: FExpRefConv includes all Implicit and Explicit reference conversions. 21 22 /*************************************************************************************************** 23 Determine whether there is an implicit reference conversion from typeSrc to typeDst. This is 24 when the source is a reference type and the destination is a base type of the source. Note 25 that typeDst.IsRefType() may still return false (when both are type parameters). 26 ***************************************************************************************************/ FImpRefConv(SymbolLoader loader, CType typeSrc, CType typeDst)27 public static bool FImpRefConv(SymbolLoader loader, CType typeSrc, CType typeDst) 28 { 29 return typeSrc.IsRefType() && loader.HasIdentityOrImplicitReferenceConversion(typeSrc, typeDst); 30 } 31 32 /*************************************************************************************************** 33 Determine whether there is an explicit or implicit reference conversion (or identity conversion) 34 from typeSrc to typeDst. This is when: 35 36 13.2.3 Explicit reference conversions 37 38 The explicit reference conversions are: 39 * From object to any reference-type. 40 * From any class-type S to any class-type T, provided S is a base class of T. 41 * From any class-type S to any interface-type T, provided S is not sealed and provided S does not implement T. 42 * From any interface-type S to any class-type T, provided T is not sealed or provided T implements S. 43 * From any interface-type S to any interface-type T, provided S is not derived from T. 44 * From an array-type S with an element type SE to an array-type T with an element type TE, provided all of the following are true: 45 o S and T differ only in element type. (In other words, S and T have the same number of dimensions.) 46 o An explicit reference conversion exists from SE to TE. 47 * From System.Array and the interfaces it implements, to any array-type. 48 * From System.Delegate and the interfaces it implements, to any delegate-type. 49 * From a one-dimensional array-type S[] to System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyList<T> and their base interfaces, provided there is an explicit reference conversion from S to T. 50 * From a generic delegate type S to generic delegate type T, provided all of the follow are true: 51 o Both types are constructed generic types of the same generic delegate type, D<X1,... Xk>.That is, 52 S is D<S1,... Sk> and T is D<T1,... Tk>. 53 o S is not compatible with or identical to T. 54 o If type parameter Xi is declared to be invariant then Si must be identical to Ti. 55 o If type parameter Xi is declared to be covariant ("out") then Si must be convertible 56 to Ti via an identify conversion, implicit reference conversion, or explicit reference conversion. 57 o If type parameter Xi is declared to be contravariant ("in") then either Si must be identical to Ti, 58 or Si and Ti must both be reference types. 59 * From System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyList<T> and their base interfaces to a one-dimensional array-type S[], provided there is an implicit or explicit reference conversion from S[] to System.Collections.Generic.IList<T> or System.Collections.Generic.IReadOnlyList<T>. This is precisely when either S and T are the same type or there is an implicit or explicit reference conversion from S to T. 60 61 For a type-parameter T that is known to be a reference type (§25.7), the following explicit reference conversions exist: 62 * From the effective base class C of T to T and from any base class of C to T. 63 * From any interface-type to T. 64 * From T to any interface-type I provided there isn’t already an implicit reference conversion from T to I. 65 * From a type-parameter U to T provided that T depends on U (§25.7). [Note: Since T is known to be a reference type, within the scope of T, the run-time type of U will always be a reference type, even if U is not known to be a reference type at compile-time. end note] 66 67 * Both src and dst are reference types and there is a builtin explicit conversion from 68 src to dst. 69 * Or src is a reference type and dst is a base type of src (in which case the conversion is 70 implicit as well). 71 * Or dst is a reference type and src is a base type of dst. 72 73 The latter two cases can happen with type variables even though the other type variable is not 74 a reference type. 75 ***************************************************************************************************/ FExpRefConv(SymbolLoader loader, CType typeSrc, CType typeDst)76 public static bool FExpRefConv(SymbolLoader loader, CType typeSrc, CType typeDst) 77 { 78 Debug.Assert(typeSrc != null); 79 Debug.Assert(typeDst != null); 80 if (typeSrc.IsRefType() && typeDst.IsRefType()) 81 { 82 // is there an implicit reference conversion in either direction? 83 // this handles the bulk of the cases ... 84 if (loader.HasIdentityOrImplicitReferenceConversion(typeSrc, typeDst) || 85 loader.HasIdentityOrImplicitReferenceConversion(typeDst, typeSrc)) 86 { 87 return true; 88 } 89 90 // For a type-parameter T that is known to be a reference type (§25.7), the following explicit reference conversions exist: 91 // • From any interface-type to T. 92 // • From T to any interface-type I provided there isn’t already an implicit reference conversion from T to I. 93 if (typeSrc.isInterfaceType() && typeDst is TypeParameterType) 94 { 95 return true; 96 } 97 if (typeSrc is TypeParameterType && typeDst.isInterfaceType()) 98 { 99 return true; 100 } 101 102 // * From any class-type S to any interface-type T, provided S is not sealed 103 // * From any interface-type S to any class-type T, provided T is not sealed 104 // * From any interface-type S to any interface-type T, provided S is not derived from T. 105 if (typeSrc is AggregateType atSrc && typeDst is AggregateType atDst) 106 { 107 AggregateSymbol aggSrc = atSrc.getAggregate(); 108 AggregateSymbol aggDest = atDst.getAggregate(); 109 110 if ((aggSrc.IsClass() && !aggSrc.IsSealed() && aggDest.IsInterface()) || 111 (aggSrc.IsInterface() && aggDest.IsClass() && !aggDest.IsSealed()) || 112 (aggSrc.IsInterface() && aggDest.IsInterface())) 113 { 114 return true; 115 } 116 } 117 118 if (typeSrc is ArrayType arrSrc) 119 { 120 // * From an array-type S with an element type SE to an array-type T with an element type TE, provided all of the following are true: 121 // o S and T differ only in element type. (In other words, S and T have the same number of dimensions.) 122 // o An explicit reference conversion exists from SE to TE. 123 if (typeDst is ArrayType arrDst) 124 { 125 return arrSrc.rank == arrDst.rank 126 && arrSrc.IsSZArray == arrDst.IsSZArray 127 && FExpRefConv(loader, arrSrc.GetElementType(), arrDst.GetElementType()); 128 } 129 130 // * From a one-dimensional array-type S[] to System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyList<T> 131 // and their base interfaces, provided there is an explicit reference conversion from S to T. 132 if (!arrSrc.IsSZArray || 133 !typeDst.isInterfaceType()) 134 { 135 return false; 136 } 137 138 AggregateType aggDst = (AggregateType)typeDst; 139 TypeArray typeArgsAll = aggDst.GetTypeArgsAll(); 140 141 if (typeArgsAll.Count != 1) 142 { 143 return false; 144 } 145 146 AggregateSymbol aggIList = loader.GetPredefAgg(PredefinedType.PT_G_ILIST); 147 AggregateSymbol aggIReadOnlyList = loader.GetPredefAgg(PredefinedType.PT_G_IREADONLYLIST); 148 149 if ((aggIList == null || 150 !SymbolLoader.IsBaseAggregate(aggIList, aggDst.getAggregate())) && 151 (aggIReadOnlyList == null || 152 !SymbolLoader.IsBaseAggregate(aggIReadOnlyList, aggDst.getAggregate()))) 153 { 154 return false; 155 } 156 157 return FExpRefConv(loader, arrSrc.GetElementType(), typeArgsAll[0]); 158 } 159 160 if (typeDst is ArrayType arrayDest && typeSrc is AggregateType aggtypeSrc) 161 { 162 // * From System.Array and the interfaces it implements, to any array-type. 163 if (loader.HasIdentityOrImplicitReferenceConversion(loader.GetPredefindType(PredefinedType.PT_ARRAY), typeSrc)) 164 { 165 return true; 166 } 167 168 // * From System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyList<T> and their base interfaces to a 169 // one-dimensional array-type S[], provided there is an implicit or explicit reference conversion from S[] to 170 // System.Collections.Generic.IList<T> or System.Collections.Generic.IReadOnlyList<T>. This is precisely when either S and T 171 // are the same type or there is an implicit or explicit reference conversion from S to T. 172 if (!arrayDest.IsSZArray || !typeSrc.isInterfaceType() || 173 aggtypeSrc.GetTypeArgsAll().Count != 1) 174 { 175 return false; 176 } 177 178 AggregateSymbol aggIList = loader.GetPredefAgg(PredefinedType.PT_G_ILIST); 179 AggregateSymbol aggIReadOnlyList = loader.GetPredefAgg(PredefinedType.PT_G_IREADONLYLIST); 180 181 if ((aggIList == null || 182 !SymbolLoader.IsBaseAggregate(aggIList, aggtypeSrc.getAggregate())) && 183 (aggIReadOnlyList == null || 184 !SymbolLoader.IsBaseAggregate(aggIReadOnlyList, aggtypeSrc.getAggregate()))) 185 { 186 return false; 187 } 188 189 CType typeArr = arrayDest.GetElementType(); 190 CType typeLst = aggtypeSrc.GetTypeArgsAll()[0]; 191 192 Debug.Assert(!(typeArr is MethodGroupType)); 193 return typeArr == typeLst || FExpRefConv(loader, typeArr, typeLst); 194 } 195 if (HasGenericDelegateExplicitReferenceConversion(loader, typeSrc, typeDst)) 196 { 197 return true; 198 } 199 } 200 else if (typeSrc.IsRefType()) 201 { 202 // conversion of T . U, where T : class, U 203 // .. these constraints implies where U : class 204 return loader.HasIdentityOrImplicitReferenceConversion(typeSrc, typeDst); 205 } 206 else if (typeDst.IsRefType()) 207 { 208 // conversion of T . U, where U : class, T 209 // .. these constraints implies where T : class 210 return loader.HasIdentityOrImplicitReferenceConversion(typeDst, typeSrc); 211 } 212 return false; 213 } 214 /*************************************************************************************************** 215 216 There exists an explicit conversion ... 217 * From a generic delegate type S to generic delegate type T, provided all of the follow are true: 218 o Both types are constructed generic types of the same generic delegate type, D<X1,... Xk>.That is, 219 S is D<S1,... Sk> and T is D<T1,... Tk>. 220 o S is not compatible with or identical to T. 221 o If type parameter Xi is declared to be invariant then Si must be identical to Ti. 222 o If type parameter Xi is declared to be covariant ("out") then Si must be convertible 223 to Ti via an identify conversion, implicit reference conversion, or explicit reference conversion. 224 o If type parameter Xi is declared to be contravariant ("in") then either Si must be identical to Ti, 225 or Si and Ti must both be reference types. 226 ***************************************************************************************************/ HasGenericDelegateExplicitReferenceConversion(SymbolLoader loader, CType pSource, CType pTarget)227 public static bool HasGenericDelegateExplicitReferenceConversion(SymbolLoader loader, CType pSource, CType pTarget) 228 { 229 if (!pSource.isDelegateType() || 230 !pTarget.isDelegateType() || 231 pSource.getAggregate() != pTarget.getAggregate() || 232 loader.HasIdentityOrImplicitReferenceConversion(pSource, pTarget)) 233 { 234 return false; 235 } 236 237 TypeArray pTypeParams = pSource.getAggregate().GetTypeVarsAll(); 238 TypeArray pSourceArgs = ((AggregateType)pSource).GetTypeArgsAll(); 239 TypeArray pTargetArgs = ((AggregateType)pTarget).GetTypeArgsAll(); 240 241 Debug.Assert(pTypeParams.Count == pSourceArgs.Count); 242 Debug.Assert(pTypeParams.Count == pTargetArgs.Count); 243 244 for (int iParam = 0; iParam < pTypeParams.Count; ++iParam) 245 { 246 CType pSourceArg = pSourceArgs[iParam]; 247 CType pTargetArg = pTargetArgs[iParam]; 248 249 // If they're identical then this one is automatically good, so skip it. 250 // If we have an error type, then we're in some fault tolerance. Let it through. 251 if (pSourceArg == pTargetArg) 252 { 253 continue; 254 } 255 256 TypeParameterType pParam = (TypeParameterType)pTypeParams[iParam]; 257 if (pParam.Invariant) 258 { 259 return false; 260 } 261 262 if (pParam.Covariant) 263 { 264 if (!FExpRefConv(loader, pSourceArg, pTargetArg)) 265 { 266 return false; 267 } 268 } 269 else if (pParam.Contravariant) 270 { 271 if (!pSourceArg.IsRefType() || !pTargetArg.IsRefType()) 272 { 273 return false; 274 } 275 } 276 } 277 return true; 278 } 279 280 /*************************************************************************************************** 281 13.1.1 Identity conversion 282 283 An identity conversion converts from any type to the same type. This conversion exists only 284 such that an entity that already has a required type can be said to be convertible to that type. 285 286 Always returns false if the types are error, anonymous method, or method group 287 ***************************************************************************************************/ 288 289 /*************************************************************************************************** 290 Determines whether there is a boxing conversion from typeSrc to typeDst 291 292 13.1.5 Boxing conversions 293 294 A boxing conversion permits any non-nullable-value-type to be implicitly converted to the type 295 object or System.ValueType or to any interface-type implemented by the non-nullable-value-type, 296 and any enum type to be implicitly converted to System.Enum as well. ... An enum can be boxed to 297 the type System.Enum, since that is the direct base class for all enums (21.4). A struct or enum 298 can be boxed to the type System.ValueType, since that is the direct base class for all 299 structs (18.3.2) and a base class for all enums. 300 301 A nullable-type has a boxing conversion to the same set of types to which the nullable-type’s 302 underlying type has boxing conversions. 303 304 For a type-parameter T that is not known to be a reference type (25.7), the following conversions 305 involving T are considered to be boxing conversions at compile-time. At run-time, if T is a value 306 type, the conversion is executed as a boxing conversion. At run-time, if T is a reference type, 307 the conversion is executed as an implicit reference conversion or identity conversion. 308 * From T to its effective base class C, from T to any base class of C, and from T to any 309 interface implemented by C. [Note: C will be one of the types System.Object, System.ValueType, 310 or System.Enum (otherwise T would be known to be a reference type and §13.1.4 would apply 311 instead of this clause). end note] 312 * From T to an interface-type I in T’s effective interface set and from T to any base 313 interface of I. 314 ***************************************************************************************************/ 315 316 /*************************************************************************************************** 317 Determines whether there is a wrapping conversion from typeSrc to typeDst 318 319 13.7 Conversions involving nullable types 320 321 The following terms are used in the subsequent sections: 322 * The term wrapping denotes the process of packaging a value, of type T, in an instance of type T?. 323 A value x of type T is wrapped to type T? by evaluating the expression new T?(x). 324 ***************************************************************************************************/ FWrappingConv(CType typeSrc, CType typeDst)325 public static bool FWrappingConv(CType typeSrc, CType typeDst) 326 { 327 return typeDst is NullableType nubDst && typeSrc == nubDst.GetUnderlyingType(); 328 } 329 330 /*************************************************************************************************** 331 Determines whether there is a unwrapping conversion from typeSrc to typeDst 332 333 13.7 Conversions involving nullable types 334 335 The following terms are used in the subsequent sections: 336 * The term unwrapping denotes the process of obtaining the value, of type T, contained in an 337 instance of type T?. A value x of type T? is unwrapped to type T by evaluating the expression 338 x.Value. Attempting to unwrap a null instance causes a System.InvalidOperationException to be 339 thrown. 340 341 ***************************************************************************************************/ FUnwrappingConv(CType typeSrc, CType typeDst)342 public static bool FUnwrappingConv(CType typeSrc, CType typeDst) 343 { 344 return FWrappingConv(typeDst, typeSrc); 345 } 346 } 347 } 348