From 26cc23846c58ce662f7531319a2396280b353081 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 8 Oct 2010 18:59:47 +0200 Subject: [PATCH] Added implicit conversion logic. --- .../TypeSystem/CecilLoaderTests.cs | 2 +- .../CSharp/Resolver/Conversions.cs | 264 ++++++++++++++++++ .../ICSharpCode.NRefactory.csproj | 2 + .../TypeSystem/ArrayType.cs | 11 +- .../TypeSystem/ByReferenceType.cs | 1 - .../TypeSystem/ExtensionMethods.cs | 3 +- ICSharpCode.NRefactory/TypeSystem/IType.cs | 16 +- .../TypeSystem/Implementation/AbstractType.cs | 5 - .../Implementation/TypeWithElementType.cs | 5 +- .../TypeSystem/PointerType.cs | 1 - .../TypeSystem/ReflectionHelper.cs | 34 +++ 11 files changed, 312 insertions(+), 32 deletions(-) create mode 100644 ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs index b785584213..de91bcaa2a 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs @@ -60,7 +60,7 @@ namespace ICSharpCode.NRefactory.TypeSystem IMethod toPointer = c.Methods.Single(p => p.Name == "ToPointer"); Assert.AreEqual("System.Void*", toPointer.ReturnType.Resolve(ctx).DotNetName); Assert.IsInstanceOf(typeof(PointerType), toPointer.ReturnType.Resolve(ctx)); - Assert.AreEqual("System.Void", toPointer.ReturnType.Resolve(ctx).GetElementType().FullName); + Assert.AreEqual("System.Void", ((PointerType)toPointer.ReturnType.Resolve(ctx)).ElementType.FullName); } [Test] diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs b/ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs new file mode 100644 index 0000000000..5b36b1e411 --- /dev/null +++ b/ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs @@ -0,0 +1,264 @@ +// 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 +{ + public class Conversions + { + readonly ITypeResolveContext context; + readonly IType objectType; + + public Conversions(ITypeResolveContext context) + { + if (context == null) + throw new ArgumentNullException("context"); + this.context = context; + this.objectType = context.GetClass(typeof(object)) ?? SharedTypes.UnknownType; + } + + #region ImplicitConversion + public bool ImplicitConversion(IType fromType, IType toType) + { + // C# 4.0 spec: §6.1 + return IdentityConversion(fromType, toType) + || ImplicitNumericConversion(fromType, toType) + || ImplicitEnumerationConversion(fromType, toType) + || ImplicitNullableConversion(fromType, toType) + || NullLiteralConversion(fromType, toType) + || ImplicitReferenceConversion(fromType, toType) + || BoxingConversion(fromType, toType) + || ImplicitConstantExpressionConversion(fromType, toType); + } + #endregion + + #region IdentityConversion + /// + /// Gets whether there is an identity conversion from to + /// + bool IdentityConversion(IType fromType, IType toType) + { + // C# 4.0 spec: §6.1.1 + DynamicErasure erasure = new DynamicErasure(this); + return fromType.AcceptVisitor(erasure).Equals(toType.AcceptVisitor(erasure)); + } + + 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[to - TypeCode.Int16, from - TypeCode.Char]; + } + } + #endregion + + #region ImplicitEnumerationConversion + bool ImplicitEnumerationConversion(IType fromType, IType toType) + { + // C# 4.0 spec: §6.1.3 + // TODO: implement ImplicitEnumerationConversion and ImplicitConstantExpressionConversion + return false; + } + #endregion + + #region ImplicitNullableConversion + bool ImplicitNullableConversion(IType fromType, IType toType) + { + // C# 4.0 spec: §6.1.4 + IType t = UnpackNullable(toType); + if (t != null) { + IType s = UnpackNullable(fromType) ?? fromType; + return IdentityConversion(s, t) || ImplicitNumericConversion(s, t); + } else { + return false; + } + } + + static IType UnpackNullable(IType type) + { + ParameterizedType pt = type as ParameterizedType; + if (pt != null && pt.TypeArguments.Count == 1 && pt.FullName == "System.Nullable") + return pt.TypeArguments[0]; + else + return null; + } + #endregion + + #region NullLiteralConversion + bool NullLiteralConversion(IType fromType, IType toType) + { + // C# 4.0 spec: §6.1.5 + return fromType == SharedTypes.Null && (UnpackNullable(toType) != null); + // This function only handles the conversion from the null literal to nullable value types, + // reference types are handled by ImplicitReferenceConversion instead. + } + #endregion + + #region ImplicitReferenceConversion + 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 == "List" || toPT.Name == "Collection" || toPT.Name == "IEnumerable")) + { + // array covariance plays a part here as well (string[] is IList) + 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; + + 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) + { + // §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; + } + } + } 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 = UnpackNullable(fromType) ?? 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(IType fromType, IType toType) + { + // C# 4.0 spec: §6.1.9 + // TODO: implement ImplicitEnumerationConversion and ImplicitConstantExpressionConversion + return false; + } + #endregion + + // TODO: add support for user-defined conversions + } +} diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index 8afef9a298..168e2366cc 100644 --- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -139,6 +139,7 @@ + @@ -255,6 +256,7 @@ + diff --git a/ICSharpCode.NRefactory/TypeSystem/ArrayType.cs b/ICSharpCode.NRefactory/TypeSystem/ArrayType.cs index 3de57ebeac..cc5babb411 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ArrayType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ArrayType.cs @@ -40,7 +40,7 @@ namespace ICSharpCode.NRefactory.TypeSystem public override int GetHashCode() { - return unchecked(GetElementType().GetHashCode() * 71681 + dimensions); + return unchecked(elementType.GetHashCode() * 71681 + dimensions); } public override bool Equals(IType other) @@ -55,9 +55,11 @@ namespace ICSharpCode.NRefactory.TypeSystem IType t = context.GetClass(typeof(Array)); if (t != null) baseTypes.Add(t); - t = context.GetClass(typeof(List<>)); - if (t != null) - baseTypes.Add(t); + if (dimensions == 1) { // single-dimensional arrays implement IList + t = context.GetClass(typeof(IList<>)); + if (t != null) + baseTypes.Add(t); + } return baseTypes; } @@ -68,7 +70,6 @@ namespace ICSharpCode.NRefactory.TypeSystem public override IType VisitChildren(TypeVisitor visitor) { - IType elementType = GetElementType(); IType e = elementType.AcceptVisitor(visitor); if (e == elementType) return this; diff --git a/ICSharpCode.NRefactory/TypeSystem/ByReferenceType.cs b/ICSharpCode.NRefactory/TypeSystem/ByReferenceType.cs index 0519036f42..6d457dc2f7 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ByReferenceType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ByReferenceType.cs @@ -38,7 +38,6 @@ namespace ICSharpCode.NRefactory.TypeSystem public override IType VisitChildren(TypeVisitor visitor) { - IType elementType = GetElementType(); IType e = elementType.AcceptVisitor(visitor); if (e == elementType) return this; diff --git a/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs b/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs index 71fdf4d510..77dc14901c 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs @@ -17,7 +17,8 @@ namespace ICSharpCode.NRefactory.TypeSystem /// Gets all base types. /// /// This is the reflexive and transitive closure of . - /// Note that this method does not return all supertypes - doing so is impossible due to contravariance. + /// Note that this method does not return all supertypes - doing so is impossible due to contravariance + /// (and underisable for covariance and the list could become very large). /// This method may return an infinite list for certain (invalid) class declarations like class C{T} : C{C{T}} /// TODO: we could ensure a finite list by filtering out cyclic inheritance /// diff --git a/ICSharpCode.NRefactory/TypeSystem/IType.cs b/ICSharpCode.NRefactory/TypeSystem/IType.cs index aa75d0e3ad..5f47fcbc0a 100644 --- a/ICSharpCode.NRefactory/TypeSystem/IType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/IType.cs @@ -20,17 +20,9 @@ namespace ICSharpCode.NRefactory.TypeSystem /// bool? IsReferenceType { get; } - /// - /// Gets the element type of array or pointer types. - /// - /// - /// This type is not an array or pointer type. - /// - IType GetElementType(); - /// /// Gets the underlying type definition. - /// Can return null for types which do not have a type definition (for example type parameters) + /// Can return null for types which do not have a type definition (for example arrays, pointers, type parameters) /// ITypeDefinition GetDefinition(); @@ -193,12 +185,6 @@ namespace ICSharpCode.NRefactory.TypeSystem } } - IType IType.GetElementType() - { - Contract.Ensures(Contract.Result() != null); - return null; - } - ITypeDefinition IType.GetDefinition() { return null; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs index b842cb31ea..b16f69300e 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs @@ -46,11 +46,6 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return null; } } - public virtual IType GetElementType() - { - throw new InvalidOperationException(); - } - public virtual ITypeDefinition GetDefinition() { return null; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs index 48c5c24a1a..f1a7393456 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs @@ -34,9 +34,8 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation public abstract string NameSuffix { get; } - public override IType GetElementType() - { - return elementType; + public IType ElementType { + get { return elementType; } } // Force concrete implementations to override VisitChildren - the base implementation diff --git a/ICSharpCode.NRefactory/TypeSystem/PointerType.cs b/ICSharpCode.NRefactory/TypeSystem/PointerType.cs index 788c0a7389..12b4736201 100644 --- a/ICSharpCode.NRefactory/TypeSystem/PointerType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/PointerType.cs @@ -40,7 +40,6 @@ namespace ICSharpCode.NRefactory.TypeSystem public override IType VisitChildren(TypeVisitor visitor) { - IType elementType = GetElementType(); IType e = elementType.AcceptVisitor(visitor); if (e == elementType) return this; diff --git a/ICSharpCode.NRefactory/TypeSystem/ReflectionHelper.cs b/ICSharpCode.NRefactory/TypeSystem/ReflectionHelper.cs index 82bccff44f..84700fea5d 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ReflectionHelper.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ReflectionHelper.cs @@ -2,6 +2,7 @@ // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; +using System.Collections.Generic; using ICSharpCode.NRefactory.TypeSystem.Implementation; namespace ICSharpCode.NRefactory.TypeSystem @@ -131,5 +132,38 @@ namespace ICSharpCode.NRefactory.TypeSystem return reflectionName; } } + + static readonly Dictionary typeNameToCodeDict = new Dictionary { + { typeof(Boolean).FullName, TypeCode.Boolean }, + { typeof(Byte).FullName, TypeCode.Byte }, + { typeof(Char).FullName, TypeCode.Char }, + { typeof(DateTime).FullName, TypeCode.DateTime }, + { typeof(DBNull).FullName, TypeCode.DBNull }, + { typeof(Decimal).FullName, TypeCode.Decimal }, + { typeof(Double).FullName, TypeCode.Double }, + { typeof(Int16).FullName, TypeCode.Int16 }, + { typeof(Int32).FullName, TypeCode.Int32 }, + { typeof(Int64).FullName, TypeCode.Int64 }, + { typeof(Object).FullName, TypeCode.Object }, + { typeof(SByte).FullName, TypeCode.SByte }, + { typeof(Single).FullName, TypeCode.Single }, + { typeof(String).FullName, TypeCode.String }, + { typeof(UInt16).FullName, TypeCode.UInt16 }, + { typeof(UInt32).FullName, TypeCode.UInt32 }, + { typeof(UInt64).FullName, TypeCode.UInt64 } + }; + + /// + /// Gets the type code for the specified type, or TypeCode.Empty if none of the other type codes matches. + /// + public static TypeCode GetTypeCode(IType type) + { + ITypeDefinition def = type as ITypeDefinition; + TypeCode typeCode; + if (def != null && def.TypeParameterCount == 0 && typeNameToCodeDict.TryGetValue(def.FullName, out typeCode)) + return typeCode; + else + return TypeCode.Empty; + } } }