diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/SimpleNameLookupTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/SimpleNameLookupTests.cs index 1d617dd2a9..0ff2a51dca 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/SimpleNameLookupTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/SimpleNameLookupTests.cs @@ -51,6 +51,14 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver Assert.AreEqual("System.String", trr.Type.FullName); } + [Test] + public void UnknownIdentifierTest() + { + UnknownIdentifierResolveResult uirr = (UnknownIdentifierResolveResult)resolver.ResolveSimpleName("xyz", new IType[0]); + Assert.IsTrue(uirr.IsError); + Assert.AreEqual("xyz", uirr.Identifier); + } + [Test] public void GlobalIsUnknownIdentifier() { diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 451138888a..e07f77c2d9 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -132,6 +132,7 @@ + diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/CommonTypeInferenceTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/CommonTypeInferenceTests.cs new file mode 100644 index 0000000000..39cd48ea25 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/CommonTypeInferenceTests.cs @@ -0,0 +1,74 @@ +// Copyright (c) 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; +using System.Collections.Generic; +using System.Linq; +using ICSharpCode.NRefactory.CSharp.Resolver; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.TypeSystem +{ + [TestFixture] + public class CommonTypeInferenceTests + { + CommonTypeInference cti = new CommonTypeInference(CecilLoaderTests.Mscorlib, new Conversions(CecilLoaderTests.Mscorlib)); + + IType[] Resolve(params Type[] types) + { + IType[] r = new IType[types.Length]; + for (int i = 0; i < types.Length; i++) { + r[i] = types[i].ToTypeReference().Resolve(CecilLoaderTests.Mscorlib); + Assert.AreNotSame(r[i], SharedTypes.UnknownType); + } + Array.Sort(r, (a,b)=>a.ReflectionName.CompareTo(b.ReflectionName)); + return r; + } + + IType[] CommonBaseTypes(params Type[] types) + { + return cti.CommonBaseTypes(Resolve(types)).OrderBy(r => r.ReflectionName).ToArray(); + } + + [Test] + public void ListOfStringAndObject() + { + Assert.AreEqual( + Resolve(typeof(IList), typeof(IEnumerable)), + CommonBaseTypes(typeof(List), typeof(List))); + } + + [Test] + public void ListOfListOfStringAndObject() + { + Assert.AreEqual( + Resolve(typeof(IList), typeof(IEnumerable), typeof(IEnumerable>)), + CommonBaseTypes(typeof(List>), typeof(List>))); + } + + [Test] + public void ShortAndInt() + { + Assert.AreEqual( + Resolve(typeof(int)), + CommonBaseTypes(typeof(short), typeof(int))); + } + + [Test] + public void ListOfShortAndInt() + { + Assert.AreEqual( + Resolve(typeof(IList)), + CommonBaseTypes(typeof(List), typeof(List))); + } + + [Test] + public void StringAndVersion() + { + Assert.AreEqual( + Resolve(typeof(ICloneable), typeof(IComparable)), + CommonBaseTypes(typeof(string), typeof(Version))); + } + } +} diff --git a/ICSharpCode.NRefactory/CSharp/Dom/Expressions/AssignmentExpression.cs b/ICSharpCode.NRefactory/CSharp/Dom/Expressions/AssignmentExpression.cs index de94a0692e..eb380d9701 100644 --- a/ICSharpCode.NRefactory/CSharp/Dom/Expressions/AssignmentExpression.cs +++ b/ICSharpCode.NRefactory/CSharp/Dom/Expressions/AssignmentExpression.cs @@ -1,4 +1,4 @@ -// +// // AssignmentExpression.cs // // Author: @@ -75,12 +75,12 @@ namespace ICSharpCode.NRefactory.CSharp /// left %= right Modulus, - /// left <<= right + /// left <<= right ShiftLeft, /// left >>= right ShiftRight, - /// left &= right + /// left &= right BitwiseAnd, /// left |= right BitwiseOr, diff --git a/ICSharpCode.NRefactory/CSharp/Dom/Expressions/UnaryOperatorExpression.cs b/ICSharpCode.NRefactory/CSharp/Dom/Expressions/UnaryOperatorExpression.cs index 6476192dc5..fb3a1f8bca 100644 --- a/ICSharpCode.NRefactory/CSharp/Dom/Expressions/UnaryOperatorExpression.cs +++ b/ICSharpCode.NRefactory/CSharp/Dom/Expressions/UnaryOperatorExpression.cs @@ -1,4 +1,4 @@ -// +// // UnaryOperatorExpression.cs // // Author: @@ -68,7 +68,7 @@ namespace ICSharpCode.NRefactory.CSharp PostDecrement, /// Dereferencing (*a) Dereference, - /// Get address (&a) + /// Get address (&a) AddressOf } diff --git a/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs b/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs index 1c171c582d..e9b3646a6f 100644 --- a/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs @@ -184,7 +184,7 @@ namespace ICSharpCode.NRefactory.CSharp } /// - /// Adds the 'Invoke', 'BeginInvoke', 'EndInvoke' methods, and a constructor, to the . + /// Adds the 'Invoke', 'BeginInvoke', 'EndInvoke' methods, and a constructor, to the . /// public static void AddDefaultMethodsToDelegate(DefaultTypeDefinition delegateType, ITypeReference returnType, IEnumerable parameters) { diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs index f45198af93..a53c44ab6b 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; +using System.Threading; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; @@ -22,13 +23,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver static readonly ResolveResult NullResult = new ResolveResult(SharedTypes.Null); readonly ITypeResolveContext context; + internal readonly CancellationToken cancellationToken; #region Constructor - public CSharpResolver(ITypeResolveContext context) + public CSharpResolver(ITypeResolveContext context, CancellationToken cancellationToken = default(CancellationToken)) { if (context == null) throw new ArgumentNullException("context"); this.context = context; + this.cancellationToken = cancellationToken; } #endregion @@ -335,6 +338,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #region ResolveUnaryOperator method public ResolveResult ResolveUnaryOperator(UnaryOperatorType op, ResolveResult expression) { + cancellationToken.ThrowIfCancellationRequested(); + if (expression.Type == SharedTypes.Dynamic) return DynamicResult; @@ -584,6 +589,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #region ResolveBinaryOperator method public ResolveResult ResolveBinaryOperator(BinaryOperatorType op, ResolveResult lhs, ResolveResult rhs) { + cancellationToken.ThrowIfCancellationRequested(); + if (lhs.Type == SharedTypes.Dynamic || rhs.Type == SharedTypes.Dynamic) return DynamicResult; @@ -1427,6 +1434,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #region ResolveCast public ResolveResult ResolveCast(IType targetType, ResolveResult expression) { + cancellationToken.ThrowIfCancellationRequested(); + // C# 4.0 spec: §7.7.6 Cast expressions if (expression.IsCompileTimeConstant) { TypeCode code = ReflectionHelper.GetTypeCode(targetType); @@ -1504,8 +1513,12 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } } - return LookupSimpleNameOrTypeName(identifier, typeArguments, - isInvocationTarget ? SimpleNameLookupMode.InvocationTarget : SimpleNameLookupMode.Expression); + ResolveResult rr = LookupSimpleNameOrTypeName( + identifier, typeArguments, + isInvocationTarget ? SimpleNameLookupMode.InvocationTarget : SimpleNameLookupMode.Expression); + if (rr == ErrorResult && typeArguments.Count == 0) + rr = new UnknownIdentifierResolveResult(identifier); + return rr; } public ResolveResult LookupSimpleNamespaceOrTypeName(string identifier, IList typeArguments, bool isUsingDeclaration = false) @@ -1523,6 +1536,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { // C# 4.0 spec: §3.8 Namespace and type names; §7.6.2 Simple Names + cancellationToken.ThrowIfCancellationRequested(); + int k = typeArguments.Count; // look in type parameters of current method @@ -1629,6 +1644,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { if (identifier == "global") return new NamespaceResolveResult(string.Empty); + for (UsingScope n = this.UsingScope; n != null; n = n.Parent) { if (n.ExternAliases.Contains(identifier)) { return ResolveExternAlias(identifier); @@ -1654,6 +1670,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { // C# 4.0 spec: §7.6.4 + cancellationToken.ThrowIfCancellationRequested(); + NamespaceResolveResult nrr = target as NamespaceResolveResult; if (nrr != null) { string fullName = NamespaceDeclaration.BuildQualifiedName(nrr.NamespaceName, identifier); @@ -1684,6 +1702,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver public ResolveResult ResolveInvocation(ResolveResult target, ResolveResult[] arguments, string[] argumentNames = null) { // C# 4.0 spec: §7.6.5 + + cancellationToken.ThrowIfCancellationRequested(); + if (target.Type == SharedTypes.Dynamic) return DynamicResult; @@ -1703,6 +1724,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (umrr != null) { return new UnknownMethodResolveResult(umrr.TargetType, umrr.MemberName, umrr.TypeArguments, CreateParameters(arguments, argumentNames)); } + UnknownIdentifierResolveResult uirr = target as UnknownIdentifierResolveResult; + if (uirr != null && CurrentTypeDefinition != null) { + return new UnknownMethodResolveResult(CurrentTypeDefinition, uirr.Identifier, EmptyList.Instance, CreateParameters(arguments, argumentNames)); + } IMethod invokeMethod = target.Type.GetDelegateInvokeMethod(); if (invokeMethod != null) { return new ResolveResult(invokeMethod.ReturnType.Resolve(context)); @@ -1795,6 +1820,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #region ResolveIndexer public ResolveResult ResolveIndexer(ResolveResult target, ResolveResult[] arguments, string[] argumentNames = null) { + cancellationToken.ThrowIfCancellationRequested(); + if (target.Type == SharedTypes.Dynamic) return DynamicResult; @@ -1818,6 +1845,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #region ResolveObjectCreation public ResolveResult ResolveObjectCreation(IType type, ResolveResult[] arguments, string[] argumentNames = null) { + cancellationToken.ThrowIfCancellationRequested(); + OverloadResolution or = new OverloadResolution(context, arguments, argumentNames, new IType[0]); MemberLookup lookup = CreateMemberLookup(); bool allowProtectedAccess = lookup.AllowProtectedAccess(type); @@ -1904,6 +1933,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver public ResolveResult ResolveConditional(ResolveResult trueExpression, ResolveResult falseExpression) { // C# 4.0 spec §7.14: Conditional operator + + cancellationToken.ThrowIfCancellationRequested(); + Conversions c = new Conversions(context); bool isValid; IType resultType; diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs b/ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs index 1ea89789b5..3609f1c208 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/Conversions.cs @@ -11,7 +11,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// /// Contains logic that determines whether an implicit conversion exists between two types. /// - public class Conversions + public class Conversions : IConversions { readonly ITypeResolveContext context; readonly IType objectType; @@ -163,7 +163,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #endregion #region ImplicitReferenceConversion - bool ImplicitReferenceConversion(IType fromType, IType toType) + public bool ImplicitReferenceConversion(IType fromType, IType toType) { // C# 4.0 spec: §6.1.6 diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/MemberLookup.cs b/ICSharpCode.NRefactory/CSharp/Resolver/MemberLookup.cs index 3dee886402..c7af0f0235 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/MemberLookup.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/MemberLookup.cs @@ -54,9 +54,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// /// Gets whether is accessible in the current class. /// - /// The entity to test + /// The entity to test /// Whether protected access is allowed. - /// True if the type of the reference is derived from the current class. + /// True if the type of the reference is derived from the current class. public bool IsAccessible(IEntity entity, bool allowProtectedAccess) { if (entity == null) diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs b/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs index 5e06052019..b513623f49 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/UnknownMemberResolveResult.cs b/ICSharpCode.NRefactory/CSharp/Resolver/UnknownMemberResolveResult.cs index 56e8311b2e..189ae6ad1a 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/UnknownMemberResolveResult.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/UnknownMemberResolveResult.cs @@ -65,9 +65,32 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver public ReadOnlyCollection Parameters { get { return parameters; } } + } + + /// + /// Represents an unknown identifier. + /// + public class UnknownIdentifierResolveResult : ResolveResult + { + readonly string identifier; + + public UnknownIdentifierResolveResult(string identifier) + : base(SharedTypes.UnknownType) + { + this.identifier = identifier; + } + + public string Identifier { + get { return identifier; } + } public override bool IsError { get { return true; } } + + public override string ToString() + { + return string.Format("[{0} {1}]", GetType().Name, identifier); + } } } diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index da29f3737d..c39125f19e 100644 --- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -14,6 +14,7 @@ False False false + 1591 AnyCPU @@ -39,7 +40,7 @@ false 4 True - True + true None @@ -189,12 +190,14 @@ + + diff --git a/ICSharpCode.NRefactory/TypeSystem/CommonTypeInference.cs b/ICSharpCode.NRefactory/TypeSystem/CommonTypeInference.cs new file mode 100644 index 0000000000..d947923eda --- /dev/null +++ b/ICSharpCode.NRefactory/TypeSystem/CommonTypeInference.cs @@ -0,0 +1,342 @@ +// Copyright (c) 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 System.Linq; +using System.Text; + +namespace ICSharpCode.NRefactory.TypeSystem +{ + /// + /// Inference engine for common base type / common super type. + /// This is not used in the C# resolver, as C# does not have this kind of powerful type inference. + /// The logic used by C# is implemented in . + /// + /// This inference engine is intended for use in Refactorings. + /// + public sealed class CommonTypeInference + { + // The algorithm used is loosely based an extended version of the corresponding Java algorithm. + // The Java specifiction calls this 'lub' (least upper bound), and is defined only in one direction + // (for the other direction, Java uses intersection types). + // + // An improved algorithm for Java is presented in: + // Daniel Smith and Robert Cartwright. Java Type Inference Is Broken: Can We + // Fix It? In OOPSLA ’08: Proceedings of the 23rd ACM SIGPLAN conference + // on Object-oriented programming systems languages and applications pages 505–524, + // New York, NY, USA, 2008. + // + // The algorithm used here is losely based on that, although of course there are major differences: + // C# does not have any equivalent to Java's 'recursive types' + // (e.g. Comparable>, nested infinitely), + // so a large part of the problematic cases disappear. + // + // However, C# also does not have any kind of intersection or union types. + // This means we have to find an approximation to such types, for which there might + // not be a unique solution. + + // We use the term Union(T,S) to denote a type X for which T <: X and S <: X. + // X is a common base type of T and S. + // (if we had union types, the union "T|S" would be the most specific possible type X). + + // We use the term Intersect(T,S) to denote a type X for which X <: T and X <: S. + // X is a common subtype of T and S. + // (if we had intersection types, the intersection "T|S" would be the least specific possible type X). + + // Some examples to show the possible common base types: + // Union(List, List) = { + // IEnumerable = IEnumerable, + // IList, + // ICollection, + // IEnumerable, + // object, + // } + // Removing entries that are uninteresting as they as less specific than other existing entries, the result + // of Union(List, List) is { IEnumerable, IList }. + + // The number of options can be extremely high, especially when dealing with common subtypes. + // Intersect(IDisposable, ICloneable) will return all classes that implement both interfaces. + // In fact, for certain kinds of class declarations, there will be an infinite number of options. + // For this reason, this algorithm supports aborting the operation, either after a specific number of options + // has been found; or using a CancellationToken (e.g. when user clicks Cancel, or simply when too much time has expired). + + readonly ITypeResolveContext context; + readonly IConversions conversions; + + /// + /// Creates a new CommonTypeInference instance. + /// + /// The type resolve context to use. + /// The language-specified conversion rules to use. + public CommonTypeInference(ITypeResolveContext context, IConversions conversions) + { + if (context == null) + throw new ArgumentNullException("context"); + if (conversions == null) + throw new ArgumentNullException("conversions"); + this.context = context; + this.conversions = conversions; + } + + public IEnumerable CommonBaseTypes(IList inputTypes, bool useOnlyReferenceConversion = false) + { + if (inputTypes == null) + throw new ArgumentNullException("inputTypes"); + if (inputTypes.Count == 0) + return EmptyList.Instance; + + // First test whether there is a type in the input that all other input types are convertible to + IType potentialCommonBaseType = inputTypes[0]; + for (int i = 1; i < inputTypes.Count; i++) { + if (useOnlyReferenceConversion) { + if (conversions.ImplicitReferenceConversion(inputTypes[i], potentialCommonBaseType)) { + // OK, continue + } else if (conversions.ImplicitReferenceConversion(potentialCommonBaseType, inputTypes[i])) { + potentialCommonBaseType = inputTypes[i]; + } else { + potentialCommonBaseType = null; + break; + } + } else { + if (conversions.ImplicitConversion(inputTypes[i], potentialCommonBaseType)) { + // OK, continue + } else if (conversions.ImplicitConversion(potentialCommonBaseType, inputTypes[i])) { + potentialCommonBaseType = inputTypes[i]; + } else { + potentialCommonBaseType = null; + break; + } + } + } + if (potentialCommonBaseType != null) + return new[] { potentialCommonBaseType }; + + // If we're supposed to only use reference conversions, but there is a non-reference type left in the input, + // we can give up. + if (useOnlyReferenceConversion && inputTypes.Any(t => t.IsReferenceType != true)) + return EmptyList.Instance; + + // Debug output: input values + Debug.WriteLine("CommonBaseTypes input = {"); + Debug.Indent(); + foreach (IType type in inputTypes) + Debug.WriteLine(type); + Debug.Unindent(); + Debug.WriteLine("}"); + + Dictionary dict = new Dictionary(); + HashSet potentialTypes = new HashSet(); + // Retrieve the initial candidates from the first bound + // generic types go to dict, non-generic types directly go to potentialTypes + foreach (IType baseType in inputTypes[0].GetAllBaseTypes(context)) { + ParameterizedType pt = baseType as ParameterizedType; + if (pt != null) { + TP[] tp = new TP[pt.TypeParameterCount]; + for (int i = 0; i < tp.Length; i++) { + tp[i] = new TP(pt.GetDefinition().TypeParameters[i]); + tp[i].Bounds.Add(pt.TypeArguments[i]); + } + dict[pt.GetDefinition()] = tp; + } else { + potentialTypes.Add(baseType); + } + } + // Now retrieve candidates for all other bounds, and intersect the different sets of candidates. + for (int i = 1; i < inputTypes.Count; i++) { + IEnumerable baseTypesForThisBound = inputTypes[i].GetAllBaseTypes(context); + HashSet genericTypeDefsForThisLowerBound = new HashSet(); + foreach (IType baseType in baseTypesForThisBound) { + ParameterizedType pt = baseType as ParameterizedType; + if (pt != null) { + TP[] tp; + if (dict.TryGetValue(pt.GetDefinition(), out tp)) { + genericTypeDefsForThisLowerBound.Add(pt.GetDefinition()); + for (int j = 0; j < tp.Length; j++) { + tp[j].Bounds.Add(pt.TypeArguments[j]); + } + } + } + } + potentialTypes.IntersectWith(baseTypesForThisBound); + foreach (ITypeDefinition def in dict.Keys.ToArray()) { + if (!genericTypeDefsForThisLowerBound.Contains(def)) + dict.Remove(def); + } + } + + // Now figure out the generic types, and add them to potential types if possible. + foreach (var pair in dict) { + Debug.WriteLine("CommonBaseTypes: " + pair.Key); + Debug.Indent(); + IType[][] typeArguments = new IType[pair.Value.Length][]; + bool error = false; + for (int i = 0; i < pair.Value.Length; i++) { + var tp = pair.Value[i]; + Debug.WriteLine("Fixing " + tp); + Debug.Indent(); + switch (tp.Variance) { + case VarianceModifier.Covariant: + typeArguments[i] = CommonBaseTypes(tp.Bounds.ToArray(), true).ToArray(); + break; + case VarianceModifier.Contravariant: + typeArguments[i] = CommonSubTypes(tp.Bounds.ToArray(), true).ToArray(); + break; + default: // Invariant + if (tp.Bounds.Count == 1) + typeArguments[i] = new IType[] { tp.Bounds.Single() }; + break; + } + Debug.Unindent(); + if (typeArguments[i] == null || typeArguments[i].Length == 0) { + Debug.WriteLine(" -> error"); + error = true; + break; + } else { + Debug.WriteLine(" -> " + string.Join(",", typeArguments[i].AsEnumerable())); + } + } + if (!error) { + foreach (IType[] ta in AllCombinations(typeArguments)) { + IType result = new ParameterizedType(pair.Key, ta); + Debug.WriteLine("Result: " + result); + potentialTypes.Add(result); + } + } + Debug.Unindent(); + } + + // Debug output: list candidates found so far: + Debug.WriteLine("CommonBaseTypes candidates = {"); + Debug.Indent(); + foreach (IType type in potentialTypes) + Debug.WriteLine(type); + Debug.Unindent(); + Debug.WriteLine("}"); + + // Remove redundant types + foreach (IType type in potentialTypes.ToArray()) { + bool isRedundant = false; + foreach (IType otherType in potentialTypes) { + if (type != otherType && conversions.ImplicitReferenceConversion(otherType, type)) { + isRedundant = true; + break; + } + } + if (isRedundant) + potentialTypes.Remove(type); + } + + return potentialTypes; + } + + /// + /// Performs the combinatorial explosion. + /// + IEnumerable AllCombinations(IType[][] typeArguments) + { + int[] index = new int[typeArguments.Length]; + index[typeArguments.Length - 1] = -1; + while (true) { + int i; + for (i = index.Length - 1; i >= 0; i--) { + if (++index[i] == typeArguments[i].Length) + index[i] = 0; + else + break; + } + if (i < 0) + break; + IType[] r = new IType[typeArguments.Length]; + for (i = 0; i < r.Length; i++) { + r[i] = typeArguments[i][index[i]]; + } + yield return r; + } + } + + public IEnumerable CommonSubTypes(IList inputTypes, bool useOnlyReferenceConversion = false) + { + if (inputTypes == null) + throw new ArgumentNullException("inputTypes"); + if (inputTypes.Count == 0) + return EmptyList.Instance; + + // First test whether there is a type in the input that can be converted to all other input types + IType potentialCommonSubType = inputTypes[0]; + for (int i = 1; i < inputTypes.Count; i++) { + if (useOnlyReferenceConversion) { + if (conversions.ImplicitReferenceConversion(potentialCommonSubType, inputTypes[i])) { + // OK, continue + } else if (conversions.ImplicitReferenceConversion(inputTypes[i], potentialCommonSubType)) { + potentialCommonSubType = inputTypes[i]; + } else { + potentialCommonSubType = null; + break; + } + } else { + if (conversions.ImplicitConversion(potentialCommonSubType, inputTypes[i])) { + // OK, continue + } else if (conversions.ImplicitConversion(inputTypes[i], potentialCommonSubType)) { + potentialCommonSubType = inputTypes[i]; + } else { + potentialCommonSubType = null; + break; + } + } + } + if (potentialCommonSubType != null) + return new[] { potentialCommonSubType }; + + // Now we're left with the open-ended quest to find a type that derives from all input types. + + + return new IType[0]; + } + + sealed class TP + { + public readonly VarianceModifier Variance; + + public TP(ITypeParameter tp) + { + this.Variance = tp.Variance; + } + + public readonly HashSet Bounds = new HashSet(); + + #if DEBUG + public override string ToString() + { + StringBuilder b = new StringBuilder(); + b.Append('('); + if (this.Variance == VarianceModifier.Covariant) { + bool first = true; + foreach (IType type in Bounds) { + if (first) first = false; else b.Append(" | "); + b.Append(type); + } + b.Append(" <: "); + } + b.Append("TP"); + if (this.Variance == VarianceModifier.Contravariant) { + b.Append(" <: "); + bool first = true; + foreach (IType type in Bounds) { + if (first) first = false; else b.Append(" & "); + b.Append(type); + } + } else if (this.Variance == VarianceModifier.Invariant) { + foreach (IType type in Bounds) { + b.Append(" = "); + b.Append(type); + } + } + b.Append(')'); + return b.ToString(); + } + #endif + } + } +} diff --git a/ICSharpCode.NRefactory/TypeSystem/IConversions.cs b/ICSharpCode.NRefactory/TypeSystem/IConversions.cs new file mode 100644 index 0000000000..5495a78b76 --- /dev/null +++ b/ICSharpCode.NRefactory/TypeSystem/IConversions.cs @@ -0,0 +1,16 @@ +// Copyright (c) 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; + +namespace ICSharpCode.NRefactory.TypeSystem +{ + /// + /// Interface used to check whether types are convertible. + /// + public interface IConversions + { + bool ImplicitConversion(IType fromType, IType toType); + bool ImplicitReferenceConversion(IType fromType, IType toType); + } +} diff --git a/ICSharpCode.NRefactory/TypeSystem/IMember.cs b/ICSharpCode.NRefactory/TypeSystem/IMember.cs index 22c0b0daf3..4fc545495e 100644 --- a/ICSharpCode.NRefactory/TypeSystem/IMember.cs +++ b/ICSharpCode.NRefactory/TypeSystem/IMember.cs @@ -16,7 +16,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// /// Gets/Sets the declaring type (incl. type arguments, if any). /// This property never returns null -- for top-level members, it returns SharedTypes.UnknownType. - /// If this is not a specialized member, the value returned is equal to . + /// If this is not a specialized member, the value returned is equal to . /// IType DeclaringType { get; } diff --git a/ICSharpCode.NRefactory/TypeSystem/INamedElement.cs b/ICSharpCode.NRefactory/TypeSystem/INamedElement.cs index ecb35fbd38..407caf6c38 100644 --- a/ICSharpCode.NRefactory/TypeSystem/INamedElement.cs +++ b/ICSharpCode.NRefactory/TypeSystem/INamedElement.cs @@ -50,7 +50,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// /// /// For types, the reflection name can be parsed back into a ITypeReference by using - /// . + /// . /// /// /// "System.Int32[]" for int[]
diff --git a/ICSharpCode.NRefactory/Utils/TreeTraversal.cs b/ICSharpCode.NRefactory/Utils/TreeTraversal.cs index f2bf9f3321..4e1b238acc 100644 --- a/ICSharpCode.NRefactory/Utils/TreeTraversal.cs +++ b/ICSharpCode.NRefactory/Utils/TreeTraversal.cs @@ -14,8 +14,8 @@ namespace ICSharpCode.NRefactory.Utils /// /// Converts a tree data structure into a flat list by traversing it in pre-order. /// - /// The root element of the tree. - /// The function that gets the children of an element. + /// The root element of the tree. + /// The function that gets the children of an element. /// Iterator that enumerates the tree structure in pre-order. public static IEnumerable PreOrder(T root, Func> recursion) { @@ -26,7 +26,7 @@ namespace ICSharpCode.NRefactory.Utils /// Converts a tree data structure into a flat list by traversing it in pre-order. /// /// The root elements of the forest. - /// The function that gets the children of an element. + /// The function that gets the children of an element. /// Iterator that enumerates the tree structure in pre-order. public static IEnumerable PreOrder(IEnumerable input, Func> recursion) { @@ -54,8 +54,8 @@ namespace ICSharpCode.NRefactory.Utils /// /// Converts a tree data structure into a flat list by traversing it in post-order. /// - /// The root element of the tree. - /// The function that gets the children of an element. + /// The root element of the tree. + /// The function that gets the children of an element. /// Iterator that enumerates the tree structure in post-order. public static IEnumerable PostOrder(T root, Func> recursion) { @@ -66,7 +66,7 @@ namespace ICSharpCode.NRefactory.Utils /// Converts a tree data structure into a flat list by traversing it in post-order. /// /// The root elements of the forest. - /// The function that gets the children of an element. + /// The function that gets the children of an element. /// Iterator that enumerates the tree structure in post-order. public static IEnumerable PostOrder(IEnumerable input, Func> recursion) { diff --git a/README b/README index 473cc66f71..0830a4ce6b 100644 --- a/README +++ b/README @@ -172,7 +172,8 @@ A: They don't use any particular format. They're merely intended as a debugging Currently .ToString() usually matches .ReflectionName, but that may change in the future. -Q: Why are there extension methods IType.IsEnum() and IType.IsDelegate(), but no IType.IsStruct()' or IType.IsInterface()? +Q: Why are there extension methods IType.IsEnum() and IType.IsDelegate(), but no IType.IsStruct() + or IType.IsInterface()? A: Because if you're asking whether a type is a struct, it's very likely that you're asking the wrong question. @@ -181,7 +182,7 @@ A: Because if you're asking whether a type is a struct, it's very likely that yo so important in the world of types. If whatever you are doing works with struct-types, then it likely will also work with - enum-types, and also with type parameters constraint to be a value-type. + enum-types, and also with type parameters with a value-type constraint. So instead of asking IsStruct(), you really should be asking: IType.IsReferenceType == false Enums and delegates are special because you can do special things with those types