From 68450c3f95c68238fc7d3eeed5cda6d1705bc2f4 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Wed, 8 Dec 2010 19:36:33 +0100 Subject: [PATCH] Worked on improved type inference implementation. --- .../CSharp/Resolver/TypeInferenceTests.cs | 93 ++-- .../CSharp/Resolver/TypeInference.cs | 93 +++- .../ICSharpCode.NRefactory.csproj | 2 +- .../TypeSystem/CommonTypeInference.cs | 423 ------------------ .../Implementation/DefaultTypeParameter.cs | 5 + .../TypeSystem/IntersectionType.cs | 129 ++++++ 6 files changed, 280 insertions(+), 465 deletions(-) delete mode 100644 ICSharpCode.NRefactory/TypeSystem/CommonTypeInference.cs create mode 100644 ICSharpCode.NRefactory/TypeSystem/IntersectionType.cs diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/TypeInferenceTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/TypeInferenceTests.cs index 22ecb07833..2dcd3c0b25 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/TypeInferenceTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/TypeInferenceTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using ICSharpCode.NRefactory.TypeSystem; @@ -14,7 +15,13 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver [TestFixture] public class TypeInferenceTests { - TypeInference ti = new TypeInference(CecilLoaderTests.Mscorlib); + TypeInference ti; + + [SetUp] + public void Setup() + { + ti = new TypeInference(CecilLoaderTests.Mscorlib); + } IType[] Resolve(params Type[] types) { @@ -23,41 +30,73 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver 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 ResolveType(params Type[] type) + IType[] FindAllTypesInBounds(IList lowerBounds, IList upperBounds = null) { - return type.Single().ToTypeReference().Resolve(CecilLoaderTests.Mscorlib); + ti.Algorithm = TypeInferenceAlgorithm.ImprovedReturnAllResults; + IType type = ti.FindTypeInBounds(lowerBounds, upperBounds ?? new IType[0]); + return ExpandIntersections(type).OrderBy(t => t.ReflectionName).ToArray(); } - [Test] - public void ListOfShortAndInt() + static IEnumerable ExpandIntersections(IType type) { - Assert.AreEqual( - ResolveType(typeof(IList)), - ti.FindTypeInBounds(Resolve(typeof(List), typeof(List)), Resolve())); + IntersectionType it = type as IntersectionType; + if (it != null) { + return it.Types.SelectMany(t => ExpandIntersections(t)); + } + ParameterizedType pt = type as ParameterizedType; + if (pt != null) { + IType[][] typeArguments = new IType[pt.TypeArguments.Count][]; + for (int i = 0; i < typeArguments.Length; i++) { + typeArguments[i] = ExpandIntersections(pt.TypeArguments[i]).ToArray(); + } + return AllCombinations(typeArguments).Select(ta => new ParameterizedType(pt.GetDefinition(), ta)); + } + return new [] { type }; } - - - /* - IType[] CommonBaseTypes(params Type[] types) + /// + /// Performs the combinatorial explosion. + /// + static IEnumerable AllCombinations(IType[][] typeArguments) { - return cti.CommonBaseTypes(Resolve(types)).OrderBy(r => r.ReflectionName).ToArray(); + 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; + } } - IType[] CommonSubTypes(params Type[] types) + [Test] + public void ListOfShortAndInt() { - return cti.CommonSubTypes(Resolve(types)).OrderBy(r => r.ReflectionName).ToArray(); + Assert.AreEqual( + Resolve(typeof(IList)), + FindAllTypesInBounds(Resolve(typeof(List), typeof(List)))); } [Test] public void ListOfStringAndObject() { Assert.AreEqual( - ResolveType(typeof(IList), typeof(IEnumerable)), - ti.FindTypeInBounds(Resolve(), Resolve(typeof(List), typeof(List)))); + Resolve(typeof(IList), typeof(IEnumerable)), + FindAllTypesInBounds(Resolve(typeof(List), typeof(List)))); } [Test] @@ -65,15 +104,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { Assert.AreEqual( Resolve(typeof(IList), typeof(IEnumerable), typeof(IEnumerable>)), - CommonBaseTypes(typeof(List>), typeof(List>))); + FindAllTypesInBounds(Resolve(typeof(List>), typeof(List>)))); } - [Test] + [Test, Ignore("Primitive types are not yet supported")] public void ShortAndInt() { Assert.AreEqual( Resolve(typeof(int)), - CommonBaseTypes(typeof(short), typeof(int))); + FindAllTypesInBounds(Resolve(typeof(short), typeof(int)))); } [Test] @@ -81,7 +120,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { Assert.AreEqual( Resolve(typeof(ICloneable), typeof(IComparable)), - CommonBaseTypes(typeof(string), typeof(Version))); + FindAllTypesInBounds(Resolve(typeof(string), typeof(Version)))); } [Test] @@ -89,7 +128,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { Assert.AreEqual( Resolve(typeof(string), typeof(Version)), - CommonSubTypes(typeof(ICloneable), typeof(IComparable))); + FindAllTypesInBounds(Resolve(), Resolve(typeof(ICloneable), typeof(IComparable)))); } [Test] @@ -97,7 +136,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { Assert.AreEqual( Resolve(typeof(IEnumerable), typeof(IEnumerable)), - CommonBaseTypes(typeof(IList), typeof(IList))); + FindAllTypesInBounds(Resolve(typeof(IList), typeof(IList)))); } [Test] @@ -105,15 +144,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { Assert.AreEqual( Resolve(typeof(IEnumerable), typeof(IEnumerable)), - CommonSubTypes(typeof(IEnumerable), typeof(IEnumerable))); + FindAllTypesInBounds(Resolve(), Resolve(typeof(IEnumerable), typeof(IEnumerable)))); } [Test] public void CommonSubTypeIEnumerableClonableIEnumerableComparableList() { Assert.AreEqual( - Resolve(typeof(List), typeof(List)), - CommonSubTypes(typeof(IEnumerable), typeof(IEnumerable), typeof(IList))); - }*/ + Resolve(typeof(List), typeof(List), typeof(Collection), typeof(Collection), typeof(ReadOnlyCollection), typeof(ReadOnlyCollection)), + FindAllTypesInBounds(Resolve(), Resolve(typeof(IEnumerable), typeof(IEnumerable), typeof(IList)))); + } } } diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/TypeInference.cs b/ICSharpCode.NRefactory/CSharp/Resolver/TypeInference.cs index aa2fa34e52..885503c69e 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/TypeInference.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/TypeInference.cs @@ -15,19 +15,29 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// /// C# 4.0 type inference. /// - CSharp40, + CSharp4, /// - /// Improved algorithm (not part of any specification) using FindTypeInBounds. + /// Improved algorithm (not part of any specification) using FindTypeInBounds for fixing. /// - Improved + Improved, + /// + /// Improved algorithm (not part of any specification) using FindTypeInBounds for fixing; + /// uses to report all results (in case of ambiguities). + /// + ImprovedReturnAllResults } /// /// Implements C# 4.0 Type Inference (§7.5.2). /// - public class TypeInference + public sealed class TypeInference { readonly ITypeResolveContext context; + TypeInferenceAlgorithm algorithm = TypeInferenceAlgorithm.CSharp4; + + // determines the maximum generic nesting level; necessary to avoid infinite recursion in 'Improved' mode. + const int maxNestingLevel = 5; + int nestingLevel; #region Constructor public TypeInference(ITypeResolveContext context) @@ -42,7 +52,18 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// /// Gets/Sets the type inference algorithm used. /// - public TypeInferenceAlgorithm Algorithm { get; set; } + public TypeInferenceAlgorithm Algorithm { + get { return algorithm; } + set { algorithm = value; } + } + + TypeInference CreateNestedInstance() + { + TypeInference c = new TypeInference(context); + c.algorithm = algorithm; + c.nestingLevel = nestingLevel + 1; + return c; + } #endregion TP[] typeParameters; @@ -594,7 +615,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver for (int i = 0; i < uniqueBaseType.TypeParameterCount; i++) { IType Ui = pU.TypeArguments[i]; IType Vi = uniqueBaseType.TypeArguments[i]; - if (Vi.IsReferenceType == true) { + if (Ui.IsReferenceType == true) { // look for variance ITypeParameter Xi = pU.GetDefinition().TypeParameters[i]; switch (Xi.Variance) { @@ -619,20 +640,37 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } #endregion - #region Fixing + #region Fixing (§7.5.2.11) bool Fix(TP tp) { - Log("Trying to fix " + tp); + Log(" Trying to fix " + tp); Debug.Assert(!tp.IsFixed); - Log(" Lower bounds: ", tp.LowerBounds); - Log(" Upper bounds: ", tp.UpperBounds); + if (algorithm == TypeInferenceAlgorithm.Improved) { + // Fix using FindTypeInBounds + Debug.Indent(); + var types = CreateNestedInstance().FindTypesInBounds(tp.LowerBounds.ToArray(), tp.UpperBounds.ToArray()); + Debug.Unindent(); + tp.FixedTo = types.FirstOrDefault(); + Log(" T was fixed to " + tp.FixedTo); + return types.Count == 1; + } else if (algorithm == TypeInferenceAlgorithm.ImprovedReturnAllResults) { + // Fix using FindTypeInBounds + Debug.Indent(); + tp.FixedTo = CreateNestedInstance().FindTypeInBounds(tp.LowerBounds.ToArray(), tp.UpperBounds.ToArray()); + Debug.Unindent(); + Log(" T was fixed to " + tp.FixedTo); + return tp.FixedTo != SharedTypes.UnknownType; + } + // Fix using C# 4.0 §7.5.2.11 + Log(" Lower bounds: ", tp.LowerBounds); + Log(" Upper bounds: ", tp.UpperBounds); Conversions conversions = new Conversions(context); IEnumerable candidates = tp.LowerBounds.Union(tp.UpperBounds); // keep only the candidates that are within all bounds candidates = candidates.Where(c => tp.LowerBounds.All(b => conversions.ImplicitConversion(b, c))); candidates = candidates.Where(c => tp.UpperBounds.All(b => conversions.ImplicitConversion(c, b))); candidates = candidates.ToList(); // evaluate the query only once - Log(" Candidates: ", candidates); + Log(" Candidates: ", candidates); IType solution = null; foreach (var c in candidates) { if (candidates.All(o => conversions.ImplicitConversion(c, o))) { @@ -768,7 +806,32 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (upperBounds == null) throw new ArgumentNullException("upperBounds"); + IList result = FindTypesInBounds(lowerBounds, upperBounds); + + if (algorithm == TypeInferenceAlgorithm.ImprovedReturnAllResults) { + return IntersectionType.Create(result); + } else { + // return any of the candidates (prefer non-interfaces) + return result.FirstOrDefault(c => c.GetDefinition().ClassType != ClassType.Interface) + ?? result.FirstOrDefault() ?? SharedTypes.UnknownType; + } + } + + IList FindTypesInBounds(IList lowerBounds, IList upperBounds) + { + // If there's only a single type; return that single type. + // If both inputs are empty, return the empty list. + if (lowerBounds.Count == 0 && upperBounds.Count <= 1) + return upperBounds; + if (upperBounds.Count == 0 && lowerBounds.Count <= 1) + return lowerBounds; + if (nestingLevel > maxNestingLevel) + return EmptyList.Instance; + // Finds a type X so that "LB <: X <: UB" + Log("FindTypesInBound, LowerBounds=", lowerBounds); + Log("FindTypesInBound, UpperBounds=", upperBounds); + Debug.Indent(); List candidateTypeDefinitions; if (lowerBounds.Count > 0) { @@ -798,6 +861,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (candidateDef.TypeParameterCount == 0) { candidate = candidateDef; } else { + Log("Inferring arguments for candidate type definition: " + candidateDef); bool success; IType[] result = InferTypeArgumentsFromBounds( candidateDef.TypeParameters, @@ -807,9 +871,11 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver if (success) { candidate = new ParameterizedType(candidateDef, result); } else { + Log("Inference failed; ignoring candidate"); continue; } } + Log("Candidate type: " + candidate); if (lowerBounds.Count > 0) { // if there were lower bounds, we aim for the most specific candidate: @@ -833,9 +899,8 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } } } - // return any of the candidates (prefer non-interfaces) - return candidateTypes.FirstOrDefault(c => c.GetDefinition().ClassType != ClassType.Interface) - ?? candidateTypes.FirstOrDefault() ?? SharedTypes.UnknownType; + Debug.Unindent(); + return candidateTypes; } #endregion diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index fdd6d45754..8d3905e595 100644 --- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -186,7 +186,6 @@ - @@ -231,6 +230,7 @@ + diff --git a/ICSharpCode.NRefactory/TypeSystem/CommonTypeInference.cs b/ICSharpCode.NRefactory/TypeSystem/CommonTypeInference.cs deleted file mode 100644 index b15dca8153..0000000000 --- a/ICSharpCode.NRefactory/TypeSystem/CommonTypeInference.cs +++ /dev/null @@ -1,423 +0,0 @@ -// 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 - { - // Note: I'm pretty sure this is wrong in some rare cases, and incomplete in others; - // but it should be good enough 99% of the time. - - // We use the term CommonBaseTypes(T,S) to denote the set { X | T <: X and S <: X }. - // (if we had union types, the union "T|S" would be the most specific possible type X). - - // We use the term CommonSubTypes(T,S) to denote the set { X | X <: T and X <: 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], false); - 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 = Fix(pair.Value); - if (typeArguments != null) { - 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; - } - - IType[][] Fix(TP[] tps) - { - IType[][] typeArguments = new IType[tps.Length][]; - bool error = false; - for (int i = 0; i < tps.Length; i++) { - var tp = tps[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())); - } - } - return error ? null : typeArguments; - } - - /// - /// 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 }; - - ITypeDefinition[] inputTypeDefinitions = new ITypeDefinition[inputTypes.Count]; - for (int i = 0; i < inputTypeDefinitions.Length; i++) { - inputTypeDefinitions[i] = inputTypes[i].GetDefinition(); - if (inputTypeDefinitions[i] == null) { - // if there's any array or pointer type, we cannot find a common subtype - return EmptyList.Instance; - } - } - - // Debug output: input values - Debug.WriteLine("CommonSubTypes input = {"); - Debug.Indent(); - foreach (IType type in inputTypes) - Debug.WriteLine(type); - Debug.Unindent(); - Debug.WriteLine("}"); - - // Now we're left with the open-ended quest to find a type that derives from all input types. - List candidateTypeDefs = new List(); - foreach (ITypeDefinition d in context.GetAllClasses()) { - bool ok = true; - // first check whether the type is derived from all input types - foreach (ITypeDefinition inputTypeDef in inputTypeDefinitions) { - if (!d.IsDerivedFrom(inputTypeDef, context)) { - ok = false; - break; - } - } - if (!ok) - continue; - // then check that the type isn't redundant (derives from existing candidate) - foreach (ITypeDefinition oldCandidate in candidateTypeDefs) { - if (d.IsDerivedFrom(oldCandidate, context)) { - ok = false; - break; - } - } - if (!ok) - continue; - // remove all existing candidates that are made redundant by the new type - candidateTypeDefs.RemoveAll(oldCandidate => oldCandidate.IsDerivedFrom(d, context)); - candidateTypeDefs.Add(d); // add new candidate - } - - HashSet potentialTypes = new HashSet(); - foreach (var candidate in candidateTypeDefs) { - if (candidate.TypeParameterCount == 0) { - potentialTypes.Add(candidate); - continue; - } - Debug.WriteLine(" Considering " + candidate); - TP[] tp = new TP[candidate.TypeParameterCount]; - for (int i = 0; i < tp.Length; i++) { - tp[i] = new TP(candidate.TypeParameters[i], true); - } - // self-parameterize the candidate - IType parameterizedCandidate = new ParameterizedType(candidate, candidate.TypeParameters); - foreach (var candidateBase in parameterizedCandidate.GetAllBaseTypes(context).OfType()) { - for (int i = 0; i < inputTypeDefinitions.Length; i++) { - if (candidateBase.GetDefinition() == inputTypeDefinitions[i]) { - ParameterizedType pt = inputTypes[i] as ParameterizedType; - if (pt != null && pt.TypeParameterCount == candidateBase.TypeParameterCount) { - // HACK: only handle the trivial case - // what actually needs to be done here is very much like C# type inference, - // so I should probably restructure the code to reuse C#'s MakeLowerBoundInference etc. - for (int j = 0; j < Math.Min(pt.TypeParameterCount, candidate.TypeParameterCount); j++) { - if (candidateBase.TypeArguments[j] == candidate.TypeParameters[j]) { - tp[j].Bounds.Add(pt.TypeArguments[j]); - } - } - } - } - } - } - Debug.Indent(); - var typeArguments = Fix(tp); - if (typeArguments != null) { - foreach (IType[] ta in AllCombinations(typeArguments)) { - IType result = new ParameterizedType(candidate, ta); - Debug.WriteLine("Result: " + result); - potentialTypes.Add(result); - } - } - Debug.Unindent(); - } - - return potentialTypes; - } - - sealed class TP - { - public readonly VarianceModifier Variance; - - public TP(ITypeParameter tp, bool negative) - { - this.Variance = tp.Variance; - if (negative) { - switch (tp.Variance) { - case VarianceModifier.Covariant: - this.Variance = VarianceModifier.Contravariant; - break; - case VarianceModifier.Contravariant: - this.Variance = VarianceModifier.Covariant; - break; - } - } - } - - 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/Implementation/DefaultTypeParameter.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs index 3bb8770f3d..a31b79b347 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs @@ -301,5 +301,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation { return this == other; } + + public override string ToString() + { + return this.ReflectionName; + } } } diff --git a/ICSharpCode.NRefactory/TypeSystem/IntersectionType.cs b/ICSharpCode.NRefactory/TypeSystem/IntersectionType.cs new file mode 100644 index 0000000000..c23431a2b8 --- /dev/null +++ b/ICSharpCode.NRefactory/TypeSystem/IntersectionType.cs @@ -0,0 +1,129 @@ +// 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.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using ICSharpCode.NRefactory.TypeSystem.Implementation; + +namespace ICSharpCode.NRefactory.TypeSystem +{ + /// + /// Represents the intersection of several types. + /// + public class IntersectionType : AbstractType + { + readonly ReadOnlyCollection types; + + public ReadOnlyCollection Types { + get { return types; } + } + + private IntersectionType(IType[] types) + { + Debug.Assert(types.Length >= 2); + this.types = Array.AsReadOnly(types); + } + + public static IType Create(IEnumerable types) + { + IType[] arr = types.Where(t => t != null).Distinct().ToArray(); + if (arr.Length == 0) + return SharedTypes.UnknownType; + else if (arr.Length == 1) + return arr[0]; + else + return new IntersectionType(arr); + } + + public override string Name { + get { + StringBuilder b = new StringBuilder(); + foreach (var t in types) { + if (b.Length > 0) + b.Append(" & "); + b.Append(t.Name); + } + return b.ToString(); + } + } + + public override string ReflectionName { + get { + StringBuilder b = new StringBuilder(); + foreach (var t in types) { + if (b.Length > 0) + b.Append(" & "); + b.Append(t.ReflectionName); + } + return b.ToString(); + } + } + + public override Nullable IsReferenceType { + get { return null; } + } + + public override int GetHashCode() + { + int hashCode = 0; + unchecked { + foreach (var t in types) { + hashCode *= 7137517; + hashCode += t.GetHashCode(); + } + } + return hashCode; + } + + public override bool Equals(IType other) + { + IntersectionType o = other as IntersectionType; + if (o != null && types.Count == o.types.Count) { + for (int i = 0; i < types.Count; i++) { + if (!types[i].Equals(o.types[i])) + return false; + } + return true; + } + return false; + } + + public override IEnumerable GetBaseTypes(ITypeResolveContext context) + { + return types; + } + + public override IEnumerable GetEvents(ITypeResolveContext context, Predicate filter) + { + filter = FilterNonStatic(filter); + return types.SelectMany(t => t.GetEvents(context, filter)); + } + + public override IEnumerable GetMethods(ITypeResolveContext context, Predicate filter) + { + filter = FilterNonStatic(filter); + return types.SelectMany(t => t.GetMethods(context, filter)); + } + + public override IEnumerable GetProperties(ITypeResolveContext context, Predicate filter) + { + filter = FilterNonStatic(filter); + return types.SelectMany(t => t.GetProperties(context, filter)); + } + + public override IEnumerable GetFields(ITypeResolveContext context, Predicate filter) + { + filter = FilterNonStatic(filter); + return types.SelectMany(t => t.GetFields(context, filter)); + } + + static Predicate FilterNonStatic(Predicate filter) where T : class, IMember + { + return member => !member.IsStatic && (filter == null || filter(member)); + } + } +}