// 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
{
///
/// Holds information about a conversion between two types.
///
public struct Conversion : IEquatable
{
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);
}
///
/// Creates a new anonymous function conversion.
///
/// Used by ResolveVisitor to pass the LambdaTypeHypothesis.
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;
}
///
/// Gets whether this conversion is an implicit conversion.
///
public bool IsImplicitConversion {
get { return kind > 0 && kind <= lastImplicitConversion; }
}
///
/// Gets whether this conversion is an explicit conversion.
///
public bool IsExplicitConversion {
get { return kind > lastImplicitConversion; }
}
///
/// Gets whether this conversion is user-defined.
///
public bool IsUserDefined {
get {
switch (kind) {
case userDefinedImplicitConversionKind:
case liftedUserDefinedImplicitConversionKind:
case userDefinedExplicitConversionKind:
case liftedUserDefinedExplicitConversionKind:
return true;
default:
return false;
}
}
}
///
/// Gets whether this conversion is a lifted version of a user-defined conversion operator.
///
/// Lifted versions of builtin conversion operators are classified as nullable-conversion
public bool IsLifted {
get {
return kind == liftedUserDefinedImplicitConversionKind || kind == liftedUserDefinedExplicitConversionKind;
}
}
///
/// Gets whether this conversion is a method group conversion.
///
public bool IsMethodGroupConversion {
get { return kind == methodGroupConversionKind; }
}
///
/// Gets whether this conversion is an anonymous function conversion.
///
public bool IsAnonymousFunctionConversion {
get { return kind == anonymousFunctionConversionKind; }
}
///
/// 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.
///
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
}
///
/// Contains logic that determines whether an implicit conversion exists between two types.
///
public class Conversions
{
readonly Dictionary implicitConversionCache = new Dictionary();
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);
}
///
/// Gets the Conversions instance for the specified .
/// This will make use of the context's cache manager (if available) to reuse the Conversions instance.
///
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
{
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;
}
///
/// 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("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
///
/// Gets whether there is an identity conversion from to
///
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:
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