6 changed files with 280 additions and 465 deletions
@ -1,423 +0,0 @@
@@ -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 |
||||
{ |
||||
/// <summary>
|
||||
/// 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 <see cref="CSharp.Resolver.TypeInference.GetBestCommonType"/>.
|
||||
///
|
||||
/// This inference engine is intended for use in Refactorings.
|
||||
/// </summary>
|
||||
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<string>, List<object>) = {
|
||||
// IEnumerable<Union(string, object)> = IEnumerable<object>,
|
||||
// IList,
|
||||
// ICollection,
|
||||
// IEnumerable,
|
||||
// object,
|
||||
// }
|
||||
// Removing entries that are uninteresting as they as less specific than other existing entries, the result
|
||||
// of Union(List<string>, List<object>) is { IEnumerable<object>, 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; |
||||
|
||||
/// <summary>
|
||||
/// Creates a new CommonTypeInference instance.
|
||||
/// </summary>
|
||||
/// <param name="context">The type resolve context to use.</param>
|
||||
/// <param name="conversions">The language-specified conversion rules to use.</param>
|
||||
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<IType> CommonBaseTypes(IList<IType> inputTypes, bool useOnlyReferenceConversion = false) |
||||
{ |
||||
if (inputTypes == null) |
||||
throw new ArgumentNullException("inputTypes"); |
||||
if (inputTypes.Count == 0) |
||||
return EmptyList<IType>.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<IType>.Instance; |
||||
|
||||
// Debug output: input values
|
||||
Debug.WriteLine("CommonBaseTypes input = {"); |
||||
Debug.Indent(); |
||||
foreach (IType type in inputTypes) |
||||
Debug.WriteLine(type); |
||||
Debug.Unindent(); |
||||
Debug.WriteLine("}"); |
||||
|
||||
Dictionary<ITypeDefinition, TP[]> dict = new Dictionary<ITypeDefinition, TP[]>(); |
||||
HashSet<IType> potentialTypes = new HashSet<IType>(); |
||||
// 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<IType> baseTypesForThisBound = inputTypes[i].GetAllBaseTypes(context); |
||||
HashSet<ITypeDefinition> genericTypeDefsForThisLowerBound = new HashSet<ITypeDefinition>(); |
||||
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; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Performs the combinatorial explosion.
|
||||
/// </summary>
|
||||
IEnumerable<IType[]> 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<IType> CommonSubTypes(IList<IType> inputTypes, bool useOnlyReferenceConversion = false) |
||||
{ |
||||
if (inputTypes == null) |
||||
throw new ArgumentNullException("inputTypes"); |
||||
if (inputTypes.Count == 0) |
||||
return EmptyList<IType>.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<IType>.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<ITypeDefinition> candidateTypeDefs = new List<ITypeDefinition>(); |
||||
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<IType> potentialTypes = new HashSet<IType>(); |
||||
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<ParameterizedType>()) { |
||||
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<IType> Bounds = new HashSet<IType>(); |
||||
|
||||
#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
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,129 @@
@@ -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 |
||||
{ |
||||
/// <summary>
|
||||
/// Represents the intersection of several types.
|
||||
/// </summary>
|
||||
public class IntersectionType : AbstractType |
||||
{ |
||||
readonly ReadOnlyCollection<IType> types; |
||||
|
||||
public ReadOnlyCollection<IType> Types { |
||||
get { return types; } |
||||
} |
||||
|
||||
private IntersectionType(IType[] types) |
||||
{ |
||||
Debug.Assert(types.Length >= 2); |
||||
this.types = Array.AsReadOnly(types); |
||||
} |
||||
|
||||
public static IType Create(IEnumerable<IType> 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<bool> 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<IType> GetBaseTypes(ITypeResolveContext context) |
||||
{ |
||||
return types; |
||||
} |
||||
|
||||
public override IEnumerable<IEvent> GetEvents(ITypeResolveContext context, Predicate<IEvent> filter) |
||||
{ |
||||
filter = FilterNonStatic(filter); |
||||
return types.SelectMany(t => t.GetEvents(context, filter)); |
||||
} |
||||
|
||||
public override IEnumerable<IMethod> GetMethods(ITypeResolveContext context, Predicate<IMethod> filter) |
||||
{ |
||||
filter = FilterNonStatic(filter); |
||||
return types.SelectMany(t => t.GetMethods(context, filter)); |
||||
} |
||||
|
||||
public override IEnumerable<IProperty> GetProperties(ITypeResolveContext context, Predicate<IProperty> filter) |
||||
{ |
||||
filter = FilterNonStatic(filter); |
||||
return types.SelectMany(t => t.GetProperties(context, filter)); |
||||
} |
||||
|
||||
public override IEnumerable<IField> GetFields(ITypeResolveContext context, Predicate<IField> filter) |
||||
{ |
||||
filter = FilterNonStatic(filter); |
||||
return types.SelectMany(t => t.GetFields(context, filter)); |
||||
} |
||||
|
||||
static Predicate<T> FilterNonStatic<T>(Predicate<T> filter) where T : class, IMember |
||||
{ |
||||
return member => !member.IsStatic && (filter == null || filter(member)); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue