Browse Source

Worked on improved type inference implementation.

newNRvisualizers
Daniel Grunwald 15 years ago
parent
commit
68450c3f95
  1. 93
      ICSharpCode.NRefactory.Tests/CSharp/Resolver/TypeInferenceTests.cs
  2. 93
      ICSharpCode.NRefactory/CSharp/Resolver/TypeInference.cs
  3. 2
      ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
  4. 423
      ICSharpCode.NRefactory/TypeSystem/CommonTypeInference.cs
  5. 5
      ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs
  6. 129
      ICSharpCode.NRefactory/TypeSystem/IntersectionType.cs

93
ICSharpCode.NRefactory.Tests/CSharp/Resolver/TypeInferenceTests.cs

@ -4,6 +4,7 @@ @@ -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 @@ -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 @@ -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<IType> lowerBounds, IList<IType> 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<IType> ExpandIntersections(IType type)
{
Assert.AreEqual(
ResolveType(typeof(IList)),
ti.FindTypeInBounds(Resolve(typeof(List<short>), typeof(List<int>)), 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)
/// <summary>
/// Performs the combinatorial explosion.
/// </summary>
static IEnumerable<IType[]> 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<short>), typeof(List<int>))));
}
[Test]
public void ListOfStringAndObject()
{
Assert.AreEqual(
ResolveType(typeof(IList), typeof(IEnumerable<object>)),
ti.FindTypeInBounds(Resolve(), Resolve(typeof(List<string>), typeof(List<object>))));
Resolve(typeof(IList), typeof(IEnumerable<object>)),
FindAllTypesInBounds(Resolve(typeof(List<string>), typeof(List<object>))));
}
[Test]
@ -65,15 +104,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -65,15 +104,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
{
Assert.AreEqual(
Resolve(typeof(IList), typeof(IEnumerable<IList>), typeof(IEnumerable<IEnumerable<object>>)),
CommonBaseTypes(typeof(List<List<string>>), typeof(List<List<object>>)));
FindAllTypesInBounds(Resolve(typeof(List<List<string>>), typeof(List<List<object>>))));
}
[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 @@ -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 @@ -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 @@ -97,7 +136,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
{
Assert.AreEqual(
Resolve(typeof(IEnumerable<ICloneable>), typeof(IEnumerable<IComparable>)),
CommonBaseTypes(typeof(IList<string>), typeof(IList<Version>)));
FindAllTypesInBounds(Resolve(typeof(IList<string>), typeof(IList<Version>))));
}
[Test]
@ -105,15 +144,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -105,15 +144,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
{
Assert.AreEqual(
Resolve(typeof(IEnumerable<string>), typeof(IEnumerable<Version>)),
CommonSubTypes(typeof(IEnumerable<ICloneable>), typeof(IEnumerable<IComparable>)));
FindAllTypesInBounds(Resolve(), Resolve(typeof(IEnumerable<ICloneable>), typeof(IEnumerable<IComparable>))));
}
[Test]
public void CommonSubTypeIEnumerableClonableIEnumerableComparableList()
{
Assert.AreEqual(
Resolve(typeof(List<string>), typeof(List<Version>)),
CommonSubTypes(typeof(IEnumerable<ICloneable>), typeof(IEnumerable<IComparable>), typeof(IList)));
}*/
Resolve(typeof(List<string>), typeof(List<Version>), typeof(Collection<string>), typeof(Collection<Version>), typeof(ReadOnlyCollection<string>), typeof(ReadOnlyCollection<Version>)),
FindAllTypesInBounds(Resolve(), Resolve(typeof(IEnumerable<ICloneable>), typeof(IEnumerable<IComparable>), typeof(IList))));
}
}
}

93
ICSharpCode.NRefactory/CSharp/Resolver/TypeInference.cs

@ -15,19 +15,29 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -15,19 +15,29 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
/// <summary>
/// C# 4.0 type inference.
/// </summary>
CSharp40,
CSharp4,
/// <summary>
/// Improved algorithm (not part of any specification) using FindTypeInBounds.
/// Improved algorithm (not part of any specification) using FindTypeInBounds for fixing.
/// </summary>
Improved
Improved,
/// <summary>
/// Improved algorithm (not part of any specification) using FindTypeInBounds for fixing;
/// uses <see cref="IntersectionType"/> to report all results (in case of ambiguities).
/// </summary>
ImprovedReturnAllResults
}
/// <summary>
/// Implements C# 4.0 Type Inference (§7.5.2).
/// </summary>
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 @@ -42,7 +52,18 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
/// <summary>
/// Gets/Sets the type inference algorithm used.
/// </summary>
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 @@ -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 @@ -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<IType> 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 @@ -768,7 +806,32 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
if (upperBounds == null)
throw new ArgumentNullException("upperBounds");
IList<IType> 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<IType> FindTypesInBounds(IList<IType> lowerBounds, IList<IType> 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<IType>.Instance;
// Finds a type X so that "LB <: X <: UB"
Log("FindTypesInBound, LowerBounds=", lowerBounds);
Log("FindTypesInBound, UpperBounds=", upperBounds);
Debug.Indent();
List<ITypeDefinition> candidateTypeDefinitions;
if (lowerBounds.Count > 0) {
@ -798,6 +861,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -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 @@ -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 @@ -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

2
ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj

@ -186,7 +186,6 @@ @@ -186,7 +186,6 @@
<Compile Include="TypeSystem\ByReferenceType.cs" />
<Compile Include="TypeSystem\CecilLoader.cs" />
<Compile Include="TypeSystem\ClassType.cs" />
<Compile Include="TypeSystem\CommonTypeInference.cs" />
<Compile Include="TypeSystem\DomRegion.cs" />
<Compile Include="TypeSystem\EntityType.cs" />
<Compile Include="TypeSystem\ExtensionMethods.cs" />
@ -231,6 +230,7 @@ @@ -231,6 +230,7 @@
<Compile Include="TypeSystem\Implementation\TypeWithElementType.cs" />
<Compile Include="TypeSystem\Implementation\VoidTypeDefinition.cs" />
<Compile Include="TypeSystem\INamedElement.cs" />
<Compile Include="TypeSystem\IntersectionType.cs" />
<Compile Include="TypeSystem\IParameter.cs" />
<Compile Include="TypeSystem\IParameterizedMember.cs" />
<Compile Include="TypeSystem\IProjectContent.cs" />

423
ICSharpCode.NRefactory/TypeSystem/CommonTypeInference.cs

@ -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
}
}
}

5
ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs

@ -301,5 +301,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation @@ -301,5 +301,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation
{
return this == other;
}
public override string ToString()
{
return this.ReflectionName;
}
}
}

129
ICSharpCode.NRefactory/TypeSystem/IntersectionType.cs

@ -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…
Cancel
Save