// 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
{
///
/// Contains logic that determines whether an implicit conversion exists between two types.
///
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
///
/// 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 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:
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