1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
5 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
6 * copy of the license can be found in the License.html file at the root of this distribution. If
7 * you cannot locate the Apache License, Version 2.0, please send an email to
8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
9 * by the terms of the Apache License, Version 2.0.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
17 using Microsoft.Scripting.Ast;
19 using System.Linq.Expressions;
22 using System.Collections.Generic;
23 using System.Diagnostics;
24 using System.Reflection;
26 namespace System.Dynamic.Utils {
28 internal static class TypeUtils {
29 private const BindingFlags AnyStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
30 internal const MethodAttributes PublicStatic = MethodAttributes.Public | MethodAttributes.Static;
32 internal static Type GetNonNullableType(this Type type) {
33 if (IsNullableType(type)) {
34 return type.GetGenericArguments()[0];
39 internal static Type GetNullableType(Type type) {
40 Debug.Assert(type != null, "type cannot be null");
41 if (type.IsValueType && !IsNullableType(type)) {
42 return typeof(Nullable<>).MakeGenericType(type);
47 internal static bool IsNullableType(this Type type) {
48 return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
51 internal static bool IsBool(Type type) {
52 return GetNonNullableType(type) == typeof(bool);
55 internal static bool IsNumeric(Type type) {
56 type = GetNonNullableType(type);
58 switch (Type.GetTypeCode(type)) {
76 internal static bool IsInteger(Type type) {
77 type = GetNonNullableType(type);
81 switch (Type.GetTypeCode(type)) {
97 internal static bool IsArithmetic(Type type) {
98 type = GetNonNullableType(type);
100 switch (Type.GetTypeCode(type)) {
104 case TypeCode.Double:
105 case TypeCode.Single:
106 case TypeCode.UInt16:
107 case TypeCode.UInt32:
108 case TypeCode.UInt64:
115 internal static bool IsUnsignedInt(Type type) {
116 type = GetNonNullableType(type);
118 switch (Type.GetTypeCode(type)) {
119 case TypeCode.UInt16:
120 case TypeCode.UInt32:
121 case TypeCode.UInt64:
128 internal static bool IsIntegerOrBool(Type type) {
129 type = GetNonNullableType(type);
131 switch (Type.GetTypeCode(type)) {
135 case TypeCode.UInt64:
136 case TypeCode.UInt32:
137 case TypeCode.UInt16:
138 case TypeCode.Boolean:
147 internal static bool AreEquivalent(Type t1, Type t2) {
148 #if FEATURE_TYPE_EQUIVALENCE
149 return t1 == t2 || t1.IsEquivalentTo(t2);
155 internal static bool AreReferenceAssignable(Type dest, Type src) {
156 // WARNING: This actually implements "Is this identity assignable and/or reference assignable?"
157 if (AreEquivalent(dest, src)) {
160 if (!dest.IsValueType && !src.IsValueType && dest.IsAssignableFrom(src)) {
166 // Checks if the type is a valid target for an instance call
167 internal static bool IsValidInstanceType(MemberInfo member, Type instanceType) {
168 Type targetType = member.DeclaringType;
169 if (AreReferenceAssignable(targetType, instanceType)) {
172 if (instanceType.IsValueType) {
173 if (AreReferenceAssignable(targetType, typeof(System.Object))) {
176 if (AreReferenceAssignable(targetType, typeof(System.ValueType))) {
179 if (instanceType.IsEnum && AreReferenceAssignable(targetType, typeof(System.Enum))) {
182 // A call to an interface implemented by a struct is legal whether the struct has
183 // been boxed or not.
184 if (targetType.IsInterface) {
185 foreach (Type interfaceType in instanceType.GetInterfaces()) {
186 if (AreReferenceAssignable(targetType, interfaceType)) {
195 internal static bool HasIdentityPrimitiveOrNullableConversion(Type source, Type dest) {
196 Debug.Assert(source != null && dest != null);
198 // Identity conversion
199 if (AreEquivalent(source, dest)) {
203 // Nullable conversions
204 if (IsNullableType(source) && AreEquivalent(dest, GetNonNullableType(source))) {
207 if (IsNullableType(dest) && AreEquivalent(source, GetNonNullableType(dest))) {
210 // Primitive runtime conversions
211 // All conversions amongst enum, bool, char, integer and float types
212 // (and their corresponding nullable types) are legal except for
213 // nonbool==>bool and nonbool==>bool?
214 // Since we have already covered bool==>bool, bool==>bool?, etc, above,
215 // we can just disallow having a bool or bool? destination type here.
216 if (IsConvertible(source) && IsConvertible(dest) && GetNonNullableType(dest) != typeof(bool)) {
222 internal static bool HasReferenceConversion(Type source, Type dest) {
223 Debug.Assert(source != null && dest != null);
225 // void -> void conversion is handled elsewhere
226 // (it's an identity conversion)
227 // All other void conversions are disallowed.
228 if (source == typeof(void) || dest == typeof(void)) {
232 Type nnSourceType = TypeUtils.GetNonNullableType(source);
233 Type nnDestType = TypeUtils.GetNonNullableType(dest);
236 if (nnSourceType.IsAssignableFrom(nnDestType)) {
240 if (nnDestType.IsAssignableFrom(nnSourceType)) {
243 // Interface conversion
244 if (source.IsInterface || dest.IsInterface) {
247 // Variant delegate conversion
248 if (IsLegalExplicitVariantDelegateConversion(source, dest))
252 if (source == typeof(object) || dest == typeof(object)) {
258 private static bool IsCovariant(Type t)
260 Debug.Assert(t != null);
261 return 0 != (t.GenericParameterAttributes & GenericParameterAttributes.Covariant);
264 private static bool IsContravariant(Type t)
266 Debug.Assert(t != null);
267 return 0 != (t.GenericParameterAttributes & GenericParameterAttributes.Contravariant);
270 private static bool IsInvariant(Type t)
272 Debug.Assert(t != null);
273 return 0 == (t.GenericParameterAttributes & GenericParameterAttributes.VarianceMask);
276 private static bool IsDelegate(Type t)
278 Debug.Assert(t != null);
279 return t.IsSubclassOf(typeof(System.MulticastDelegate));
282 internal static bool IsLegalExplicitVariantDelegateConversion(Type source, Type dest)
284 Debug.Assert(source != null && dest != null);
286 // There *might* be a legal conversion from a generic delegate type S to generic delegate type T,
287 // provided all of the follow are true:
288 // o Both types are constructed generic types of the same generic delegate type, D<X1,... Xk>.
289 // That is, S = D<S1...>, T = D<T1...>.
290 // o If type parameter Xi is declared to be invariant then Si must be identical to Ti.
291 // o If type parameter Xi is declared to be covariant ("out") then Si must be convertible
292 // to Ti via an identify conversion, implicit reference conversion, or explicit reference conversion.
293 // o If type parameter Xi is declared to be contravariant ("in") then either Si must be identical to Ti,
294 // or Si and Ti must both be reference types.
296 if (!IsDelegate(source) || !IsDelegate(dest) || !source.IsGenericType || !dest.IsGenericType)
299 Type genericDelegate = source.GetGenericTypeDefinition();
301 if (dest.GetGenericTypeDefinition() != genericDelegate)
304 Type[] genericParameters = genericDelegate.GetGenericArguments();
305 Type[] sourceArguments = source.GetGenericArguments();
306 Type[] destArguments = dest.GetGenericArguments();
308 Debug.Assert(genericParameters != null);
309 Debug.Assert(sourceArguments != null);
310 Debug.Assert(destArguments != null);
311 Debug.Assert(genericParameters.Length == sourceArguments.Length);
312 Debug.Assert(genericParameters.Length == destArguments.Length);
314 for (int iParam = 0; iParam < genericParameters.Length; ++iParam)
316 Type sourceArgument = sourceArguments[iParam];
317 Type destArgument = destArguments[iParam];
319 Debug.Assert(sourceArgument != null && destArgument != null);
321 // If the arguments are identical then this one is automatically good, so skip it.
322 if (AreEquivalent(sourceArgument, destArgument))
327 Type genericParameter = genericParameters[iParam];
329 Debug.Assert(genericParameter != null);
331 if (IsInvariant(genericParameter))
336 if (IsCovariant(genericParameter))
338 if (!HasReferenceConversion(sourceArgument, destArgument))
343 else if (IsContravariant(genericParameter))
345 if (sourceArgument.IsValueType || destArgument.IsValueType)
354 internal static bool IsConvertible(Type type) {
355 type = GetNonNullableType(type);
359 switch (Type.GetTypeCode(type)) {
360 case TypeCode.Boolean:
366 case TypeCode.UInt16:
367 case TypeCode.UInt32:
368 case TypeCode.UInt64:
369 case TypeCode.Single:
370 case TypeCode.Double:
378 internal static bool HasReferenceEquality(Type left, Type right) {
379 if (left.IsValueType || right.IsValueType) {
383 // If we have an interface and a reference type then we can do
384 // reference equality.
386 // If we have two reference types and one is assignable to the
387 // other then we can do reference equality.
389 return left.IsInterface || right.IsInterface ||
390 AreReferenceAssignable(left, right) ||
391 AreReferenceAssignable(right, left);
394 internal static bool HasBuiltInEqualityOperator(Type left, Type right) {
395 // If we have an interface and a reference type then we can do
396 // reference equality.
397 if (left.IsInterface && !right.IsValueType) {
400 if (right.IsInterface && !left.IsValueType) {
403 // If we have two reference types and one is assignable to the
404 // other then we can do reference equality.
405 if (!left.IsValueType && !right.IsValueType) {
406 if (AreReferenceAssignable(left, right) || AreReferenceAssignable(right, left)) {
410 // Otherwise, if the types are not the same then we definitely
411 // do not have a built-in equality operator.
412 if (!AreEquivalent(left, right)) {
415 // We have two identical value types, modulo nullability. (If they were both the
416 // same reference type then we would have returned true earlier.)
417 Debug.Assert(left.IsValueType);
418 // Equality between struct types is only defined for numerics, bools, enums,
419 // and their nullable equivalents.
420 Type nnType = GetNonNullableType(left);
421 if (nnType == typeof(bool) || IsNumeric(nnType) || nnType.IsEnum) {
427 internal static bool IsImplicitlyConvertible(Type source, Type destination) {
428 return AreEquivalent(source, destination) || // identity conversion
429 IsImplicitNumericConversion(source, destination) ||
430 IsImplicitReferenceConversion(source, destination) ||
431 IsImplicitBoxingConversion(source, destination) ||
432 IsImplicitNullableConversion(source, destination);
436 internal static MethodInfo GetUserDefinedCoercionMethod(Type convertFrom, Type convertToType, bool implicitOnly) {
437 // check for implicit coercions first
438 Type nnExprType = TypeUtils.GetNonNullableType(convertFrom);
439 Type nnConvType = TypeUtils.GetNonNullableType(convertToType);
440 // try exact match on types
441 MethodInfo[] eMethods = nnExprType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
442 MethodInfo method = FindConversionOperator(eMethods, convertFrom, convertToType, implicitOnly);
443 if (method != null) {
446 MethodInfo[] cMethods = nnConvType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
447 method = FindConversionOperator(cMethods, convertFrom, convertToType, implicitOnly);
448 if (method != null) {
451 // try lifted conversion
452 if (!TypeUtils.AreEquivalent(nnExprType, convertFrom) ||
453 !TypeUtils.AreEquivalent(nnConvType, convertToType)) {
454 method = FindConversionOperator(eMethods, nnExprType, nnConvType, implicitOnly);
455 if (method == null) {
456 method = FindConversionOperator(cMethods, nnExprType, nnConvType, implicitOnly);
458 if (method != null) {
465 internal static MethodInfo FindConversionOperator(MethodInfo[] methods, Type typeFrom, Type typeTo, bool implicitOnly) {
466 foreach (MethodInfo mi in methods) {
467 if (mi.Name != "op_Implicit" && (implicitOnly || mi.Name != "op_Explicit")) {
470 if (!TypeUtils.AreEquivalent(mi.ReturnType, typeTo)) {
473 ParameterInfo[] pis = mi.GetParametersCached();
474 if (!TypeUtils.AreEquivalent(pis[0].ParameterType, typeFrom)) {
482 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
483 private static bool IsImplicitNumericConversion(Type source, Type destination) {
484 TypeCode tcSource = Type.GetTypeCode(source);
485 TypeCode tcDest = Type.GetTypeCode(destination);
493 case TypeCode.Single:
494 case TypeCode.Double:
495 case TypeCode.Decimal:
502 case TypeCode.UInt16:
504 case TypeCode.UInt32:
506 case TypeCode.UInt64:
507 case TypeCode.Single:
508 case TypeCode.Double:
509 case TypeCode.Decimal:
517 case TypeCode.Single:
518 case TypeCode.Double:
519 case TypeCode.Decimal:
523 case TypeCode.UInt16:
526 case TypeCode.UInt32:
528 case TypeCode.UInt64:
529 case TypeCode.Single:
530 case TypeCode.Double:
531 case TypeCode.Decimal:
538 case TypeCode.Single:
539 case TypeCode.Double:
540 case TypeCode.Decimal:
544 case TypeCode.UInt32:
546 case TypeCode.UInt32:
547 case TypeCode.UInt64:
548 case TypeCode.Single:
549 case TypeCode.Double:
550 case TypeCode.Decimal:
555 case TypeCode.UInt64:
557 case TypeCode.Single:
558 case TypeCode.Double:
559 case TypeCode.Decimal:
565 case TypeCode.UInt16:
567 case TypeCode.UInt32:
569 case TypeCode.UInt64:
570 case TypeCode.Single:
571 case TypeCode.Double:
572 case TypeCode.Decimal:
576 case TypeCode.Single:
577 return (tcDest == TypeCode.Double);
582 private static bool IsImplicitReferenceConversion(Type source, Type destination) {
583 return destination.IsAssignableFrom(source);
586 private static bool IsImplicitBoxingConversion(Type source, Type destination) {
587 if (source.IsValueType && (destination == typeof(object) || destination == typeof(System.ValueType)))
589 if (source.IsEnum && destination == typeof(System.Enum))
594 private static bool IsImplicitNullableConversion(Type source, Type destination) {
595 if (IsNullableType(destination))
596 return IsImplicitlyConvertible(GetNonNullableType(source), GetNonNullableType(destination));
600 internal static bool IsSameOrSubclass(Type type, Type subType) {
601 return AreEquivalent(type, subType) || subType.IsSubclassOf(type);
604 internal static void ValidateType(Type type) {
605 if (type.IsGenericTypeDefinition) {
606 throw Error.TypeIsGeneric(type);
608 if (type.ContainsGenericParameters) {
609 throw Error.TypeContainsGenericParameters(type);
614 internal static Type FindGenericType(Type definition, Type type) {
615 while (type != null && type != typeof(object)) {
616 if (type.IsGenericType && AreEquivalent(type.GetGenericTypeDefinition(), definition)) {
619 if (definition.IsInterface) {
620 foreach (Type itype in type.GetInterfaces()) {
621 Type found = FindGenericType(definition, itype);
626 type = type.BaseType;
631 internal static bool IsUnsigned(Type type) {
632 type = GetNonNullableType(type);
633 switch (Type.GetTypeCode(type)) {
635 case TypeCode.UInt16:
637 case TypeCode.UInt32:
638 case TypeCode.UInt64:
645 internal static bool IsFloatingPoint(Type type) {
646 type = GetNonNullableType(type);
647 switch (Type.GetTypeCode(type)) {
648 case TypeCode.Single:
649 case TypeCode.Double:
657 /// Searches for an operator method on the type. The method must have
658 /// the specified signature, no generic arguments, and have the
659 /// SpecialName bit set. Also searches inherited operator methods.
661 /// NOTE: This was designed to satisfy the needs of op_True and
662 /// op_False, because we have to do runtime lookup for those. It may
663 /// not work right for unary operators in general.
665 internal static MethodInfo GetBooleanOperator(Type type, string name) {
667 MethodInfo result = type.GetMethodValidated(name, AnyStatic, null, new Type[] { type }, null);
668 if (result != null && result.IsSpecialName && !result.ContainsGenericParameters) {
671 type = type.BaseType;
672 } while (type != null);
676 internal static Type GetNonRefType(this Type type) {
677 return type.IsByRef ? type.GetElementType() : type;
680 private static readonly Assembly _mscorlib = typeof(object).Assembly;
681 private static readonly Assembly _systemCore = typeof(Expression).Assembly;
684 /// We can cache references to types, as long as they aren't in
685 /// collectable assemblies. Unfortunately, we can't really distinguish
686 /// between different flavors of assemblies. But, we can at least
687 /// create a whitelist for types in mscorlib (so we get the primitives)
688 /// and System.Core (so we find Func/Action overloads, etc).
690 internal static bool CanCache(this Type t) {
691 // Note: we don't have to scan base or declaring types here.
692 // There's no way for a type in mscorlib to derive from or be
693 // contained in a type from another assembly. The only thing we
694 // need to look at is the generic arguments, which are the thing
695 // that allows mscorlib types to be specialized by types in other
698 var asm = t.Assembly;
699 if (asm != _mscorlib && asm != _systemCore) {
700 // Not in mscorlib or our assembly
704 if (t.IsGenericType) {
705 foreach (Type g in t.GetGenericArguments()) {