// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.CSharp.Resolver { /// /// Contains logic that determines whether an implicit conversion exists between two types. /// /// /// This class is thread-safe. /// public sealed class CSharpConversions { readonly ConcurrentDictionary implicitConversionCache = new ConcurrentDictionary(); readonly ICompilation compilation; public CSharpConversions(ICompilation compilation) { if (compilation == null) throw new ArgumentNullException(nameof(compilation)); this.compilation = compilation; } /// /// Gets the Conversions instance for the specified . /// This will make use of the context's cache manager to reuse the Conversions instance. /// public static CSharpConversions Get(ICompilation compilation) { if (compilation == null) throw new ArgumentNullException(nameof(compilation)); CacheManager cache = compilation.CacheManager; CSharpConversions operators = (CSharpConversions)cache.GetShared(typeof(CSharpConversions)); if (operators == null) { operators = (CSharpConversions)cache.GetOrAddShared(typeof(CSharpConversions), new CSharpConversions(compilation)); } return operators; } #region TypePair (for caching) struct TypePair : IEquatable { public readonly IType FromType; public readonly IType ToType; public TypePair(IType fromType, IType toType) { Debug.Assert(fromType != null && toType != null); this.FromType = fromType; this.ToType = toType; } public override bool Equals(object obj) { return (obj is TypePair) && Equals((TypePair)obj); } public bool Equals(TypePair other) { return object.Equals(this.FromType, other.FromType) && object.Equals(this.ToType, other.ToType); } public override int GetHashCode() { unchecked { return 1000000007 * FromType.GetHashCode() + 1000000009 * ToType.GetHashCode(); } } } #endregion #region ImplicitConversion private Conversion ImplicitConversion(ResolveResult resolveResult, IType toType, bool allowUserDefined, bool allowTuple) { Conversion c; if (resolveResult.IsCompileTimeConstant) { c = ImplicitEnumerationConversion(resolveResult, toType); if (c.IsValid) return c; if (ImplicitConstantExpressionConversion(resolveResult, toType)) return Conversion.ImplicitConstantExpressionConversion; c = StandardImplicitConversion(resolveResult.Type, toType, allowTuple); if (c != Conversion.None) return c; if (allowUserDefined) { c = UserDefinedImplicitConversion(resolveResult, resolveResult.Type, toType); if (c != Conversion.None) return c; } } else { if (allowTuple && resolveResult is TupleResolveResult tupleRR) { c = TupleConversion(tupleRR, toType, isExplicit: false); if (c != Conversion.None) return c; } if (resolveResult is ThrowResolveResult) { return Conversion.ThrowExpressionConversion; } if (allowUserDefined && allowTuple) { // if allowUserDefined and allowTuple are true, we might as well use the cache c = ImplicitConversion(resolveResult.Type, toType); } else { c = ImplicitConversion(resolveResult.Type, toType, allowUserDefined, allowTuple); } if (c != Conversion.None) return c; } if (resolveResult is InterpolatedStringResolveResult isrr) { if (toType.IsKnownType(KnownTypeCode.IFormattable) || toType.IsKnownType(KnownTypeCode.FormattableString)) return Conversion.ImplicitInterpolatedStringConversion; } if (resolveResult.Type.Kind == TypeKind.Dynamic) return Conversion.ImplicitDynamicConversion; c = AnonymousFunctionConversion(resolveResult, toType); if (c != Conversion.None) return c; c = MethodGroupConversion(resolveResult, toType); return c; } private Conversion ImplicitConversion(IType fromType, IType toType, bool allowUserDefined, bool allowTuple) { // C# 4.0 spec: §6.1 var c = StandardImplicitConversion(fromType, toType, allowTuple); if (c == Conversion.None && allowUserDefined) { c = UserDefinedImplicitConversion(null, fromType, toType); } return c; } public Conversion ImplicitConversion(ResolveResult resolveResult, IType toType) { if (resolveResult == null) throw new ArgumentNullException(nameof(resolveResult)); return ImplicitConversion(resolveResult, toType, allowUserDefined: true, allowTuple: true); } public Conversion ImplicitConversion(IType fromType, IType toType) { if (fromType == null) throw new ArgumentNullException(nameof(fromType)); if (toType == null) throw new ArgumentNullException(nameof(toType)); TypePair pair = new TypePair(fromType, toType); if (implicitConversionCache.TryGetValue(pair, out Conversion c)) return c; c = ImplicitConversion(fromType, toType, allowUserDefined: true, allowTuple: true); implicitConversionCache[pair] = c; return c; } public Conversion StandardImplicitConversion(IType fromType, IType toType) { if (fromType == null) throw new ArgumentNullException(nameof(fromType)); if (toType == null) throw new ArgumentNullException(nameof(toType)); return StandardImplicitConversion(fromType, toType, allowTupleConversion: true); } Conversion StandardImplicitConversion(IType fromType, IType toType, bool allowTupleConversion) { // C# 4.0 spec: §6.3.1 if (IdentityConversion(fromType, toType)) return Conversion.IdentityConversion; if (ImplicitNumericConversion(fromType, toType)) return Conversion.ImplicitNumericConversion; Conversion c = ImplicitNullableConversion(fromType, toType); if (c != Conversion.None) return c; if (NullLiteralConversion(fromType, toType)) return Conversion.NullLiteralConversion; if (ImplicitReferenceConversion(fromType, toType, 0)) return Conversion.ImplicitReferenceConversion; if (IsBoxingConversion(fromType, toType)) return Conversion.BoxingConversion; if (ImplicitTypeParameterConversion(fromType, toType)) { // Implicit type parameter conversions that aren't also // reference conversions are considered to be boxing conversions return Conversion.BoxingConversion; } if (ImplicitPointerConversion(fromType, toType)) return Conversion.ImplicitPointerConversion; if (allowTupleConversion) { c = TupleConversion(fromType, toType, isExplicit: false); if (c != Conversion.None) return c; } return Conversion.None; } /// /// Gets whether the type 'fromType' is convertible to 'toType' /// using one of the conversions allowed when satisying constraints (§4.4.4) /// public bool IsConstraintConvertible(IType fromType, IType toType) { if (fromType == null) throw new ArgumentNullException(nameof(fromType)); if (toType == null) throw new ArgumentNullException(nameof(toType)); if (IdentityConversion(fromType, toType)) return true; if (ImplicitReferenceConversion(fromType, toType, 0)) return true; if (NullableType.IsNullable(fromType)) { // An 'object' constraint still allows nullable value types // (object constraints don't exist in C#, but are inserted by DefaultResolvedTypeParameter.DirectBaseTypes) if (toType.IsKnownType(KnownTypeCode.Object)) return true; } else { if (IsBoxingConversion(fromType, toType)) return true; } if (ImplicitTypeParameterConversion(fromType, toType)) return true; return false; } #endregion #region ExplicitConversion public Conversion ExplicitConversion(ResolveResult resolveResult, IType toType) { if (resolveResult == null) throw new ArgumentNullException(nameof(resolveResult)); if (toType == null) throw new ArgumentNullException(nameof(toType)); if (resolveResult.Type.Kind == TypeKind.Dynamic) return Conversion.ExplicitDynamicConversion; Conversion c = ImplicitConversion(resolveResult, toType, allowUserDefined: false, allowTuple: false); if (c != Conversion.None) return c; if (resolveResult is TupleResolveResult tupleRR) { c = TupleConversion(tupleRR, toType, isExplicit: true); if (c != Conversion.None) return c; } c = ExplicitConversionImpl(resolveResult.Type, toType); if (c != Conversion.None) return c; return UserDefinedExplicitConversion(resolveResult, resolveResult.Type, toType); } public Conversion ExplicitConversion(IType fromType, IType toType) { if (fromType == null) throw new ArgumentNullException(nameof(fromType)); if (toType == null) throw new ArgumentNullException(nameof(toType)); Conversion c = ImplicitConversion(fromType, toType, allowUserDefined: false, allowTuple: false); if (c != Conversion.None) return c; c = ExplicitConversionImpl(fromType, toType); if (c != Conversion.None) return c; return UserDefinedExplicitConversion(null, fromType, toType); } Conversion ExplicitConversionImpl(IType fromType, IType toType) { // This method is called after we already checked for implicit conversions, // so any remaining conversions must be explicit. if (AnyNumericConversion(fromType, toType)) return Conversion.ExplicitNumericConversion; if (ExplicitEnumerationConversion(fromType, toType)) return Conversion.EnumerationConversion(false, false); Conversion c = ExplicitNullableConversion(fromType, toType); if (c != Conversion.None) return c; if (ExplicitReferenceConversion(fromType, toType)) return Conversion.ExplicitReferenceConversion; if (UnboxingConversion(fromType, toType)) return Conversion.UnboxingConversion; c = ExplicitTypeParameterConversion(fromType, toType); if (c != Conversion.None) return c; if (ExplicitPointerConversion(fromType, toType)) return Conversion.ExplicitPointerConversion; return TupleConversion(fromType, toType, isExplicit: true); } #endregion #region Identity Conversion /// /// Gets whether there is an identity conversion from to /// public bool IdentityConversion(IType fromType, IType toType) { // C# 4.0 spec: §6.1.1 fromType = fromType.AcceptVisitor(NormalizeTypeVisitor.TypeErasure); toType = toType.AcceptVisitor(NormalizeTypeVisitor.TypeErasure); return fromType.Equals(toType); } #endregion #region Numeric Conversions static readonly bool[,] implicitNumericConversionLookup = { // to: short ushort int uint long ulong // from: /* char */ { false, true , true , true , true , true }, /* sbyte */ { true , false, true , false, true , false }, /* byte */ { true , true , true , true , true , true }, /* short */ { true , false, true , false, true , false }, /* ushort */ { false, true , true , true , true , true }, /* int */ { false, false, true , false, true , false }, /* uint */ { false, false, false, true , true , true }, /* long */ { false, false, false, false, true , false }, /* ulong */ { false, false, false, false, false, true }, }; bool ImplicitNumericConversion(IType fromType, IType toType) { // C# 4.0 spec: §6.1.2 TypeCode from = ReflectionHelper.GetTypeCode(fromType); if (from == TypeCode.Empty) { // When converting from a native-sized integer, treat it as 64-bits switch (fromType.Kind) { case TypeKind.NInt: from = TypeCode.Int64; break; case TypeKind.NUInt: from = TypeCode.UInt64; break; } } TypeCode to = ReflectionHelper.GetTypeCode(toType); if (to == TypeCode.Empty) { // When converting to a native-sized integer, only 32-bits can be stored safely switch (toType.Kind) { case TypeKind.NInt: to = TypeCode.Int32; break; case TypeKind.NUInt: to = TypeCode.UInt32; break; } } if (to >= TypeCode.Single && to <= TypeCode.Decimal) { // Conversions to float/double/decimal exist from all integral types, // and there's a conversion from float to double. return from >= TypeCode.Char && from <= TypeCode.UInt64 || from == TypeCode.Single && to == TypeCode.Double; } else { // Conversions to integral types: look at the table return from >= TypeCode.Char && from <= TypeCode.UInt64 && to >= TypeCode.Int16 && to <= TypeCode.UInt64 && implicitNumericConversionLookup[from - TypeCode.Char, to - TypeCode.Int16]; } } bool IsNumericType(IType type) { switch (type.Kind) { case TypeKind.NInt: case TypeKind.NUInt: return true; } TypeCode c = ReflectionHelper.GetTypeCode(type); return c >= TypeCode.Char && c <= TypeCode.Decimal; } bool AnyNumericConversion(IType fromType, IType toType) { // C# 4.0 spec: §6.1.2 + §6.2.1 return IsNumericType(fromType) && IsNumericType(toType); } #endregion #region Enumeration Conversions Conversion ImplicitEnumerationConversion(ResolveResult rr, IType toType) { // C# 4.0 spec: §6.1.3 Debug.Assert(rr.IsCompileTimeConstant); TypeCode constantType = ReflectionHelper.GetTypeCode(rr.Type); if (constantType >= TypeCode.SByte && constantType <= TypeCode.Decimal && Convert.ToDouble(rr.ConstantValue) == 0) { if (NullableType.GetUnderlyingType(toType).Kind == TypeKind.Enum) { return Conversion.EnumerationConversion(true, NullableType.IsNullable(toType)); } } return Conversion.None; } bool ExplicitEnumerationConversion(IType fromType, IType toType) { // C# 4.0 spec: §6.2.2 if (fromType.Kind == TypeKind.Enum) { return toType.Kind == TypeKind.Enum || IsNumericType(toType); } else if (IsNumericType(fromType)) { return toType.Kind == TypeKind.Enum; } return false; } #endregion #region Nullable Conversions Conversion ImplicitNullableConversion(IType fromType, IType toType) { // C# 4.0 spec: §6.1.4 if (NullableType.IsNullable(toType)) { IType t = NullableType.GetUnderlyingType(toType); IType s = NullableType.GetUnderlyingType(fromType); // might or might not be nullable if (IdentityConversion(s, t)) return Conversion.ImplicitNullableConversion; if (ImplicitNumericConversion(s, t)) return Conversion.ImplicitLiftedNumericConversion; } return Conversion.None; } Conversion ExplicitNullableConversion(IType fromType, IType toType) { // C# 4.0 spec: §6.1.4 if (NullableType.IsNullable(toType) || NullableType.IsNullable(fromType)) { IType t = NullableType.GetUnderlyingType(toType); IType s = NullableType.GetUnderlyingType(fromType); if (IdentityConversion(s, t)) return Conversion.ExplicitNullableConversion; if (AnyNumericConversion(s, t)) return Conversion.ExplicitLiftedNumericConversion; if (ExplicitEnumerationConversion(s, t)) return Conversion.EnumerationConversion(false, true); } return Conversion.None; } #endregion #region Null Literal Conversion bool NullLiteralConversion(IType fromType, IType toType) { // C# 4.0 spec: §6.1.5 if (fromType.Kind == TypeKind.Null) { return NullableType.IsNullable(toType) || toType.IsReferenceType == true; } else { return false; } } #endregion #region Implicit Reference Conversion public bool IsImplicitReferenceConversion(IType fromType, IType toType) { return ImplicitReferenceConversion(fromType, toType, 0); } bool ImplicitReferenceConversion(IType fromType, IType toType, int subtypeCheckNestingDepth) { // C# 4.0 spec: §6.1.6 // reference conversions are possible: // - if both types are known to be reference types // - if both types are type parameters and fromType has a class constraint // (ImplicitTypeParameterConversionWithClassConstraintOnlyOnT) if (!(fromType.IsReferenceType == true && toType.IsReferenceType != false)) return false; ArrayType fromArray = fromType as ArrayType; if (fromArray != null) { ArrayType toArray = toType as ArrayType; if (toArray != null) { // array covariance (the broken kind) return fromArray.Dimensions == toArray.Dimensions && ImplicitReferenceConversion(fromArray.ElementType, toArray.ElementType, subtypeCheckNestingDepth); } // conversion from single-dimensional array S[] to IList: IType toTypeArgument = UnpackGenericArrayInterface(toType); if (fromArray.Dimensions == 1 && toTypeArgument != null) { // array covariance plays a part here as well (string[] is IList) return IdentityConversion(fromArray.ElementType, toTypeArgument) || ImplicitReferenceConversion(fromArray.ElementType, toTypeArgument, subtypeCheckNestingDepth); } // conversion from any array to System.Array and the interfaces it implements: IType systemArray = compilation.FindType(KnownTypeCode.Array); return ImplicitReferenceConversion(systemArray, toType, subtypeCheckNestingDepth); } // now comes the hard part: traverse the inheritance chain and figure out generics+variance return IsSubtypeOf(fromType, toType, subtypeCheckNestingDepth); } /// /// For IList{T}, ICollection{T}, IEnumerable{T} and IReadOnlyList{T}, returns T. /// Otherwise, returns null. /// IType UnpackGenericArrayInterface(IType interfaceType) { ParameterizedType pt = interfaceType as ParameterizedType; if (pt != null) { switch (pt.GetDefinition()?.KnownTypeCode) { case KnownTypeCode.IListOfT: case KnownTypeCode.ICollectionOfT: case KnownTypeCode.IEnumerableOfT: case KnownTypeCode.IReadOnlyListOfT: return pt.GetTypeArgument(0); } } return null; } // Determines whether s is a subtype of t. // Helper method used for ImplicitReferenceConversion, BoxingConversion and ImplicitTypeParameterConversion bool IsSubtypeOf(IType s, IType t, int subtypeCheckNestingDepth) { // conversion to dynamic + object are always possible if (t.Kind == TypeKind.Dynamic || t.IsKnownType(KnownTypeCode.Object)) return true; if (subtypeCheckNestingDepth > 10) { // Subtyping in C# is undecidable // (see "On Decidability of Nominal Subtyping with Variance" by Andrew J. Kennedy and Benjamin C. Pierce), // so we'll prevent infinite recursions by putting a limit on the nesting depth of variance conversions. // No real C# code should use generics nested more than 10 levels deep, and even if they do, most of // those nestings should not involve variance. return false; } // let GetAllBaseTypes do the work for us foreach (IType baseType in s.GetAllBaseTypes()) { if (IdentityOrVarianceConversion(baseType, t, subtypeCheckNestingDepth + 1)) return true; } return false; } bool IdentityOrVarianceConversion(IType s, IType t, int subtypeCheckNestingDepth) { ITypeDefinition def = s.GetDefinition(); if (def != null) { if (!def.Equals(t.GetDefinition())) return false; ParameterizedType ps = s as ParameterizedType; ParameterizedType pt = t as ParameterizedType; if (ps != null && pt != null) { // C# 4.0 spec: §13.1.3.2 Variance Conversion for (int i = 0; i < def.TypeParameters.Count; i++) { IType si = ps.GetTypeArgument(i); IType ti = pt.GetTypeArgument(i); if (IdentityConversion(si, ti)) continue; ITypeParameter xi = def.TypeParameters[i]; switch (xi.Variance) { case VarianceModifier.Covariant: if (!ImplicitReferenceConversion(si, ti, subtypeCheckNestingDepth)) return false; break; case VarianceModifier.Contravariant: if (!ImplicitReferenceConversion(ti, si, subtypeCheckNestingDepth)) return false; break; default: return false; } } } else if (ps != null || pt != null) { return false; // only of of them is parameterized, or counts don't match? -> not valid conversion } return true; } else { // not type definitions? we still need to check for equal types (e.g. s and t might be type parameters) return s.Equals(t); } } #endregion #region Explicit Reference Conversion bool ExplicitReferenceConversion(IType fromType, IType toType) { // C# 4.0 spec: §6.2.4 // test that the types are reference types: if (toType.IsReferenceType != true) return false; if (fromType.IsReferenceType != true) { // special case: // converting from F to T is a reference conversion where T : class, F // (because F actually must be a reference type as well, even though C# doesn't treat it as one) if (fromType.Kind == TypeKind.TypeParameter) return IsSubtypeOf(toType, fromType, 0); return false; } if (toType.Kind == TypeKind.Array) { ArrayType toArray = (ArrayType)toType; if (fromType.Kind == TypeKind.Array) { // Array covariance ArrayType fromArray = (ArrayType)fromType; if (fromArray.Dimensions != toArray.Dimensions) return false; return ExplicitReferenceConversion(fromArray.ElementType, toArray.ElementType); } IType fromTypeArgument = UnpackGenericArrayInterface(fromType); if (fromTypeArgument != null && toArray.Dimensions == 1) { return ExplicitReferenceConversion(fromTypeArgument, toArray.ElementType) || IdentityConversion(fromTypeArgument, toArray.ElementType); } // Otherwise treat the array like a sealed class - require implicit conversion in the opposite direction return IsImplicitReferenceConversion(toType, fromType); } else if (fromType.Kind == TypeKind.Array) { ArrayType fromArray = (ArrayType)fromType; IType toTypeArgument = UnpackGenericArrayInterface(toType); if (toTypeArgument != null && fromArray.Dimensions == 1) { return ExplicitReferenceConversion(fromArray.ElementType, toTypeArgument); } // Otherwise treat the array like a sealed class return IsImplicitReferenceConversion(fromType, toType); } else if (fromType.Kind == TypeKind.Delegate && toType.Kind == TypeKind.Delegate) { ITypeDefinition def = fromType.GetDefinition(); if (def == null || !def.Equals(toType.GetDefinition())) return false; ParameterizedType ps = fromType as ParameterizedType; ParameterizedType pt = toType as ParameterizedType; if (ps == null || pt == null) { // non-generic delegate - return true for the identity conversion return ps == null && pt == null; } for (int i = 0; i < def.TypeParameters.Count; i++) { IType si = ps.GetTypeArgument(i); IType ti = pt.GetTypeArgument(i); if (IdentityConversion(si, ti)) continue; ITypeParameter xi = def.TypeParameters[i]; switch (xi.Variance) { case VarianceModifier.Covariant: if (!ExplicitReferenceConversion(si, ti)) return false; break; case VarianceModifier.Contravariant: if (!(si.IsReferenceType == true && ti.IsReferenceType == true)) return false; break; default: return false; } } return true; } else if (IsSealedReferenceType(fromType)) { // If the source type is sealed, explicit conversions can't do anything more than implicit ones return IsImplicitReferenceConversion(fromType, toType); } else if (IsSealedReferenceType(toType)) { // The the target type is sealed, there must be an implicit conversion in the opposite direction return IsImplicitReferenceConversion(toType, fromType); } else { if (fromType.Kind == TypeKind.Interface || toType.Kind == TypeKind.Interface) return true; else return IsImplicitReferenceConversion(toType, fromType) || IsImplicitReferenceConversion(fromType, toType); } } bool IsSealedReferenceType(IType type) { TypeKind kind = type.Kind; return kind == TypeKind.Class && type.GetDefinition().IsSealed || kind == TypeKind.Delegate; } #endregion #region Boxing Conversions bool IsBoxingConversion(IType fromType, IType toType) { // C# 4.0 spec: §6.1.7 fromType = NullableType.GetUnderlyingType(fromType); if (fromType.IsReferenceType == false && toType.IsReferenceType == true) return IsSubtypeOf(fromType, toType, 0); else return false; } /// /// Gets whether the conversion from fromType to toType is a boxing conversion, /// or an implicit conversion involving a type parameter that might be /// a boxing conversion when instantiated with a value type. /// public bool IsBoxingConversionOrInvolvingTypeParameter(IType fromType, IType toType) { return IsBoxingConversion(fromType, toType) || ImplicitTypeParameterConversion(fromType, toType); } bool UnboxingConversion(IType fromType, IType toType) { // C# 4.0 spec: §6.2.5 toType = NullableType.GetUnderlyingType(toType); if (fromType.IsReferenceType == true && toType.IsReferenceType == false) return IsSubtypeOf(toType, fromType, 0); else return false; } #endregion #region Implicit Constant-Expression Conversion bool ImplicitConstantExpressionConversion(ResolveResult rr, IType toType) { if (rr == null || !rr.IsCompileTimeConstant) return false; // C# 4.0 spec: §6.1.9 TypeCode fromTypeCode = ReflectionHelper.GetTypeCode(rr.Type); toType = NullableType.GetUnderlyingType(toType); TypeCode toTypeCode = ReflectionHelper.GetTypeCode(toType); if (toType.Kind == TypeKind.NUInt) { toTypeCode = TypeCode.UInt32; } if (fromTypeCode == TypeCode.Int64) { long val = (long)rr.ConstantValue; return val >= 0 && toTypeCode == TypeCode.UInt64; } else if (fromTypeCode == TypeCode.Int32) { object cv = rr.ConstantValue; if (cv == null) return false; int val = (int)cv; switch (toTypeCode) { case TypeCode.SByte: return val >= SByte.MinValue && val <= SByte.MaxValue; case TypeCode.Byte: return val >= Byte.MinValue && val <= Byte.MaxValue; case TypeCode.Int16: return val >= Int16.MinValue && val <= Int16.MaxValue; case TypeCode.UInt16: return val >= UInt16.MinValue && val <= UInt16.MaxValue; case TypeCode.UInt32: case TypeCode.UInt64: return val >= 0; } } return false; } #endregion #region Conversions involving type parameters /// /// Implicit conversions involving type parameters. /// bool ImplicitTypeParameterConversion(IType fromType, IType toType) { if (fromType.Kind != TypeKind.TypeParameter) return false; // not a type parameter if (fromType.IsReferenceType == true) return false; // already handled by ImplicitReferenceConversion return IsSubtypeOf(fromType, toType, 0); } Conversion ExplicitTypeParameterConversion(IType fromType, IType toType) { if (toType.Kind == TypeKind.TypeParameter) { // Explicit type parameter conversions that aren't also // reference conversions are considered to be unboxing conversions if (fromType.Kind == TypeKind.Interface || IsSubtypeOf(toType, fromType, 0)) return Conversion.UnboxingConversion; } else { if (fromType.Kind == TypeKind.TypeParameter && toType.Kind == TypeKind.Interface) return Conversion.BoxingConversion; } return Conversion.None; } #endregion #region Pointer Conversions bool ImplicitPointerConversion(IType fromType, IType toType) { // C# 4.0 spec: §18.4 Pointer conversions if (fromType.Kind.IsAnyPointer() && toType is PointerType && toType.ReflectionName == "System.Void*") return true; if (fromType.Kind == TypeKind.Null && toType.Kind.IsAnyPointer()) return true; if (fromType is FunctionPointerType fromFnPtr && toType is FunctionPointerType toFnPtr && fromFnPtr.CallingConvention == toFnPtr.CallingConvention && fromFnPtr.ParameterTypes.Length == toFnPtr.ParameterTypes.Length) { // Variance applies to function pointer types const int nestingDepth = 0; if (!(IdentityConversion(fromFnPtr.ReturnType, toFnPtr.ReturnType) || ImplicitReferenceConversion(fromFnPtr.ReturnType, toFnPtr.ReturnType, nestingDepth))) { return false; } foreach (var (fromPT, toPT) in fromFnPtr.ParameterTypes.Zip(toFnPtr.ParameterTypes)) { if (!(IdentityConversion(toPT, fromPT) || ImplicitReferenceConversion(toPT, fromPT, nestingDepth))) { return false; } } return true; } return false; } bool ExplicitPointerConversion(IType fromType, IType toType) { // C# 4.0 spec: §18.4 Pointer conversions if (fromType.Kind.IsAnyPointer()) { return toType.Kind.IsAnyPointer() || IsIntegerType(toType); } else { return toType.Kind.IsAnyPointer() && IsIntegerType(fromType); } } bool IsIntegerType(IType type) { switch (type.Kind) { case TypeKind.NInt: case TypeKind.NUInt: return true; } TypeCode c = ReflectionHelper.GetTypeCode(type); return c >= TypeCode.SByte && c <= TypeCode.UInt64; } #endregion #region User-Defined Conversions /// /// Gets whether type A is encompassed by type B. /// bool IsEncompassedBy(IType a, IType b) { return a.Kind != TypeKind.Interface && b.Kind != TypeKind.Interface && StandardImplicitConversion(a, b).IsValid; } bool IsEncompassingOrEncompassedBy(IType a, IType b) { return a.Kind != TypeKind.Interface && b.Kind != TypeKind.Interface && (StandardImplicitConversion(a, b).IsValid || StandardImplicitConversion(b, a).IsValid); } IType FindMostEncompassedType(IEnumerable candidates) { IType best = null; foreach (var current in candidates) { if (best == null || IsEncompassedBy(current, best)) best = current; else if (!IsEncompassedBy(best, current)) return null; // Ambiguous } return best; } IType FindMostEncompassingType(IEnumerable candidates) { IType best = null; foreach (var current in candidates) { if (best == null || IsEncompassedBy(best, current)) best = current; else if (!IsEncompassedBy(current, best)) return null; // Ambiguous } return best; } Conversion SelectOperator(IType mostSpecificSource, IType mostSpecificTarget, IList operators, bool isImplicit, IType source, IType target) { var selected = operators.Where(op => op.SourceType.Equals(mostSpecificSource) && op.TargetType.Equals(mostSpecificTarget)).ToList(); if (selected.Count == 0) return Conversion.None; if (selected.Count == 1) return Conversion.UserDefinedConversion(selected[0].Method, isLifted: selected[0].IsLifted, isImplicit: isImplicit, conversionBeforeUserDefinedOperator: ExplicitConversion(source, mostSpecificSource), conversionAfterUserDefinedOperator: ExplicitConversion(mostSpecificTarget, target)); int nNonLifted = selected.Count(s => !s.IsLifted); if (nNonLifted == 1) { var op = selected.First(s => !s.IsLifted); return Conversion.UserDefinedConversion(op.Method, isLifted: op.IsLifted, isImplicit: isImplicit, conversionBeforeUserDefinedOperator: ExplicitConversion(source, mostSpecificSource), conversionAfterUserDefinedOperator: ExplicitConversion(mostSpecificTarget, target)); } return Conversion.UserDefinedConversion(selected[0].Method, isLifted: selected[0].IsLifted, isImplicit: isImplicit, isAmbiguous: true, conversionBeforeUserDefinedOperator: ExplicitConversion(source, mostSpecificSource), conversionAfterUserDefinedOperator: ExplicitConversion(mostSpecificTarget, target)); } Conversion UserDefinedImplicitConversion(ResolveResult fromResult, IType fromType, IType toType) { // C# 4.0 spec §6.4.4 User-defined implicit conversions var operators = GetApplicableConversionOperators(fromResult, fromType, toType, false); if (operators.Count > 0) { var mostSpecificSource = operators.Any(op => op.SourceType.Equals(fromType)) ? fromType : FindMostEncompassedType(operators.Select(op => op.SourceType)); if (mostSpecificSource == null) return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: true, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); var mostSpecificTarget = operators.Any(op => op.TargetType.Equals(toType)) ? toType : FindMostEncompassingType(operators.Select(op => op.TargetType)); if (mostSpecificTarget == null) { if (NullableType.IsNullable(toType)) return UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType)); else return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: true, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); } var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators, true, fromType, toType); if (selected != Conversion.None) { if (selected.IsLifted && NullableType.IsNullable(toType)) { // Prefer A -> B -> B? over A -> A? -> B? var other = UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType)); if (other != Conversion.None) return other; } return selected; } else if (NullableType.IsNullable(toType)) return UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType)); else return Conversion.None; } else { return Conversion.None; } } Conversion UserDefinedExplicitConversion(ResolveResult fromResult, IType fromType, IType toType) { // C# 4.0 spec §6.4.5 User-defined explicit conversions var operators = GetApplicableConversionOperators(fromResult, fromType, toType, true); if (operators.Count > 0) { IType mostSpecificSource; if (operators.Any(op => op.SourceType.Equals(fromType))) { mostSpecificSource = fromType; } else { var operatorsWithSourceEncompassingFromType = operators.Where(op => IsEncompassedBy(fromType, op.SourceType) || ImplicitConstantExpressionConversion(fromResult, NullableType.GetUnderlyingType(op.SourceType))).ToList(); if (operatorsWithSourceEncompassingFromType.Any()) mostSpecificSource = FindMostEncompassedType(operatorsWithSourceEncompassingFromType.Select(op => op.SourceType)); else mostSpecificSource = FindMostEncompassingType(operators.Select(op => op.SourceType)); } if (mostSpecificSource == null) return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: false, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); IType mostSpecificTarget; if (operators.Any(op => op.TargetType.Equals(toType))) mostSpecificTarget = toType; else if (operators.Any(op => IsEncompassedBy(op.TargetType, toType))) mostSpecificTarget = FindMostEncompassingType(operators.Where(op => IsEncompassedBy(op.TargetType, toType)).Select(op => op.TargetType)); else mostSpecificTarget = FindMostEncompassedType(operators.Select(op => op.TargetType)); if (mostSpecificTarget == null) { if (NullableType.IsNullable(toType)) return UserDefinedExplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType)); else return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: false, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); } var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators, false, fromType, toType); if (selected != Conversion.None) { if (selected.IsLifted && NullableType.IsNullable(toType)) { // Prefer A -> B -> B? over A -> A? -> B? var other = UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType)); if (other != Conversion.None) return other; } return selected; } else if (NullableType.IsNullable(toType)) return UserDefinedExplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType)); else if (NullableType.IsNullable(fromType)) return UserDefinedExplicitConversion(null, NullableType.GetUnderlyingType(fromType), toType); // A? -> A -> B else return Conversion.None; } else { return Conversion.None; } } class OperatorInfo { public readonly IMethod Method; public readonly IType SourceType; public readonly IType TargetType; public readonly bool IsLifted; public OperatorInfo(IMethod method, IType sourceType, IType targetType, bool isLifted) { this.Method = method; this.SourceType = sourceType; this.TargetType = targetType; this.IsLifted = isLifted; } } static IType UnderlyingTypeForConversion(IType type) { if (type.Kind == TypeKind.ByReference) { type = ((ByReferenceType)type).ElementType; } return NullableType.GetUnderlyingType(type); } List GetApplicableConversionOperators(ResolveResult fromResult, IType fromType, IType toType, bool isExplicit) { // Find the candidate operators: Predicate opFilter; if (isExplicit) opFilter = m => m.IsStatic && m.IsOperator && (m.Name == "op_Explicit" || m.Name == "op_Implicit") && m.Parameters.Count == 1; else opFilter = m => m.IsStatic && m.IsOperator && m.Name == "op_Implicit" && m.Parameters.Count == 1; var operators = UnderlyingTypeForConversion(fromType).GetMethods(opFilter) .Concat(UnderlyingTypeForConversion(toType).GetMethods(opFilter)).Distinct(); // Determine whether one of them is applicable: List result = new List(); foreach (IMethod op in operators) { IType sourceType = op.Parameters[0].Type; if (sourceType.Kind == TypeKind.ByReference && op.Parameters[0].ReferenceKind == ReferenceKind.In && fromType.Kind != TypeKind.ByReference) { sourceType = ((ByReferenceType)sourceType).ElementType; } IType targetType = op.ReturnType; // Try if the operator is applicable: bool isApplicable; if (isExplicit) { isApplicable = (IsEncompassingOrEncompassedBy(fromType, sourceType) || ImplicitConstantExpressionConversion(fromResult, sourceType)) && IsEncompassingOrEncompassedBy(targetType, toType); } else { isApplicable = (IsEncompassedBy(fromType, sourceType) || ImplicitConstantExpressionConversion(fromResult, sourceType)) && IsEncompassedBy(targetType, toType); } // Try if the operator is applicable in lifted form: if (isApplicable) { result.Add(new OperatorInfo(op, sourceType, targetType, false)); } if (NullableType.IsNonNullableValueType(sourceType)) { // An operator can be applicable in both lifted and non-lifted form in case of explicit conversions IType liftedSourceType = NullableType.Create(compilation, sourceType); IType liftedTargetType = NullableType.IsNonNullableValueType(targetType) ? NullableType.Create(compilation, targetType) : targetType; if (isExplicit) { isApplicable = IsEncompassingOrEncompassedBy(fromType, liftedSourceType) && IsEncompassingOrEncompassedBy(liftedTargetType, toType); } else { isApplicable = IsEncompassedBy(fromType, liftedSourceType) && IsEncompassedBy(liftedTargetType, toType); } if (isApplicable) { result.Add(new OperatorInfo(op, liftedSourceType, liftedTargetType, true)); } } } return result; } #endregion #region AnonymousFunctionConversion Conversion AnonymousFunctionConversion(ResolveResult resolveResult, IType toType) { // C# 5.0 spec §6.5 Anonymous function conversions LambdaResolveResult f = resolveResult as LambdaResolveResult; if (f == null) return Conversion.None; if (!f.IsAnonymousMethod) { // It's a lambda, so conversions to expression trees exist // (even if the conversion leads to a compile-time error, e.g. for statement lambdas) toType = UnpackExpressionTreeType(toType); } IMethod d = toType.GetDelegateInvokeMethod(); if (d == null) return Conversion.None; IType[] dParamTypes = new IType[d.Parameters.Count]; for (int i = 0; i < dParamTypes.Length; i++) { dParamTypes[i] = d.Parameters[i].Type; } IType dReturnType = d.ReturnType; if (f.HasParameterList) { // If F contains an anonymous-function-signature, then D and F have the same number of parameters. if (d.Parameters.Count != f.Parameters.Count) return Conversion.None; if (f.IsImplicitlyTyped) { // If F has an implicitly typed parameter list, D has no ref or out parameters. foreach (IParameter p in d.Parameters) { if (p.ReferenceKind != ReferenceKind.None) return Conversion.None; } } else { // If F has an explicitly typed parameter list, each parameter in D has the same type // and modifiers as the corresponding parameter in F. for (int i = 0; i < f.Parameters.Count; i++) { IParameter pD = d.Parameters[i]; IParameter pF = f.Parameters[i]; if (pD.ReferenceKind != pF.ReferenceKind) return Conversion.None; if (!IdentityConversion(dParamTypes[i], pF.Type)) return Conversion.None; } } } else { // If F does not contain an anonymous-function-signature, then D may have zero or more parameters of any // type, as long as no parameter of D has the out parameter modifier. foreach (IParameter p in d.Parameters) { if (p.ReferenceKind == ReferenceKind.Out) return Conversion.None; } } return f.IsValid(dParamTypes, dReturnType, this); } static IType UnpackExpressionTreeType(IType type) { ParameterizedType pt = type as ParameterizedType; if (pt != null && pt.TypeParameterCount == 1 && pt.Name == "Expression" && pt.Namespace == "System.Linq.Expressions") { return pt.GetTypeArgument(0); } else { return type; } } #endregion #region MethodGroupConversion Conversion MethodGroupConversion(ResolveResult resolveResult, IType toType) { // C# 4.0 spec §6.6 Method group conversions MethodGroupResolveResult rr = resolveResult as MethodGroupResolveResult; if (rr == null) return Conversion.None; IMethod invoke = toType.GetDelegateInvokeMethod(); if (invoke == null) return Conversion.None; ResolveResult[] args = new ResolveResult[invoke.Parameters.Count]; for (int i = 0; i < args.Length; i++) { IParameter param = invoke.Parameters[i]; IType parameterType = param.Type; if (param.ReferenceKind != ReferenceKind.None && parameterType.Kind == TypeKind.ByReference) { parameterType = ((ByReferenceType)parameterType).ElementType; args[i] = new ByReferenceResolveResult(parameterType, param.ReferenceKind); } else { args[i] = new ResolveResult(parameterType); } } var or = rr.PerformOverloadResolution( compilation, args, allowExpandingParams: false, allowOptionalParameters: false, allowImplicitIn: false, conversions: this ); if (or.FoundApplicableCandidate) { IMethod method = (IMethod)or.GetBestCandidateWithSubstitutedTypeArguments(); var thisRR = rr.TargetResult as ThisResolveResult; bool isVirtual = method.IsOverridable && !(thisRR != null && thisRR.CausesNonVirtualInvocation); bool isValid = !or.IsAmbiguous && IsDelegateCompatible(method, invoke, or.IsExtensionMethodInvocation); bool delegateCapturesFirstArgument = or.IsExtensionMethodInvocation || !method.IsStatic; if (isValid) return Conversion.MethodGroupConversion(method, isVirtual, delegateCapturesFirstArgument); else return Conversion.InvalidMethodGroupConversion(method, isVirtual, delegateCapturesFirstArgument); } else { return Conversion.None; } } /// /// Gets whether a is compatible with a delegate type. /// §15.2 Delegate compatibility /// /// The method to test for compatibility /// The delegate type public bool IsDelegateCompatible(IMethod method, IType delegateType) { if (method == null) throw new ArgumentNullException(nameof(method)); if (delegateType == null) throw new ArgumentNullException(nameof(delegateType)); IMethod invoke = delegateType.GetDelegateInvokeMethod(); if (invoke == null) return false; return IsDelegateCompatible(method, invoke, false); } /// /// Gets whether a method is compatible with a delegate type. /// §15.2 Delegate compatibility /// /// The method to test for compatibility /// The invoke method of the delegate /// Gets whether m is accessed using extension method syntax. /// If this parameter is true, the first parameter of will be ignored. bool IsDelegateCompatible(IMethod m, IMethod invoke, bool isExtensionMethodInvocation) { if (m == null) throw new ArgumentNullException(nameof(m)); if (invoke == null) throw new ArgumentNullException(nameof(invoke)); int firstParameterInM = isExtensionMethodInvocation ? 1 : 0; if (m.Parameters.Count - firstParameterInM != invoke.Parameters.Count) return false; for (int i = 0; i < invoke.Parameters.Count; i++) { var pm = m.Parameters[firstParameterInM + i]; var pd = invoke.Parameters[i]; // ret/out/in must match if (pm.ReferenceKind != pd.ReferenceKind) return false; if (pm.ReferenceKind != ReferenceKind.None) { // ref/out/in parameters must have same types if (!pm.Type.Equals(pd.Type)) return false; } else { // non-ref/out parameters must have an identity or reference conversion from pd to pm if (!IdentityConversion(pd.Type, pm.Type) && !IsImplicitReferenceConversion(pd.Type, pm.Type)) return false; } } // check return type compatibility return IdentityConversion(m.ReturnType, invoke.ReturnType) || IsImplicitReferenceConversion(m.ReturnType, invoke.ReturnType); } #endregion #region Tuple Conversion Conversion TupleConversion(TupleResolveResult fromRR, IType toType, bool isExplicit) { var fromElements = fromRR.Elements; var toElements = TupleType.GetTupleElementTypes(toType); if (toElements.IsDefault || fromElements.Length != toElements.Length) return Conversion.None; Conversion[] elementConversions = new Conversion[fromElements.Length]; for (int i = 0; i < elementConversions.Length; i++) { Conversion c; if (isExplicit) { c = ExplicitConversion(fromElements[i], toElements[i]); } else { c = ImplicitConversion(fromElements[i], toElements[i]); } if (!c.IsValid) return Conversion.None; elementConversions[i] = c; } return Conversion.TupleConversion(elementConversions.ToImmutableArray()); } Conversion TupleConversion(IType fromType, IType toType, bool isExplicit) { var fromElements = TupleType.GetTupleElementTypes(fromType); if (fromElements.IsDefaultOrEmpty) return Conversion.None; var toElements = TupleType.GetTupleElementTypes(toType); if (toElements.IsDefault || fromElements.Length != toElements.Length) return Conversion.None; Conversion[] elementConversions = new Conversion[fromElements.Length]; for (int i = 0; i < elementConversions.Length; i++) { Conversion c; if (isExplicit) { c = ExplicitConversion(fromElements[i], toElements[i]); } else { c = ImplicitConversion(fromElements[i], toElements[i]); } if (!c.IsValid) return Conversion.None; elementConversions[i] = c; } return Conversion.TupleConversion(elementConversions.ToImmutableArray()); } #endregion #region BetterConversion /// /// Gets the better conversion (C# 4.0 spec, §7.5.3.3) /// /// 0 = neither is better; 1 = t1 is better; 2 = t2 is better public int BetterConversion(ResolveResult resolveResult, IType t1, IType t2) { LambdaResolveResult lambda = resolveResult as LambdaResolveResult; if (lambda != null) { if (!lambda.IsAnonymousMethod) { t1 = UnpackExpressionTreeType(t1); t2 = UnpackExpressionTreeType(t2); } IMethod m1 = t1.GetDelegateInvokeMethod(); IMethod m2 = t2.GetDelegateInvokeMethod(); if (m1 == null || m2 == null) return 0; if (m1.Parameters.Count != m2.Parameters.Count) return 0; IType[] parameterTypes = new IType[m1.Parameters.Count]; for (int i = 0; i < parameterTypes.Length; i++) { parameterTypes[i] = m1.Parameters[i].Type; if (!parameterTypes[i].Equals(m2.Parameters[i].Type)) return 0; } if (lambda.HasParameterList && parameterTypes.Length != lambda.Parameters.Count) return 0; IType ret1 = m1.ReturnType; IType ret2 = m2.ReturnType; if (ret1.Kind == TypeKind.Void && ret2.Kind != TypeKind.Void) return 2; if (ret1.Kind != TypeKind.Void && ret2.Kind == TypeKind.Void) return 1; IType inferredRet = lambda.GetInferredReturnType(parameterTypes); int r = BetterConversion(inferredRet, ret1, ret2); if (r == 0 && lambda.IsAsync) { ret1 = UnpackTask(ret1); ret2 = UnpackTask(ret2); inferredRet = UnpackTask(inferredRet); if (ret1 != null && ret2 != null && inferredRet != null) r = BetterConversion(inferredRet, ret1, ret2); } return r; } else { return BetterConversion(resolveResult.Type, t1, t2); } } /// /// Unpacks the generic Task[T]. Returns null if the input is not Task[T]. /// static IType UnpackTask(IType type) { ParameterizedType pt = type as ParameterizedType; if (pt != null && pt.TypeParameterCount == 1 && pt.Name == "Task" && pt.Namespace == "System.Threading.Tasks") { return pt.GetTypeArgument(0); } return null; } /// /// Gets the better conversion (C# 4.0 spec, §7.5.3.4) /// /// 0 = neither is better; 1 = t1 is better; 2 = t2 is better public int BetterConversion(IType s, IType t1, IType t2) { bool ident1 = IdentityConversion(s, t1); bool ident2 = IdentityConversion(s, t2); if (ident1 && !ident2) return 1; if (ident2 && !ident1) return 2; return BetterConversionTarget(t1, t2); } /// /// Gets the better conversion target (C# 4.0 spec, §7.5.3.5) /// /// 0 = neither is better; 1 = t1 is better; 2 = t2 is better int BetterConversionTarget(IType t1, IType t2) { bool t1To2 = ImplicitConversion(t1, t2).IsValid; bool t2To1 = ImplicitConversion(t2, t1).IsValid; if (t1To2 && !t2To1) return 1; if (t2To1 && !t1To2) return 2; TypeCode t1Code = ReflectionHelper.GetTypeCode(t1); TypeCode t2Code = ReflectionHelper.GetTypeCode(t2); if (IsBetterIntegralType(t1Code, t2Code)) return 1; if (IsBetterIntegralType(t2Code, t1Code)) return 2; return 0; } bool IsBetterIntegralType(TypeCode t1, TypeCode t2) { // signed types are better than unsigned types switch (t1) { case TypeCode.SByte: return t2 == TypeCode.Byte || t2 == TypeCode.UInt16 || t2 == TypeCode.UInt32 || t2 == TypeCode.UInt64; case TypeCode.Int16: return t2 == TypeCode.UInt16 || t2 == TypeCode.UInt32 || t2 == TypeCode.UInt64; case TypeCode.Int32: return t2 == TypeCode.UInt32 || t2 == TypeCode.UInt64; case TypeCode.Int64: return t2 == TypeCode.UInt64; default: return false; } } #endregion } }