You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1103 lines
38 KiB
1103 lines
38 KiB
// Copyright (c) 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.Generic; |
|
using System.Diagnostics; |
|
using System.Linq; |
|
using ICSharpCode.NRefactory.TypeSystem; |
|
using ICSharpCode.NRefactory.Utils; |
|
|
|
namespace ICSharpCode.NRefactory.CSharp.Resolver |
|
{ |
|
/// <summary> |
|
/// Holds information about a conversion between two types. |
|
/// </summary> |
|
public struct Conversion : IEquatable<Conversion> |
|
{ |
|
public static readonly Conversion None = default(Conversion); |
|
public static readonly Conversion IdentityConversion = new Conversion(1); |
|
public static readonly Conversion ImplicitNumericConversion = new Conversion(2); |
|
public static readonly Conversion ImplicitEnumerationConversion = new Conversion(3); |
|
public static readonly Conversion ImplicitNullableConversion = new Conversion(4); |
|
public static readonly Conversion NullLiteralConversion = new Conversion(5); |
|
public static readonly Conversion ImplicitReferenceConversion = new Conversion(6); |
|
public static readonly Conversion BoxingConversion = new Conversion(7); |
|
public static readonly Conversion ImplicitDynamicConversion = new Conversion(8); |
|
public static readonly Conversion ImplicitConstantExpressionConversion = new Conversion(9); |
|
const int userDefinedImplicitConversionKind = 10; |
|
const int liftedUserDefinedImplicitConversionKind = 11; |
|
public static readonly Conversion ImplicitPointerConversion = new Conversion(12); |
|
const int anonymousFunctionConversionKind = 13; |
|
const int methodGroupConversionKind = 14; |
|
public static readonly Conversion ExplicitNumericConversion = new Conversion(15); |
|
public static readonly Conversion ExplicitEnumerationConversion = new Conversion(16); |
|
public static readonly Conversion ExplicitNullableConversion = new Conversion(17); |
|
public static readonly Conversion ExplicitReferenceConversion = new Conversion(18); |
|
public static readonly Conversion UnboxingConversion = new Conversion(19); |
|
public static readonly Conversion ExplicitDynamicConversion = new Conversion(20); |
|
public static readonly Conversion ExplicitPointerConversion = new Conversion(21); |
|
const int userDefinedExplicitConversionKind = 22; |
|
const int liftedUserDefinedExplicitConversionKind = 23; |
|
|
|
const int lastImplicitConversion = methodGroupConversionKind; |
|
|
|
static readonly string[] conversionNames = { |
|
"None", |
|
"Identity conversion", |
|
"Implicit numeric conversion", |
|
"Implicit enumeration conversion", |
|
"Implicit nullable conversion", |
|
"Null literal conversion", |
|
"Implicit reference conversion", |
|
"Boxing conversion", |
|
"Implicit dynamic conversion", |
|
"Implicit constant expression conversion", |
|
"User-defined implicit conversion", |
|
"Lifted user-defined implicit conversion", |
|
"Implicit pointer conversion", |
|
"Anonymous function conversion", |
|
"Method group conversion", |
|
"Explicit numeric conversion", |
|
"Explicit enumeration conversion", |
|
"Explicit nullable conversion", |
|
"Explicit reference conversion", |
|
"Unboxing conversion", |
|
"Explicit dynamic conversion", |
|
"Explicit pointer conversion", |
|
"User-defined explicit conversion", |
|
"Lifted user-defined explicit conversion" |
|
}; |
|
|
|
public static Conversion UserDefinedImplicitConversion(IMethod operatorMethod, bool isLifted) |
|
{ |
|
if (operatorMethod == null) |
|
throw new ArgumentNullException("operatorMethod"); |
|
return new Conversion(isLifted ? liftedUserDefinedImplicitConversionKind : userDefinedImplicitConversionKind, operatorMethod); |
|
} |
|
|
|
public static Conversion UserDefinedExplicitConversion(IMethod operatorMethod, bool isLifted) |
|
{ |
|
if (operatorMethod == null) |
|
throw new ArgumentNullException("operatorMethod"); |
|
return new Conversion(isLifted ? liftedUserDefinedExplicitConversionKind : userDefinedExplicitConversionKind, operatorMethod); |
|
} |
|
|
|
public static Conversion MethodGroupConversion(IMethod chosenMethod) |
|
{ |
|
if (chosenMethod == null) |
|
throw new ArgumentNullException("chosenMethod"); |
|
return new Conversion(methodGroupConversionKind, chosenMethod); |
|
} |
|
|
|
/// <summary> |
|
/// Creates a new anonymous function conversion. |
|
/// </summary> |
|
/// <param name="data">Used by ResolveVisitor to pass the LambdaTypeHypothesis.</param> |
|
public static Conversion AnonymousFunctionConversion(object data) |
|
{ |
|
return new Conversion(anonymousFunctionConversionKind, data); |
|
} |
|
|
|
readonly int kind; |
|
internal readonly object data; |
|
|
|
public Conversion(int kind, object data = null) |
|
{ |
|
this.kind = kind; |
|
this.data = data; |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether this conversion is an implicit conversion. |
|
/// </summary> |
|
public bool IsImplicitConversion { |
|
get { return kind > 0 && kind <= lastImplicitConversion; } |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether this conversion is an explicit conversion. |
|
/// </summary> |
|
public bool IsExplicitConversion { |
|
get { return kind > lastImplicitConversion; } |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether this conversion is user-defined. |
|
/// </summary> |
|
public bool IsUserDefined { |
|
get { |
|
switch (kind) { |
|
case userDefinedImplicitConversionKind: |
|
case liftedUserDefinedImplicitConversionKind: |
|
case userDefinedExplicitConversionKind: |
|
case liftedUserDefinedExplicitConversionKind: |
|
return true; |
|
default: |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether this conversion is a lifted version of a user-defined conversion operator. |
|
/// </summary> |
|
/// <remarks>Lifted versions of builtin conversion operators are classified as nullable-conversion</remarks> |
|
public bool IsLifted { |
|
get { |
|
return kind == liftedUserDefinedImplicitConversionKind || kind == liftedUserDefinedExplicitConversionKind; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether this conversion is a method group conversion. |
|
/// </summary> |
|
public bool IsMethodGroupConversion { |
|
get { return kind == methodGroupConversionKind; } |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether this conversion is an anonymous function conversion. |
|
/// </summary> |
|
public bool IsAnonymousFunctionConversion { |
|
get { return kind == anonymousFunctionConversionKind; } |
|
} |
|
|
|
/// <summary> |
|
/// Gets the method associated with this conversion. |
|
/// For user-defined conversions, this is the method being called. |
|
/// For method-group conversions, this is the method that was chosen from the group. |
|
/// </summary> |
|
public IMethod Method { |
|
get { return data as IMethod; } |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
string name = conversionNames[kind]; |
|
if (data != null) |
|
return name + " (" + data + ")"; |
|
else |
|
return name; |
|
} |
|
|
|
public bool IsValid { |
|
get { return kind > 0; } |
|
} |
|
|
|
public static implicit operator bool(Conversion conversion) |
|
{ |
|
return conversion.kind > 0; |
|
} |
|
|
|
#region Equals and GetHashCode implementation |
|
public override int GetHashCode() |
|
{ |
|
if (data != null) |
|
return kind ^ data.GetHashCode(); |
|
else |
|
return kind; |
|
} |
|
|
|
public override bool Equals(object obj) |
|
{ |
|
return (obj is Conversion) && Equals((Conversion)obj); |
|
} |
|
|
|
public bool Equals(Conversion other) |
|
{ |
|
return this.kind == other.kind && object.Equals(this.data, other.data); |
|
} |
|
|
|
public static bool operator ==(Conversion lhs, Conversion rhs) |
|
{ |
|
return lhs.Equals(rhs); |
|
} |
|
|
|
public static bool operator !=(Conversion lhs, Conversion rhs) |
|
{ |
|
return !lhs.Equals(rhs); |
|
} |
|
#endregion |
|
} |
|
|
|
/// <summary> |
|
/// Contains logic that determines whether an implicit conversion exists between two types. |
|
/// </summary> |
|
public class Conversions |
|
{ |
|
readonly Dictionary<TypePair, Conversion> implicitConversionCache = new Dictionary<TypePair, Conversion>(); |
|
readonly ITypeResolveContext context; |
|
readonly IType objectType; |
|
|
|
public Conversions(ITypeResolveContext context) |
|
{ |
|
if (context == null) |
|
throw new ArgumentNullException("context"); |
|
this.context = context; |
|
this.objectType = KnownTypeReference.Object.Resolve(context); |
|
this.dynamicErasure = new DynamicErasure(this); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the Conversions instance for the specified <see cref="ITypeResolveContext"/>. |
|
/// This will make use of the context's cache manager (if available) to reuse the Conversions instance. |
|
/// </summary> |
|
public static Conversions Get(ITypeResolveContext context) |
|
{ |
|
CacheManager cache = context.CacheManager; |
|
if (cache != null) { |
|
Conversions conversions = cache.GetThreadLocal(typeof(Conversions)) as Conversions; |
|
if (conversions == null) { |
|
conversions = new Conversions(context); |
|
cache.SetThreadLocal(typeof(Conversions), conversions); |
|
} |
|
return conversions; |
|
} else { |
|
return new Conversions(context); |
|
} |
|
} |
|
|
|
#region TypePair (for caching) |
|
struct TypePair : IEquatable<TypePair> |
|
{ |
|
public readonly IType FromType; |
|
public readonly IType ToType; |
|
|
|
public TypePair(IType fromType, IType toType) |
|
{ |
|
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 this.FromType.Equals(other.FromType) && this.ToType.Equals(other.ToType); |
|
} |
|
|
|
public override int GetHashCode() |
|
{ |
|
unchecked { |
|
return 1000000007 * FromType.GetHashCode() + 1000000009 * ToType.GetHashCode(); |
|
} |
|
} |
|
} |
|
#endregion |
|
|
|
#region ImplicitConversion |
|
public Conversion ImplicitConversion(ResolveResult resolveResult, IType toType) |
|
{ |
|
if (resolveResult == null) |
|
throw new ArgumentNullException("resolveResult"); |
|
if (resolveResult.IsCompileTimeConstant) { |
|
if (ImplicitEnumerationConversion(resolveResult, toType)) |
|
return Conversion.ImplicitEnumerationConversion; |
|
if (ImplicitConstantExpressionConversion(resolveResult, toType)) |
|
return Conversion.ImplicitConstantExpressionConversion; |
|
} |
|
Conversion c; |
|
c = ImplicitConversion(resolveResult.Type, toType); |
|
if (c) return c; |
|
c = AnonymousFunctionConversion(resolveResult, toType); |
|
if (c) return c; |
|
c = MethodGroupConversion(resolveResult, toType); |
|
return c; |
|
} |
|
|
|
public Conversion ImplicitConversion(IType fromType, IType toType) |
|
{ |
|
if (fromType == null) |
|
throw new ArgumentNullException("fromType"); |
|
if (toType == null) |
|
throw new ArgumentNullException("toType"); |
|
|
|
TypePair pair = new TypePair(fromType, toType); |
|
Conversion c; |
|
if (implicitConversionCache.TryGetValue(pair, out c)) |
|
return c; |
|
|
|
// C# 4.0 spec: §6.1 |
|
c = StandardImplicitConversion(fromType, toType); |
|
if (!c) { |
|
c = UserDefinedImplicitConversion(fromType, toType); |
|
} |
|
implicitConversionCache[pair] = c; |
|
return c; |
|
} |
|
|
|
public Conversion StandardImplicitConversion(IType fromType, IType toType) |
|
{ |
|
if (fromType == null) |
|
throw new ArgumentNullException("fromType"); |
|
if (toType == null) |
|
throw new ArgumentNullException("toType"); |
|
// C# 4.0 spec: §6.3.1 |
|
if (IdentityConversion(fromType, toType)) |
|
return Conversion.IdentityConversion; |
|
if (ImplicitNumericConversion(fromType, toType)) |
|
return Conversion.ImplicitNumericConversion; |
|
if (ImplicitNullableConversion(fromType, toType)) |
|
return Conversion.ImplicitNullableConversion; |
|
if (NullLiteralConversion(fromType, toType)) |
|
return Conversion.NullLiteralConversion; |
|
if (ImplicitReferenceConversion(fromType, toType)) |
|
return Conversion.ImplicitReferenceConversion; |
|
if (BoxingConversion(fromType, toType)) |
|
return Conversion.BoxingConversion; |
|
if (fromType.Kind == TypeKind.Dynamic) |
|
return Conversion.ImplicitDynamicConversion; |
|
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; |
|
return Conversion.None; |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether the type 'fromType' is convertible to 'toType' |
|
/// using one of the conversions allowed when satisying constraints (§4.4.4) |
|
/// </summary> |
|
public bool IsConstraintConvertible(IType fromType, IType toType) |
|
{ |
|
if (fromType == null) |
|
throw new ArgumentNullException("fromType"); |
|
if (toType == null) |
|
throw new ArgumentNullException("toType"); |
|
|
|
if (IdentityConversion(fromType, toType)) |
|
return true; |
|
if (ImplicitReferenceConversion(fromType, toType)) |
|
return true; |
|
if (BoxingConversion(fromType, toType) && !NullableType.IsNullable(fromType)) |
|
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("resolveResult"); |
|
if (toType == null) |
|
throw new ArgumentNullException("toType"); |
|
|
|
if (resolveResult.Type.Kind == TypeKind.Dynamic) |
|
return Conversion.ExplicitDynamicConversion; |
|
Conversion c = ImplicitConversion(resolveResult, toType); |
|
if (c) |
|
return c; |
|
else |
|
return ExplicitConversionImpl(resolveResult.Type, toType); |
|
} |
|
|
|
public Conversion ExplicitConversion(IType fromType, IType toType) |
|
{ |
|
if (fromType == null) |
|
throw new ArgumentNullException("fromType"); |
|
if (toType == null) |
|
throw new ArgumentNullException("toType"); |
|
|
|
if (fromType.Kind == TypeKind.Dynamic) |
|
return Conversion.ExplicitDynamicConversion; |
|
Conversion c = ImplicitConversion(fromType, toType); |
|
if (c) |
|
return c; |
|
else |
|
return ExplicitConversionImpl(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.ExplicitEnumerationConversion; |
|
if (ExplicitNullableConversion(fromType, toType)) |
|
return Conversion.ExplicitNullableConversion; |
|
if (ExplicitReferenceConversion(fromType, toType)) |
|
return Conversion.ExplicitReferenceConversion; |
|
if (UnboxingConversion(fromType, toType)) |
|
return Conversion.UnboxingConversion; |
|
if (ExplicitTypeParameterConversion(fromType, toType)) { |
|
// Explicit type parameter conversions that aren't also |
|
// reference conversions are considered to be unboxing conversions |
|
return Conversion.UnboxingConversion; |
|
} |
|
if (ExplicitPointerConversion(fromType, toType)) |
|
return Conversion.ExplicitPointerConversion; |
|
return UserDefinedExplicitConversion(fromType, toType); |
|
} |
|
#endregion |
|
|
|
#region Identity Conversion |
|
/// <summary> |
|
/// Gets whether there is an identity conversion from <paramref name="fromType"/> to <paramref name="toType"/> |
|
/// </summary> |
|
public bool IdentityConversion(IType fromType, IType toType) |
|
{ |
|
// C# 4.0 spec: §6.1.1 |
|
return fromType.AcceptVisitor(dynamicErasure).Equals(toType.AcceptVisitor(dynamicErasure)); |
|
} |
|
|
|
readonly DynamicErasure dynamicErasure; |
|
|
|
sealed class DynamicErasure : TypeVisitor |
|
{ |
|
readonly IType objectType; |
|
|
|
public DynamicErasure(Conversions conversions) |
|
{ |
|
this.objectType = conversions.objectType; |
|
} |
|
|
|
public override IType VisitOtherType(IType type) |
|
{ |
|
if (type == SharedTypes.Dynamic) |
|
return objectType; |
|
else |
|
return base.VisitOtherType(type); |
|
} |
|
} |
|
#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 */ { false, false, true , false, true , false }, |
|
/* ushort */ { false, false, true , true , true , true }, |
|
/* int */ { false, false, false, false, true , false }, |
|
/* uint */ { false, false, false, false, true , true }, |
|
}; |
|
|
|
bool ImplicitNumericConversion(IType fromType, IType toType) |
|
{ |
|
// C# 4.0 spec: §6.1.2 |
|
|
|
TypeCode from = ReflectionHelper.GetTypeCode(fromType); |
|
TypeCode to = ReflectionHelper.GetTypeCode(toType); |
|
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.UInt32 |
|
&& to >= TypeCode.Int16 && to <= TypeCode.UInt64 |
|
&& implicitNumericConversionLookup[from - TypeCode.Char, to - TypeCode.Int16]; |
|
} |
|
} |
|
|
|
bool IsNumericType(IType type) |
|
{ |
|
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 |
|
bool 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) { |
|
return NullableType.GetUnderlyingType(toType).Kind == TypeKind.Enum; |
|
} |
|
return false; |
|
} |
|
|
|
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 |
|
bool 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 |
|
return IdentityConversion(s, t) || ImplicitNumericConversion(s, t); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
bool 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); |
|
return IdentityConversion(s, t) || AnyNumericConversion(s, t) || ExplicitEnumerationConversion(s, t); |
|
} else { |
|
return false; |
|
} |
|
} |
|
#endregion |
|
|
|
#region Null Literal Conversion |
|
bool NullLiteralConversion(IType fromType, IType toType) |
|
{ |
|
// C# 4.0 spec: §6.1.5 |
|
if (SharedTypes.Null.Equals(fromType)) { |
|
return NullableType.IsNullable(toType) || toType.IsReferenceType(context) == true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
#endregion |
|
|
|
#region Implicit Reference Conversion |
|
bool ImplicitReferenceConversion(IType fromType, IType toType) |
|
{ |
|
// C# 4.0 spec: §6.1.6 |
|
|
|
// reference conversions are possible only if both types are known to be reference types |
|
if (!(fromType.IsReferenceType(context) == true && toType.IsReferenceType(context) == true)) |
|
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); |
|
} |
|
// conversion from single-dimensional array S[] to IList<T>: |
|
ParameterizedType toPT = toType as ParameterizedType; |
|
if (fromArray.Dimensions == 1 && toPT != null && toPT.TypeParameterCount == 1 |
|
&& toPT.Namespace == "System.Collections.Generic" |
|
&& (toPT.Name == "IList" || toPT.Name == "ICollection" || toPT.Name == "IEnumerable")) |
|
{ |
|
// array covariance plays a part here as well (string[] is IList<object>) |
|
return IdentityConversion(fromArray.ElementType, toPT.GetTypeArgument(0)) |
|
|| ImplicitReferenceConversion(fromArray.ElementType, toPT.GetTypeArgument(0)); |
|
} |
|
// conversion from any array to System.Array and the interfaces it implements: |
|
ITypeDefinition systemArray = context.GetTypeDefinition("System", "Array", 0, StringComparer.Ordinal); |
|
return systemArray != null && (systemArray.Equals(toType) || ImplicitReferenceConversion(systemArray, toType)); |
|
} |
|
|
|
// now comes the hard part: traverse the inheritance chain and figure out generics+variance |
|
return IsSubtypeOf(fromType, toType); |
|
} |
|
|
|
// Determines whether s is a subtype of t. |
|
// Helper method used for ImplicitReferenceConversion, BoxingConversion and ImplicitTypeParameterConversion |
|
bool IsSubtypeOf(IType s, IType t) |
|
{ |
|
// conversion to dynamic + object are always possible |
|
if (t.Equals(SharedTypes.Dynamic) || t.Equals(objectType)) |
|
return true; |
|
|
|
// let GetAllBaseTypes do the work for us |
|
foreach (IType baseType in s.GetAllBaseTypes(context)) { |
|
if (IdentityOrVarianceConversion(baseType, t)) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
bool IdentityOrVarianceConversion(IType s, IType t) |
|
{ |
|
ITypeDefinition def = s.GetDefinition(); |
|
if (def != null && def.Equals(t.GetDefinition())) { |
|
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)) |
|
return false; |
|
break; |
|
case VarianceModifier.Contravariant: |
|
if (!ImplicitReferenceConversion(ti, si)) |
|
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; |
|
} |
|
return false; |
|
} |
|
#endregion |
|
|
|
#region Explicit Reference Conversion |
|
bool ExplicitReferenceConversion(IType fromType, IType toType) |
|
{ |
|
// C# 4.0 spec: §6.2.4 |
|
|
|
// reference conversions are possible only if both types are known to be reference types |
|
if (!(fromType.IsReferenceType(context) == true && toType.IsReferenceType(context) == true)) |
|
return false; |
|
|
|
// There's lots of additional rules, but they're not really relevant, |
|
// as they are only used to identify invalid casts, and we don't care about reporting those. |
|
return true; |
|
} |
|
#endregion |
|
|
|
#region Boxing Conversions |
|
bool BoxingConversion(IType fromType, IType toType) |
|
{ |
|
// C# 4.0 spec: §6.1.7 |
|
fromType = NullableType.GetUnderlyingType(fromType); |
|
if (fromType.IsReferenceType(context) == false && toType.IsReferenceType(context) == true) |
|
return IsSubtypeOf(fromType, toType); |
|
else |
|
return false; |
|
} |
|
|
|
bool UnboxingConversion(IType fromType, IType toType) |
|
{ |
|
// C# 4.0 spec: §6.2.5 |
|
toType = NullableType.GetUnderlyingType(toType); |
|
if (fromType.IsReferenceType(context) == true && toType.IsReferenceType(context) == false) |
|
return IsSubtypeOf(toType, fromType); |
|
else |
|
return false; |
|
} |
|
#endregion |
|
|
|
#region Implicit Constant-Expression Conversion |
|
bool ImplicitConstantExpressionConversion(ResolveResult rr, IType toType) |
|
{ |
|
// C# 4.0 spec: §6.1.9 |
|
Debug.Assert(rr.IsCompileTimeConstant); |
|
TypeCode fromTypeCode = ReflectionHelper.GetTypeCode(rr.Type); |
|
TypeCode toTypeCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(toType)); |
|
if (fromTypeCode == TypeCode.Int64) { |
|
long val = (long)rr.ConstantValue; |
|
return val >= 0 && toTypeCode == TypeCode.UInt64; |
|
} else if (fromTypeCode == TypeCode.Int32) { |
|
int val = (int)rr.ConstantValue; |
|
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: |
|
return val >= 0; |
|
case TypeCode.UInt64: |
|
return val >= 0; |
|
} |
|
} |
|
return false; |
|
} |
|
#endregion |
|
|
|
#region Conversions involving type parameters |
|
/// <summary> |
|
/// Implicit conversions involving type parameters. |
|
/// </summary> |
|
bool ImplicitTypeParameterConversion(IType fromType, IType toType) |
|
{ |
|
if (fromType.Kind != TypeKind.TypeParameter) |
|
return false; // not a type parameter |
|
if (fromType.IsReferenceType(context) == true) |
|
return false; // already handled by ImplicitReferenceConversion |
|
return IsSubtypeOf(fromType, toType); |
|
} |
|
|
|
bool ExplicitTypeParameterConversion(IType fromType, IType toType) |
|
{ |
|
if (toType.Kind == TypeKind.TypeParameter) { |
|
return fromType.Kind == TypeKind.TypeParameter || fromType.IsReferenceType(context) == true; |
|
} else { |
|
return fromType.Kind == TypeKind.TypeParameter && toType.Kind == TypeKind.Interface; |
|
} |
|
} |
|
#endregion |
|
|
|
#region Pointer Conversions |
|
bool ImplicitPointerConversion(IType fromType, IType toType) |
|
{ |
|
// C# 4.0 spec: §18.4 Pointer conversions |
|
if (fromType is PointerType && toType is PointerType && toType.ReflectionName == "System.Void*") |
|
return true; |
|
if (SharedTypes.Null.Equals(fromType) && toType is PointerType) |
|
return true; |
|
return false; |
|
} |
|
|
|
bool ExplicitPointerConversion(IType fromType, IType toType) |
|
{ |
|
// C# 4.0 spec: §18.4 Pointer conversions |
|
if (fromType.Kind == TypeKind.Pointer) { |
|
return toType.Kind == TypeKind.Pointer || IsIntegerType(toType); |
|
} else { |
|
return toType.Kind == TypeKind.Pointer && IsIntegerType(fromType); |
|
} |
|
} |
|
|
|
bool IsIntegerType(IType type) |
|
{ |
|
TypeCode c = ReflectionHelper.GetTypeCode(type); |
|
return c >= TypeCode.SByte && c <= TypeCode.UInt64; |
|
} |
|
#endregion |
|
|
|
#region User-Defined Conversions |
|
/// <summary> |
|
/// Gets whether type A is encompassed by type B. |
|
/// </summary> |
|
bool IsEncompassedBy(IType a, IType b) |
|
{ |
|
return a.Kind != TypeKind.Interface && b.Kind != TypeKind.Interface && StandardImplicitConversion(a, b); |
|
} |
|
|
|
bool IsEncompassingOrEncompassedBy(IType a, IType b) |
|
{ |
|
return a.Kind != TypeKind.Interface && b.Kind != TypeKind.Interface |
|
&& (StandardImplicitConversion(a, b) || StandardImplicitConversion(b, a)); |
|
} |
|
|
|
Conversion UserDefinedImplicitConversion(IType fromType, IType toType) |
|
{ |
|
// C# 4.0 spec §6.4.4 User-defined implicit conversions |
|
var operators = GetApplicableConversionOperators(fromType, toType, false); |
|
// TODO: Find most specific conversion |
|
if (operators.Count > 0) |
|
return Conversion.UserDefinedImplicitConversion(operators[0].Method, operators[0].IsLifted); |
|
else |
|
return Conversion.None; |
|
} |
|
|
|
Conversion UserDefinedExplicitConversion(IType fromType, IType toType) |
|
{ |
|
// C# 4.0 spec §6.4.5 User-defined implicit conversions |
|
var operators = GetApplicableConversionOperators(fromType, toType, true); |
|
// TODO: Find most specific conversion |
|
if (operators.Count > 0) |
|
return Conversion.UserDefinedExplicitConversion(operators[0].Method, operators[0].IsLifted); |
|
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; |
|
} |
|
} |
|
|
|
List<OperatorInfo> GetApplicableConversionOperators(IType fromType, IType toType, bool isExplicit) |
|
{ |
|
// Find the candidate operators: |
|
Predicate<IMethod> opFilter; |
|
if (isExplicit) |
|
opFilter = m => m.IsStatic && m.IsOperator && m.Name == "op_Explicit" && m.Parameters.Count == 1; |
|
else |
|
opFilter = m => m.IsStatic && m.IsOperator && m.Name == "op_Implicit" && m.Parameters.Count == 1; |
|
|
|
var operators = NullableType.GetUnderlyingType(fromType).GetMethods(context, opFilter) |
|
.Concat(NullableType.GetUnderlyingType(toType).GetMethods(context, opFilter)).Distinct(); |
|
// Determine whether one of them is applicable: |
|
List<OperatorInfo> result = new List<OperatorInfo>(); |
|
foreach (IMethod op in operators) { |
|
IType sourceType = op.Parameters[0].Type.Resolve(context); |
|
IType targetType = op.ReturnType.Resolve(context); |
|
// Try if the operator is applicable: |
|
bool isApplicable; |
|
if (isExplicit) { |
|
isApplicable = IsEncompassingOrEncompassedBy(fromType, sourceType) |
|
&& IsEncompassingOrEncompassedBy(targetType, toType); |
|
} else { |
|
isApplicable = IsEncompassedBy(fromType, sourceType) && IsEncompassedBy(targetType, toType); |
|
} |
|
if (isApplicable) { |
|
result.Add(new OperatorInfo(op, sourceType, targetType, false)); |
|
} |
|
// Try if the operator is applicable in lifted form: |
|
if (NullableType.IsNonNullableValueType(sourceType, context) |
|
&& NullableType.IsNonNullableValueType(targetType, context)) |
|
{ |
|
IType liftedSourceType = NullableType.Create(sourceType, context); |
|
IType liftedTargetType = NullableType.Create(targetType, context); |
|
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# 4.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.Resolve(context); |
|
} |
|
IType dReturnType = d.ReturnType.Resolve(context); |
|
|
|
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.IsOut || p.IsRef) |
|
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.IsRef != pF.IsRef || pD.IsOut != pF.IsOut) |
|
return Conversion.None; |
|
if (!dParamTypes[i].Equals(pF.Type.Resolve(context))) |
|
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.IsOut) |
|
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 m = toType.GetDelegateInvokeMethod(); |
|
if (m == null) |
|
return Conversion.None; |
|
|
|
ResolveResult[] args = new ResolveResult[m.Parameters.Count]; |
|
for (int i = 0; i < args.Length; i++) { |
|
IParameter param = m.Parameters[i]; |
|
IType parameterType = param.Type.Resolve(context); |
|
if ((param.IsRef || param.IsOut) && parameterType.Kind == TypeKind.ByReference) { |
|
parameterType = ((ByReferenceType)parameterType).ElementType; |
|
args[i] = new ByReferenceResolveResult(parameterType, param.IsOut); |
|
} else { |
|
args[i] = new ResolveResult(parameterType); |
|
} |
|
} |
|
var or = rr.PerformOverloadResolution(context, args, allowExpandingParams: false, conversions: this); |
|
if (or.FoundApplicableCandidate) |
|
return Conversion.MethodGroupConversion((IMethod)or.BestCandidate); |
|
else |
|
return Conversion.None; |
|
} |
|
#endregion |
|
|
|
#region BetterConversion |
|
/// <summary> |
|
/// Gets the better conversion (C# 4.0 spec, §7.5.3.3) |
|
/// </summary> |
|
/// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns> |
|
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; |
|
int r = BetterConversionTarget(t1, t2); |
|
if (r != 0) |
|
return r; |
|
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.Resolve(context); |
|
if (!parameterTypes[i].Equals(m2.Parameters[i].Type.Resolve(context))) |
|
return 0; |
|
} |
|
if (lambda.HasParameterList && parameterTypes.Length != lambda.Parameters.Count) |
|
return 0; |
|
|
|
IType ret1 = m1.ReturnType.Resolve(context); |
|
IType ret2 = m2.ReturnType.Resolve(context); |
|
if (ret1.Kind == TypeKind.Void && ret2.Kind != TypeKind.Void) |
|
return 1; |
|
if (ret1.Kind != TypeKind.Void && ret2.Kind == TypeKind.Void) |
|
return 2; |
|
|
|
IType inferredRet = lambda.GetInferredReturnType(parameterTypes); |
|
return BetterConversion(inferredRet, ret1, ret2); |
|
} else { |
|
return BetterConversion(resolveResult.Type, t1, t2); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets the better conversion (C# 4.0 spec, §7.5.3.4) |
|
/// </summary> |
|
/// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns> |
|
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); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the better conversion target (C# 4.0 spec, §7.5.3.5) |
|
/// </summary> |
|
/// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns> |
|
int BetterConversionTarget(IType t1, IType t2) |
|
{ |
|
bool t1To2 = ImplicitConversion(t1, t2); |
|
bool t2To1 = ImplicitConversion(t2, t1); |
|
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 |
|
} |
|
}
|
|
|