mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
10 KiB
267 lines
10 KiB
// Copyright (c) 2010 AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under MIT X11 license (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using ICSharpCode.NRefactory.TypeSystem; |
|
|
|
namespace ICSharpCode.NRefactory.CSharp.Resolver |
|
{ |
|
/// <summary> |
|
/// Implementation of member lookup (C# 4.0 spec, §7.4). |
|
/// </summary> |
|
public class MemberLookup |
|
{ |
|
#region Static helper methods |
|
/// <summary> |
|
/// Gets whether the member is considered to be invocable. |
|
/// </summary> |
|
public static bool IsInvocable(IMember member, ITypeResolveContext context) |
|
{ |
|
if (member == null) |
|
throw new ArgumentNullException("member"); |
|
// C# 4.0 spec, §7.4 member lookup |
|
if (member is IEvent || member is IMethod) |
|
return true; |
|
if (member.ReturnType == SharedTypes.Dynamic) |
|
return true; |
|
return member.ReturnType.Resolve(context).IsDelegate(); |
|
} |
|
#endregion |
|
|
|
ITypeResolveContext context; |
|
ITypeDefinition currentTypeDefinition; |
|
IProjectContent currentProject; |
|
|
|
public MemberLookup(ITypeResolveContext context, ITypeDefinition currentTypeDefinition, IProjectContent currentProject) |
|
{ |
|
if (context == null) |
|
throw new ArgumentNullException("context"); |
|
this.context = context; |
|
this.currentTypeDefinition = currentTypeDefinition; |
|
this.currentProject = currentProject; |
|
} |
|
|
|
#region IsAccessible |
|
public bool IsProtectedAccessAllowed(IType targetType) |
|
{ |
|
ITypeDefinition typeDef = targetType.GetDefinition(); |
|
return typeDef != null && typeDef.IsDerivedFrom(currentTypeDefinition, context); |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether <paramref name="entity"/> is accessible in the current class. |
|
/// </summary> |
|
/// <param name="entity">The entity to test</param> |
|
/// <param name="allowProtectedAccess">Whether protected access is allowed. |
|
/// True if the type of the reference is derived from the current class.</param> |
|
public bool IsAccessible(IEntity entity, bool allowProtectedAccess) |
|
{ |
|
if (entity == null) |
|
throw new ArgumentNullException("entity"); |
|
// C# 4.0 spec, §3.5.2 Accessiblity domains |
|
switch (entity.Accessibility) { |
|
case Accessibility.None: |
|
return false; |
|
case Accessibility.Private: |
|
return entity.DeclaringTypeDefinition == currentTypeDefinition; |
|
case Accessibility.Public: |
|
return true; |
|
case Accessibility.Protected: |
|
return allowProtectedAccess && IsProtectedAccessible(entity.DeclaringTypeDefinition); |
|
case Accessibility.Internal: |
|
return IsInternalAccessible(entity.ProjectContent); |
|
case Accessibility.ProtectedOrInternal: |
|
return (allowProtectedAccess && IsProtectedAccessible(entity.DeclaringTypeDefinition)) |
|
|| IsInternalAccessible(entity.ProjectContent); |
|
case Accessibility.ProtectedAndInternal: |
|
return (allowProtectedAccess && IsProtectedAccessible(entity.DeclaringTypeDefinition)) |
|
&& IsInternalAccessible(entity.ProjectContent); |
|
default: |
|
throw new Exception("Invalid value for Accessibility"); |
|
} |
|
} |
|
|
|
bool IsInternalAccessible(IProjectContent declaringProject) |
|
{ |
|
return declaringProject != null && currentProject != null && declaringProject.InternalsVisibleTo(currentProject, context); |
|
} |
|
|
|
bool IsProtectedAccessible(ITypeDefinition declaringType) |
|
{ |
|
if (declaringType == currentTypeDefinition) |
|
return true; |
|
// PERF: this might hurt performance as this method is called several times (once for each member) |
|
// make sure resolving base types is cheap (caches?) or cache within the MemberLookup instance |
|
return currentTypeDefinition != null && currentTypeDefinition.IsDerivedFrom(declaringType, context); |
|
} |
|
#endregion |
|
|
|
public ResolveResult LookupType(IType declaringType, string name, IList<IType> typeArguments) |
|
{ |
|
int typeArgumentCount = typeArguments.Count; |
|
Predicate<ITypeDefinition> typeFilter = delegate (ITypeDefinition d) { |
|
return d.TypeParameterCount == typeArgumentCount && d.Name == name && IsAccessible(d, true); |
|
}; |
|
List<IType> types = declaringType.GetNestedTypes(context, typeFilter).ToList(); |
|
RemoveTypesHiddenByOtherTypes(types); |
|
if (types.Count > 0) |
|
return CreateTypeResolveResult(types[0], types.Count > 1, typeArguments); |
|
else |
|
return new UnknownMemberResolveResult(declaringType, name, typeArguments); |
|
} |
|
|
|
void RemoveTypesHiddenByOtherTypes(List<IType> types) |
|
{ |
|
for (int i = types.Count - 1; i >= 0; i--) { |
|
ITypeDefinition d = GetDeclaringTypeDef(types[i]); |
|
if (d == null) |
|
continue; |
|
// nested loop depends on the fact that the members of more derived classes appear later in the list |
|
for (int j = i + 1; j < types.Count; j++) { |
|
if (types[i].TypeParameterCount != types[j].TypeParameterCount) |
|
continue; |
|
ITypeDefinition s = GetDeclaringTypeDef(types[j]); |
|
if (s != null && s != d && s.IsDerivedFrom(d, context)) { |
|
// types[j] hides types[i] |
|
types.RemoveAt(i); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
ResolveResult CreateTypeResolveResult(IType returnedType, bool isAmbiguous, IList<IType> typeArguments) |
|
{ |
|
if (typeArguments.Count > 0) { |
|
// parameterize the type if necessary |
|
ITypeDefinition returnedTypeDef = returnedType as ITypeDefinition; |
|
if (returnedTypeDef != null) |
|
returnedType = new ParameterizedType(returnedTypeDef, typeArguments); |
|
} |
|
if (isAmbiguous) |
|
return new AmbiguousTypeResolveResult(returnedType); |
|
else |
|
return new TypeResolveResult(returnedType); |
|
} |
|
|
|
/// <summary> |
|
/// Performs a member lookup. |
|
/// </summary> |
|
public ResolveResult Lookup(IType type, string name, IList<IType> typeArguments, bool isInvocation) |
|
{ |
|
int typeArgumentCount = typeArguments.Count; |
|
|
|
List<IType> types = new List<IType>(); |
|
List<IMember> members = new List<IMember>(); |
|
if (!isInvocation) { |
|
// Consider nested types only if it's not an invocation. The type parameter count must match in this case. |
|
Predicate<ITypeDefinition> typeFilter = delegate (ITypeDefinition d) { |
|
return d.TypeParameterCount == typeArgumentCount && d.Name == name && IsAccessible(d, true); |
|
}; |
|
types.AddRange(type.GetNestedTypes(context, typeFilter)); |
|
} |
|
|
|
bool allowProtectedAccess = IsProtectedAccessAllowed(type); |
|
|
|
if (typeArgumentCount == 0) { |
|
Predicate<IMember> memberFilter = delegate(IMember member) { |
|
return !member.IsOverride && member.Name == name && IsAccessible(member, allowProtectedAccess); |
|
}; |
|
members.AddRange(type.GetMethods(context, memberFilter.SafeCast<IMember, IMethod>()).SafeCast<IMethod, IMember>()); |
|
members.AddRange(type.GetProperties(context, memberFilter.SafeCast<IMember, IProperty>()).SafeCast<IProperty, IMember>()); |
|
members.AddRange(type.GetFields(context, memberFilter.SafeCast<IMember, IField>()).SafeCast<IField, IMember>()); |
|
members.AddRange(type.GetEvents(context, memberFilter.SafeCast<IMember, IEvent>()).SafeCast<IEvent, IMember>()); |
|
if (isInvocation) |
|
members.RemoveAll(m => !IsInvocable(m, context)); |
|
} else { |
|
// No need to check for isInvocation/isInvocable here: |
|
// we filter out all non-methods |
|
Predicate<IMethod> memberFilter = delegate(IMethod method) { |
|
return method.TypeParameters.Count == typeArgumentCount |
|
&& !method.IsOverride && method.Name == name && IsAccessible(method, allowProtectedAccess); |
|
}; |
|
members.AddRange(type.GetMethods(context, memberFilter).SafeCast<IMethod, IMember>()); |
|
} |
|
|
|
// TODO: can't members also hide types? |
|
|
|
RemoveTypesHiddenByOtherTypes(types); |
|
// remove members hidden by types |
|
for (int i = 0; i < types.Count; i++) { |
|
ITypeDefinition d = GetDeclaringTypeDef(types[i]); |
|
if (d != null) |
|
members.RemoveAll(m => d.IsDerivedFrom(m.DeclaringTypeDefinition, context)); |
|
} |
|
// remove members hidden by other members |
|
for (int i = members.Count - 1; i >= 0; i--) { |
|
ITypeDefinition d = members[i].DeclaringTypeDefinition; |
|
IMethod mi = members[i] as IMethod; |
|
// nested loop depends on the fact that the members of more derived classes appear later in the list |
|
for (int j = i + 1; j < members.Count; j++) { |
|
if (mi != null) { |
|
IMethod mj = members[j] as IMethod; |
|
if (mj != null && !ParameterListComparer.Instance.Equals(mi, mj)) |
|
continue; |
|
} |
|
ITypeDefinition s = members[j].DeclaringTypeDefinition; |
|
if (s != null && s != d && s.IsDerivedFrom(d, context)) { |
|
// members[j] hides members[i] |
|
members.RemoveAt(i); |
|
break; |
|
} |
|
} |
|
} |
|
// remove interface members hidden by class members |
|
if (type is ITypeParameter) { |
|
// this can happen only with type parameters |
|
for (int i = members.Count - 1; i >= 0; i--) { |
|
ITypeDefinition d = members[i].DeclaringTypeDefinition; |
|
if (d.ClassType != ClassType.Interface) |
|
continue; |
|
IMethod mi = members[i] as IMethod; |
|
for (int j = 0; j < members.Count; j++) { |
|
if (mi != null) { |
|
IMethod mj = members[j] as IMethod; |
|
if (mj != null && !ParameterListComparer.Instance.Equals(mi, mj)) |
|
continue; |
|
} |
|
ITypeDefinition s = members[j].DeclaringTypeDefinition; |
|
if (s != null && IsNonInterfaceType(s)) { |
|
// members[j] hides members[i] |
|
members.RemoveAt(i); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (types.Count > 0) { |
|
bool isAmbiguous = !(types.Count == 1 && members.Count == 0); |
|
return CreateTypeResolveResult(types[0], isAmbiguous, typeArguments); |
|
} |
|
if (members.Count == 0) |
|
return new UnknownMemberResolveResult(type, name, typeArguments); |
|
IMember firstNonMethod = members.FirstOrDefault(m => !(m is IMethod)); |
|
if (members.Count == 1 && firstNonMethod != null) |
|
return new MemberResolveResult(firstNonMethod, context); |
|
if (firstNonMethod == null) |
|
return new MethodGroupResolveResult(type, name, members.ConvertAll(m => (IMethod)m), typeArguments); |
|
return new AmbiguousMemberResultResult(firstNonMethod, firstNonMethod.ReturnType.Resolve(context)); |
|
} |
|
|
|
static bool IsNonInterfaceType(ITypeDefinition def) |
|
{ |
|
// return type if def is neither an interface nor System.Object |
|
return def.ClassType != ClassType.Interface && !(def.Name == "Object" && def.Namespace == "System" && def.TypeParameterCount == 0); |
|
} |
|
|
|
static ITypeDefinition GetDeclaringTypeDef(IType type) |
|
{ |
|
IType declType = type.DeclaringType; |
|
return declType != null ? declType.GetDefinition() : null; |
|
} |
|
} |
|
}
|
|
|