.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
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.
 
 
 
 

386 lines
13 KiB

// Copyright (c) 2010 AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using ICSharpCode.NRefactory.TypeSystem;
namespace ICSharpCode.NRefactory.CSharp.Resolver
{
/// <summary>
/// Contains logic that determines whether an implicit conversion exists between two types.
/// </summary>
public class Conversions : IConversions
{
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);
}
#region ImplicitConversion
public bool ImplicitConversion(ResolveResult resolveResult, IType toType)
{
if (resolveResult == null)
throw new ArgumentNullException("resolveResult");
if (resolveResult.IsCompileTimeConstant && (ImplicitEnumerationConversion(resolveResult, toType) || ImplicitConstantExpressionConversion(resolveResult, toType)))
return true;
return ImplicitConversion(resolveResult.Type, toType);
}
public bool ImplicitConversion(IType fromType, IType toType)
{
if (fromType == null)
throw new ArgumentNullException("fromType");
if (toType == null)
throw new ArgumentNullException("toType");
// C# 4.0 spec: §6.1
if (IdentityConversion(fromType, toType))
return true;
if (ImplicitNumericConversion(fromType, toType))
return true;
if (ImplicitReferenceConversion(fromType, toType))
return true;
if (ImplicitNullableConversion(fromType, toType))
return true;
if (NullLiteralConversion(fromType, toType))
return true;
if (BoxingConversion(fromType, toType))
return true;
if (ImplicitDynamicConversion(fromType, toType))
return true;
if (ImplicitPointerConversion(fromType, toType))
return true;
return false;
}
#endregion
#region IdentityConversion
/// <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 ImplicitNumericConversion
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];
}
}
#endregion
#region ImplicitEnumerationConversion
bool ImplicitEnumerationConversion(ResolveResult rr, IType toType)
{
// C# 4.0 spec: §6.1.3
TypeCode constantType = ReflectionHelper.GetTypeCode(rr.Type);
if (constantType >= TypeCode.SByte && constantType <= TypeCode.Decimal && Convert.ToDouble(rr.ConstantValue) == 0) {
return NullableType.GetUnderlyingType(toType).IsEnum();
}
return false;
}
#endregion
#region ImplicitNullableConversion
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;
}
}
#endregion
#region NullLiteralConversion
bool NullLiteralConversion(IType fromType, IType toType)
{
// C# 4.0 spec: §6.1.5
return fromType == SharedTypes.Null && NullableType.IsNullable(toType);
// This function only handles the conversion from the null literal to nullable value types,
// reference types are handled by ImplicitReferenceConversion instead.
}
#endregion
#region ImplicitReferenceConversion
public 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 != true || toType.IsReferenceType != true)
return false;
// conversion from null literal is always possible
if (fromType == SharedTypes.Null)
return true;
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.TypeArguments.Count == 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.TypeArguments[0])
|| ImplicitReferenceConversion(fromArray.ElementType, toPT.TypeArguments[0]);
}
// conversion from any array to System.Array and the interfaces it implements:
ITypeDefinition systemArray = context.GetClass("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 and BoxingConversion
bool IsSubtypeOf(IType s, IType t)
{
// conversion to dynamic + object are always possible
if (t == 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
&& ps.TypeArguments.Count == pt.TypeArguments.Count
&& ps.TypeArguments.Count == def.TypeParameters.Count)
{
// C# 4.0 spec: §13.1.3.2 Variance Conversion
for (int i = 0; i < def.TypeParameters.Count; i++) {
IType si = ps.TypeArguments[i];
IType ti = pt.TypeArguments[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 BoxingConversion
bool BoxingConversion(IType fromType, IType toType)
{
// C# 4.0 spec: §6.1.7
fromType = NullableType.GetUnderlyingType(fromType);
return fromType.IsReferenceType == false && toType.IsReferenceType == true && IsSubtypeOf(fromType, toType);
}
#endregion
#region ImplicitDynamicConversion
bool ImplicitDynamicConversion(IType fromType, IType toType)
{
// C# 4.0 spec: §6.1.8
return fromType == SharedTypes.Dynamic;
}
#endregion
#region ImplicitConstantExpressionConversion
bool ImplicitConstantExpressionConversion(ResolveResult rr, IType toType)
{
// C# 4.0 spec: §6.1.9
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 ImplicitPointerConversion
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 (fromType == SharedTypes.Null && toType is PointerType)
return true;
return false;
}
#endregion
// TODO: add support for user-defined conversions
#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)
{
// TODO: implement the special logic for anonymous functions
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
}
}