// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace ICSharpCode.SharpDevelop.Dom
{
///
/// Class with methods to help finding the correct overload for a member.
///
///
/// This class does member lookup as specified by the C# spec (ECMA-334, § 14.3).
/// Other languages might need custom lookup methods.
///
public static class MemberLookupHelper
{
#region LookupMember / GetAccessibleMembers
public static List GetAllMembers(IReturnType rt)
{
List members = new List();
if (rt != null) {
rt.GetMethods().ForEach(members.Add);
rt.GetProperties().ForEach(members.Add);
rt.GetFields().ForEach(members.Add);
rt.GetEvents().ForEach(members.Add);
}
return members;
}
public static List> LookupMember(
IReturnType type, string name, IClass callingClass,
LanguageProperties language, bool isInvocation, bool? isAccessThoughReferenceOfCurrentClass)
{
if (language == null)
throw new ArgumentNullException("language");
if (isAccessThoughReferenceOfCurrentClass == null) {
isAccessThoughReferenceOfCurrentClass = false;
IClass underlyingClass = type.GetUnderlyingClass();
if (underlyingClass != null)
isAccessThoughReferenceOfCurrentClass = underlyingClass.IsTypeInInheritanceTree(callingClass);
}
IEnumerable members;
if (language == LanguageProperties.VBNet && language.NameComparer.Equals(name, "New")) {
members = GetAllMembers(type).OfType().Where(m => m.IsConstructor).Select(m=>(IMember)m);
} else {
members = GetAllMembers(type).Where(m => language.NameComparer.Equals(m.Name, name));
}
return LookupMember(members, callingClass, (bool)isAccessThoughReferenceOfCurrentClass, isInvocation);
}
sealed class InheritanceLevelComparer : IComparer
{
public readonly static InheritanceLevelComparer Instance = new InheritanceLevelComparer();
public int Compare(IClass x, IClass y)
{
if (x == y)
return 0;
if (x.IsTypeInInheritanceTree(y))
return 1;
else
return -1;
}
}
public static List> LookupMember(
IEnumerable possibleMembers, IClass callingClass,
bool isAccessThoughReferenceOfCurrentClass, bool isInvocation)
{
// Console.WriteLine("Possible members:");
// foreach (IMember m in possibleMembers) {
// Console.WriteLine(" " + m.DotNetName);
// }
IEnumerable accessibleMembers = possibleMembers.Where(member => member.IsAccessible(callingClass, isAccessThoughReferenceOfCurrentClass));
if (isInvocation) {
accessibleMembers = accessibleMembers.Where(IsInvocable);
}
// base most member => most derived member
//Dictionary overrideDict = new Dictionary();
ParameterListComparer parameterListComparer = new ParameterListComparer();
HashSet handledMethods = new HashSet(parameterListComparer);
Dictionary overrideMethodDict = new Dictionary(parameterListComparer);
IMember nonMethodOverride = null;
List> allResults = new List>();
List results = new List();
foreach (var group in accessibleMembers
.GroupBy(m => m.DeclaringType.GetCompoundClass())
.OrderByDescending(g => g.Key, InheritanceLevelComparer.Instance))
{
//Console.WriteLine("Member group " + group.Key);
foreach (IMember m in group) {
//Console.WriteLine(" " + m.DotNetName);
if (m.IsOverride) {
IMethod method = m as IMethod;
if (method != null) {
if (!overrideMethodDict.ContainsKey(method))
overrideMethodDict[method] = method;
} else {
if (nonMethodOverride == null)
nonMethodOverride = m;
}
} else {
IMethod method = m as IMethod;
if (method != null) {
if (handledMethods.Add(method)) {
IMethod mostOverriddenMethod;
if (overrideMethodDict.TryGetValue(method, out mostOverriddenMethod))
results.Add(mostOverriddenMethod);
else {
results.Add(method);
}
}
} else {
// non-methods are only available if they aren't hidden by something else
if (allResults.Count == 0) {
results.Add(nonMethodOverride ?? m);
}
}
}
}
if (results.Count > 0) {
allResults.Add(results);
results = new List();
}
}
// Sometimes there might be 'override's without corresponding 'virtual's.
// Ensure those get found, too.
if (nonMethodOverride != null && allResults.Count == 0) {
results.Add(nonMethodOverride);
}
foreach (IMethod method in overrideMethodDict.Values) {
if (handledMethods.Add(method)) {
results.Add(method);
}
}
if (results.Count > 0) {
allResults.Add(results);
}
return allResults;
}
static bool IsInvocable(IMember member)
{
if (member == null)
throw new ArgumentNullException("member");
if (member is IMethod || member is IEvent)
return true;
IProperty p = member as IProperty;
if (p != null && p.Parameters.Count > 0)
return true;
IReturnType returnType = member.ReturnType;
if (returnType == null)
return false;
IClass c = returnType.GetUnderlyingClass();
return c != null && c.ClassType == ClassType.Delegate;
}
///
/// Gets all accessible members, including indexers and constructors.
///
public static List GetAccessibleMembers(IReturnType rt, IClass callingClass, LanguageProperties language)
{
bool isAccessThoughReferenceOfCurrentClass = false;
IClass underlyingClass = rt.GetUnderlyingClass();
if (underlyingClass != null)
isAccessThoughReferenceOfCurrentClass = underlyingClass.IsTypeInInheritanceTree(callingClass);
return GetAccessibleMembers(rt, callingClass, language, isAccessThoughReferenceOfCurrentClass);
}
///
/// Gets all accessible members, including indexers and constructors.
///
public static List GetAccessibleMembers(IReturnType rt, IClass callingClass, LanguageProperties language, bool isAccessThoughReferenceOfCurrentClass)
{
if (language == null)
throw new ArgumentNullException("language");
List result = new List();
foreach (var g in GetAllMembers(rt).GroupBy(m => m.Name, language.NameComparer).OrderBy(g2=>g2.Key)) {
foreach (var group in LookupMember(g, callingClass, isAccessThoughReferenceOfCurrentClass, false)) {
result.AddRange(group);
}
}
return result;
}
#endregion
#region FindOverload
///
/// Finds the correct overload according to the C# specification.
///
/// List with the methods to check.
/// The types of the arguments passed to the method.
/// Out parameter. Will be true if the resulting method
/// is an acceptable match, false if the resulting method is just a guess and will lead
/// to a compile error.
/// The method that will be called.
public static IMethod FindOverload(IList methods, IReturnType[] arguments, out bool resultIsAcceptable)
{
if (methods == null)
throw new ArgumentNullException("methods");
resultIsAcceptable = false;
if (methods.Count == 0)
return null;
return (IMethod)CSharp.OverloadResolution.FindOverload(
methods,
arguments,
false,
true,
out resultIsAcceptable);
}
public static IProperty FindOverload(IList properties, IReturnType[] arguments)
{
if (properties.Count == 0)
return null;
bool acceptableMatch;
return (IProperty)CSharp.OverloadResolution.FindOverload(
properties,
arguments,
false,
false,
out acceptableMatch);
}
#endregion
#region Type Argument Inference
///
/// Infers type arguments specified by passing expectedArgument as parameter where passedArgument
/// was expected. The resulting type arguments are written to outputArray.
/// Returns false when expectedArgument and passedArgument are incompatible, otherwise true
/// is returned (true is used both for successful inferring and other kind of errors).
///
/// Warning: This method for single-argument type inference doesn't support lambdas!
///
///
/// The C# spec (§ 25.6.4) has a bug: it says that type inference works if the passedArgument is IEnumerable{T}
/// and the expectedArgument is an array; passedArgument and expectedArgument must be swapped here.
///
public static bool InferTypeArgument(IReturnType expectedArgument, IReturnType passedArgument, IReturnType[] outputArray)
{
if (expectedArgument == null) return true;
if (passedArgument == null || passedArgument == NullReturnType.Instance) return true;
if (passedArgument.IsArrayReturnType) {
IReturnType passedArrayElementType = passedArgument.CastToArrayReturnType().ArrayElementType;
if (expectedArgument.IsArrayReturnType && expectedArgument.CastToArrayReturnType().ArrayDimensions == passedArgument.CastToArrayReturnType().ArrayDimensions) {
return InferTypeArgument(expectedArgument.CastToArrayReturnType().ArrayElementType, passedArrayElementType, outputArray);
} else if (expectedArgument.IsConstructedReturnType) {
switch (expectedArgument.FullyQualifiedName) {
case "System.Collections.Generic.IList":
case "System.Collections.Generic.ICollection":
case "System.Collections.Generic.IEnumerable":
return InferTypeArgument(expectedArgument.CastToConstructedReturnType().TypeArguments[0], passedArrayElementType, outputArray);
}
}
// If P is an array type, and A is not an array type of the same rank,
// or an instantiation of IList<>, ICollection<>, or IEnumerable<>, then
// type inference fails for the generic method.
return false;
}
if (expectedArgument.IsGenericReturnType) {
GenericReturnType methodTP = expectedArgument.CastToGenericReturnType();
if (methodTP.TypeParameter.Method != null) {
if (methodTP.TypeParameter.Index < outputArray.Length) {
outputArray[methodTP.TypeParameter.Index] = passedArgument;
}
return true;
}
}
if (expectedArgument.IsConstructedReturnType) {
// The spec for this case is quite complex.
// For our purposes, we can simplify enourmously:
if (!passedArgument.IsConstructedReturnType) return false;
IList expectedTA = expectedArgument.CastToConstructedReturnType().TypeArguments;
IList passedTA = passedArgument.CastToConstructedReturnType().TypeArguments;
int count = Math.Min(expectedTA.Count, passedTA.Count);
for (int i = 0; i < count; i++) {
InferTypeArgument(expectedTA[i], passedTA[i], outputArray);
}
}
return true;
}
#endregion
#region IsApplicable
public static bool IsApplicable(IReturnType argument, IParameter expected, IMethod targetMethod)
{
bool parameterIsRefOrOut = expected.IsRef || expected.IsOut;
bool argumentIsRefOrOut = argument != null && argument.IsDecoratingReturnType();
if (parameterIsRefOrOut != argumentIsRefOrOut)
return false;
if (parameterIsRefOrOut) {
return object.Equals(argument, expected.ReturnType);
} else {
return IsApplicable(argument, expected.ReturnType, targetMethod);
}
}
///
/// Tests whether an argument of type "argument" is valid for a parameter of type "expected" for a call
/// to "targetMethod".
/// targetMethod may be null, it is only used when it is a generic method and expected is (or contains) one of
/// its type parameters.
///
public static bool IsApplicable(IReturnType argument, IReturnType expected, IMethod targetMethod)
{
return ConversionExistsInternal(argument, expected, targetMethod);
}
#endregion
#region Conversion exists
///
/// Checks if an implicit conversion exists from to .
///
public static bool ConversionExists(IReturnType from, IReturnType to)
{
return ConversionExistsInternal(from, to, null);
}
///
/// Tests if an implicit conversion exists from "from" to "to".
/// Conversions from concrete types to generic types are only allowed when the generic type belongs to the
/// method "allowGenericTargetsOnThisMethod".
///
static bool ConversionExistsInternal(IReturnType from, IReturnType to, IMethod allowGenericTargetsOnThisMethod)
{
// ECMA-334, § 13.1 Implicit conversions
// Identity conversion:
if (from == to) return true;
if (from == null || to == null) return false;
if (from.Equals(to)) {
return true;
}
bool fromIsDefault = from.IsDefaultReturnType;
bool toIsDefault = to.IsDefaultReturnType;
if (fromIsDefault && toIsDefault) {
// Implicit numeric conversions:
int f = GetPrimitiveType(from);
int t = GetPrimitiveType(to);
if (f == SByte && (t == Short || t == Int || t == Long || t == Float || t == Double || t == Decimal))
return true;
if (f == Byte && (t == Short || t == UShort || t == Int || t == UInt || t == Long || t == ULong || t == Float || t == Double || t == Decimal))
return true;
if (f == Short && (t == Int || t == Long || t == Float || t == Double || t == Decimal))
return true;
if (f == UShort && (t == Int || t == UInt || t == Long || t == ULong || t == Float || t == Double || t == Decimal))
return true;
if (f == Int && (t == Long || t == Float || t == Double || t == Decimal))
return true;
if (f == UInt && (t == Long || t == ULong || t == Float || t == Double || t == Decimal))
return true;
if ((f == Long || f == ULong) && (t == Float || t == Double || t == Decimal))
return true;
if (f == Char && (t == UShort || t == Int || t == UInt || t == Long || t == ULong || t == Float || t == Double || t == Decimal))
return true;
if (f == Float && t == Double)
return true;
}
// Implicit reference conversions:
if (toIsDefault && to.FullyQualifiedName == "System.Object") {
return true; // from any type to object
}
if (from == NullReturnType.Instance) {
IClass toClass = to.GetUnderlyingClass();
if (toClass != null) {
switch (toClass.ClassType) {
case ClassType.Class:
case ClassType.Delegate:
case ClassType.Interface:
return true;
case ClassType.Struct:
return toClass.FullyQualifiedName == "System.Nullable";
}
}
return false;
}
if ((toIsDefault || to.IsConstructedReturnType || to.IsGenericReturnType)
&& (fromIsDefault || from.IsArrayReturnType || from.IsConstructedReturnType))
{
foreach (IReturnType baseTypeOfFrom in GetTypeInheritanceTree(from)) {
if (IsConstructedConversionToGenericReturnType(baseTypeOfFrom, to, allowGenericTargetsOnThisMethod))
return true;
}
}
if (from.IsArrayReturnType && to.IsArrayReturnType) {
ArrayReturnType fromArt = from.CastToArrayReturnType();
ArrayReturnType toArt = to.CastToArrayReturnType();
// from array to other array type
if (fromArt.ArrayDimensions == toArt.ArrayDimensions) {
return ConversionExistsInternal(fromArt.ArrayElementType, toArt.ArrayElementType, allowGenericTargetsOnThisMethod);
}
}
if (from.IsDecoratingReturnType() && (toIsDefault || to.IsConstructedReturnType)) {
AnonymousMethodReturnType amrt = from.CastToDecoratingReturnType();
IMethod method = CSharp.TypeInference.GetDelegateOrExpressionTreeSignature(to, amrt.CanBeConvertedToExpressionTree);
if (method != null) {
if (amrt.HasParameterList) {
if (amrt.MethodParameters.Count != method.Parameters.Count)
return false;
for (int i = 0; i < amrt.MethodParameters.Count; i++) {
if (amrt.MethodParameters[i].ReturnType != null) {
if (!object.Equals(amrt.MethodParameters[i].ReturnType,
method.Parameters[i].ReturnType))
{
return false;
}
}
}
}
IReturnType rt = amrt.ResolveReturnType(method.Parameters.Select(p => p.ReturnType).ToArray());
return ConversionExistsInternal(rt, method.ReturnType, allowGenericTargetsOnThisMethod);
}
}
return false;
}
static bool IsConstructedConversionToGenericReturnType(IReturnType from, IReturnType to, IMethod allowGenericTargetsOnThisMethod)
{
// null could be passed when type arguments could not be resolved/inferred
if (from == to) // both are null or
return true;
if (from == null || to == null)
return false;
if (from.Equals(to))
return true;
if (allowGenericTargetsOnThisMethod == null)
return false;
if (to.IsGenericReturnType) {
ITypeParameter typeParameter = to.CastToGenericReturnType().TypeParameter;
if (typeParameter.Method == allowGenericTargetsOnThisMethod)
return true;
// applicability ignores constraints
// foreach (IReturnType constraintType in typeParameter.Constraints) {
// if (!ConversionExistsInternal(from, constraintType, allowGenericTargetsOnThisMethod)) {
// return false;
// }
// }
return false;
}
// for conversions like from IEnumerable to IEnumerable, where T is a GenericReturnType
ConstructedReturnType cFrom = from.CastToConstructedReturnType();
ConstructedReturnType cTo = to.CastToConstructedReturnType();
if (cFrom != null && cTo != null) {
if (cFrom.FullyQualifiedName == cTo.FullyQualifiedName && cFrom.TypeArguments.Count == cTo.TypeArguments.Count) {
for (int i = 0; i < cFrom.TypeArguments.Count; i++) {
if (!IsConstructedConversionToGenericReturnType(cFrom.TypeArguments[i], cTo.TypeArguments[i], allowGenericTargetsOnThisMethod))
return false;
}
return true;
}
}
return false;
}
#endregion
#region Better conversion
///
/// Gets if the conversion from to is better than
/// the conversion from to .
///
///
/// 0 = neither conversion is better
/// 1 = from -> to1 is the better conversion
/// 2 = from -> to2 is the better conversion.
///
public static int GetBetterConversion(IReturnType from, IReturnType to1, IReturnType to2)
{
if (from == null) return 0;
if (to1 == null) return 2;
if (to2 == null) return 1;
// See ECMA-334, § 14.4.2.3
// If T1 and T2 are the same type, neither conversion is better.
if (to1.Equals(to2)) {
return 0;
}
// If S is T1, C1 is the better conversion.
if (from.Equals(to1)) {
return 1;
}
// If S is T2, C2 is the better conversion.
if (from.Equals(to2)) {
return 2;
}
bool canConvertFrom1To2 = ConversionExists(to1, to2);
bool canConvertFrom2To1 = ConversionExists(to2, to1);
// If an implicit conversion from T1 to T2 exists, and no implicit conversion
// from T2 to T1 exists, C1 is the better conversion.
if (canConvertFrom1To2 && !canConvertFrom2To1) {
return 1;
}
// If an implicit conversion from T2 to T1 exists, and no implicit conversion
// from T1 to T2 exists, C2 is the better conversion.
if (canConvertFrom2To1 && !canConvertFrom1To2) {
return 2;
}
if (to1.IsDefaultReturnType && to2.IsDefaultReturnType) {
return GetBetterPrimitiveConversion(to1, to2);
}
// Otherwise, neither conversion is better.
return 0;
}
const int Byte = 1;
const int Short = 2;
const int Int = 3;
const int Long = 4;
const int SByte = 5;
const int UShort = 6;
const int UInt = 7;
const int ULong = 8;
const int Float = 9;
const int Double = 10;
const int Char = 11;
const int Decimal= 12;
static int GetBetterPrimitiveConversion(IReturnType to1, IReturnType to2)
{
int t1 = GetPrimitiveType(to1);
int t2 = GetPrimitiveType(to2);
if (t1 == 0 || t2 == 0) return 0; // not primitive
if (t1 == SByte && (t2 == Byte || t2 == UShort || t2 == UInt || t2 == ULong))
return 1;
if (t2 == SByte && (t1 == Byte || t1 == UShort || t1 == UInt || t1 == ULong))
return 2;
if (t1 == Short && (t2 == UShort || t2 == UInt || t2 == ULong))
return 1;
if (t2 == Short && (t1 == UShort || t1 == UInt || t1 == ULong))
return 2;
if (t1 == Int && (t2 == UInt || t2 == ULong))
return 1;
if (t2 == Int && (t1 == UInt || t1 == ULong))
return 2;
if (t1 == Long && t2 == ULong)
return 1;
if (t2 == Long && t1 == ULong)
return 2;
return 0;
}
static int GetPrimitiveType(IReturnType t)
{
switch (t.FullyQualifiedName) {
case "System.SByte": return SByte;
case "System.Byte": return Byte;
case "System.Int16": return Short;
case "System.UInt16": return UShort;
case "System.Int32": return Int;
case "System.UInt32": return UInt;
case "System.Int64": return Long;
case "System.UInt64": return ULong;
case "System.Single": return Float;
case "System.Double": return Double;
case "System.Char": return Char;
case "System.Decimal": return Decimal;
default: return 0;
}
}
#endregion
#region GetCommonType
///
/// Gets the common base type of a and b.
///
public static IReturnType GetCommonType(IProjectContent projectContent, IReturnType a, IReturnType b)
{
if (projectContent == null)
throw new ArgumentNullException("projectContent");
if (a == null) return b;
if (b == null) return a;
if (ConversionExists(a, b))
return b;
//if (ConversionExists(b, a)) - not required because the first baseTypeOfA is a
// return a;
foreach (IReturnType baseTypeOfA in GetTypeInheritanceTree(a)) {
if (ConversionExists(b, baseTypeOfA))
return baseTypeOfA;
}
return projectContent.SystemTypes.Object;
}
#endregion
#region GetTypeParameterPassedToBaseClass / GetTypeInheritanceTree
///
/// Gets the type parameter that was passed to a certain base class.
/// For example, when is Dictionary(of string, int)
/// this method will return KeyValuePair(of string, int)
///
public static IReturnType GetTypeParameterPassedToBaseClass(IReturnType parentType, IClass baseClass, int baseClassTypeParameterIndex)
{
foreach (IReturnType rt in GetTypeInheritanceTree(parentType)) {
ConstructedReturnType crt = rt.CastToConstructedReturnType();
if (crt != null && baseClass.CompareTo(rt.GetUnderlyingClass()) == 0) {
if (baseClassTypeParameterIndex < crt.TypeArguments.Count) {
return crt.TypeArguments[baseClassTypeParameterIndex];
}
}
}
return null;
}
///
/// Translates typeToTranslate using the type arguments from parentType;
///
static IReturnType TranslateIfRequired(IReturnType parentType, IReturnType typeToTranslate)
{
if (typeToTranslate == null)
return null;
ConstructedReturnType parentConstructedType = parentType.CastToConstructedReturnType();
if (parentConstructedType != null) {
return ConstructedReturnType.TranslateType(typeToTranslate, parentConstructedType.TypeArguments, false);
} else {
return typeToTranslate;
}
}
readonly static Dictionary> getTypeInheritanceTreeCache = new Dictionary>();
static void ClearGetTypeInheritanceTreeCache()
{
lock (getTypeInheritanceTreeCache) {
getTypeInheritanceTreeCache.Clear();
}
}
///
/// Gets all types the specified type inherits from (all classes and interfaces).
/// Unlike the class inheritance tree, this method takes care of type arguments and calculates the type
/// arguments that are passed to base classes.
///
public static IEnumerable GetTypeInheritanceTree(IReturnType typeToListInheritanceTreeFor)
{
if (typeToListInheritanceTreeFor == null)
throw new ArgumentNullException("typeToListInheritanceTreeFor");
lock (getTypeInheritanceTreeCache) {
IEnumerable result;
if (getTypeInheritanceTreeCache.TryGetValue(typeToListInheritanceTreeFor, out result))
return result;
}
IClass classToListInheritanceTreeFor = typeToListInheritanceTreeFor.GetUnderlyingClass();
if (classToListInheritanceTreeFor == null)
return new IReturnType[] { typeToListInheritanceTreeFor };
if (typeToListInheritanceTreeFor.IsArrayReturnType) {
IReturnType elementType = typeToListInheritanceTreeFor.CastToArrayReturnType().ArrayElementType;
List resultList = new List();
resultList.Add(typeToListInheritanceTreeFor);
resultList.AddRange(GetTypeInheritanceTree(
new ConstructedReturnType(
classToListInheritanceTreeFor.ProjectContent.GetClass("System.Collections.Generic.IList", 1).DefaultReturnType,
new IReturnType[] { elementType }
)
));
resultList.Add(classToListInheritanceTreeFor.ProjectContent.GetClass("System.Collections.IList", 0).DefaultReturnType);
resultList.Add(classToListInheritanceTreeFor.ProjectContent.GetClass("System.Collections.ICollection", 0).DefaultReturnType);
// non-generic IEnumerable is already added by generic IEnumerable
return resultList;
}
HashSet visitedSet = new HashSet();
List visitedList = new List();
Queue typesToVisit = new Queue();
bool enqueuedLastBaseType = false;
IReturnType currentType = typeToListInheritanceTreeFor;
IClass currentClass = classToListInheritanceTreeFor;
IReturnType nextType;
do {
if (currentClass != null) {
if (visitedSet.Add(currentType)) {
visitedList.Add(currentType);
foreach (IReturnType type in currentClass.BaseTypes) {
typesToVisit.Enqueue(TranslateIfRequired(currentType, type));
}
}
}
if (typesToVisit.Count > 0) {
nextType = typesToVisit.Dequeue();
} else {
nextType = enqueuedLastBaseType ? null : DefaultClass.GetBaseTypeByClassType(classToListInheritanceTreeFor);
enqueuedLastBaseType = true;
}
if (nextType != null) {
currentType = nextType;
currentClass = nextType.GetUnderlyingClass();
}
} while (nextType != null);
lock (getTypeInheritanceTreeCache) {
if (getTypeInheritanceTreeCache.Count == 0) {
DomCache.RegisterForClear(ClearGetTypeInheritanceTreeCache);
}
getTypeInheritanceTreeCache[typeToListInheritanceTreeFor] = visitedList;
}
return visitedList;
}
#endregion
#region IsSimilarMember / FindBaseMember
///
/// Gets if member1 is the same as member2 or if member1 overrides member2.
///
public static bool IsSimilarMember(IMember member1, IMember member2)
{
member1 = GetGenericMember(member1);
member2 = GetGenericMember(member2);
do {
if (IsSimilarMemberInternal(member1, member2))
return true;
} while ((member1 = FindBaseMember(member1)) != null);
return false;
}
///
/// Gets the generic member from a specialized member.
/// Specialized members are the result of overload resolution with type substitution.
///
static IMember GetGenericMember(IMember member)
{
// e.g. member = string[] ToArray(IEnumerable input)
// result = T[] ToArray(IEnumerable input)
if (member != null) {
while (member.GenericMember != null)
member = member.GenericMember;
}
return member;
}
static bool IsSimilarMemberInternal(IMember member1, IMember member2)
{
if (member1 == member2)
return true;
if (member1 == null || member2 == null)
return false;
if (member1.FullyQualifiedName != member2.FullyQualifiedName)
return false;
if (member1.IsStatic != member2.IsStatic)
return false;
IMethodOrProperty m1 = member1 as IMethodOrProperty;
IMethodOrProperty m2 = member2 as IMethodOrProperty;
if (m1 != null || m2 != null) {
if (m1 != null && m2 != null) {
if (DiffUtility.Compare(m1.Parameters, m2.Parameters) != 0)
return false;
if (m1 is IMethod && m2 is IMethod) {
if ((m1 as IMethod).TypeParameters.Count != (m2 as IMethod).TypeParameters.Count)
return false;
}
} else {
return false;
}
}
IField f1 = member1 as IField;
IField f2 = member2 as IField;
if (f1 != null || f2 != null) {
if (f1 != null && f2 != null) {
if (f1.IsLocalVariable != f2.IsLocalVariable || f1.IsParameter != f2.IsParameter)
return false;
} else {
return false;
}
}
return true;
}
public static IMember FindSimilarMember(IClass type, IMember member)
{
if (type == null)
throw new ArgumentNullException("type");
StringComparer nameComparer = member.DeclaringType.ProjectContent.Language.NameComparer;
member = GetGenericMember(member);
if (member is IMethod) {
IMethod parentMethod = (IMethod)member;
foreach (IMethod m in type.Methods) {
if (nameComparer.Equals(parentMethod.Name, m.Name)) {
if (m.IsStatic == parentMethod.IsStatic) {
if (DiffUtility.Compare(parentMethod.Parameters, m.Parameters) == 0) {
return m;
}
}
}
}
} else if (member is IProperty) {
IProperty parentMethod = (IProperty)member;
foreach (IProperty m in type.Properties) {
if (nameComparer.Equals(parentMethod.Name, m.Name)) {
if (m.IsStatic == parentMethod.IsStatic) {
if (DiffUtility.Compare(parentMethod.Parameters, m.Parameters) == 0) {
return m;
}
}
}
}
}
return null;
}
public static IMember FindBaseMember(IMember member)
{
if (member == null) return null;
if (member is IMethod && (member as IMethod).IsConstructor) return null;
IClass parentClass = member.DeclaringType;
IClass baseClass = parentClass.BaseClass;
if (baseClass == null) return null;
foreach (IClass childClass in baseClass.ClassInheritanceTree) {
IMember m = FindSimilarMember(childClass, member);
if (m != null)
return m;
}
return null;
}
#endregion
[System.Diagnostics.ConditionalAttribute("DEBUG")]
internal static void Log(string text)
{
Debug.WriteLine(text);
}
[System.Diagnostics.ConditionalAttribute("DEBUG")]
internal static void Log(string text, IEnumerable types)
{
Log(text, types.Select(t => t != null ? t.DotNetName : ""));
}
[System.Diagnostics.ConditionalAttribute("DEBUG")]
internal static void Log(string text, IEnumerable lines)
{
#if DEBUG
T[] arr = lines.ToArray();
if (arr.Length == 0) {
Log(text + "");
} else {
Log(text + arr[0]);
for (int i = 1; i < arr.Length; i++) {
Log(new string(' ', text.Length) + arr[i]);
}
}
#endif
}
}
}