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