// 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 { /// /// Implementation of member lookup (C# 4.0 spec, §7.4). /// public class MemberLookup { #region Static helper methods /// /// Gets whether the member is considered to be invocable. /// 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); } /// /// Gets whether is accessible in the current class. /// /// The entity to test /// Whether protected access is allowed. /// True if the type of the reference is derived from the current class. 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 typeArguments) { int typeArgumentCount = typeArguments.Count; Predicate typeFilter = delegate (ITypeDefinition d) { return d.TypeParameterCount == typeArgumentCount && d.Name == name && IsAccessible(d, true); }; List 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 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 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); } /// /// Performs a member lookup. /// public ResolveResult Lookup(IType type, string name, IList typeArguments, bool isInvocation) { int typeArgumentCount = typeArguments.Count; List types = new List(); List members = new List(); if (!isInvocation) { // Consider nested types only if it's not an invocation. The type parameter count must match in this case. Predicate 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 memberFilter = delegate(IMember member) { return !member.IsOverride && member.Name == name && IsAccessible(member, allowProtectedAccess); }; members.AddRange(type.GetMethods(context, memberFilter.SafeCast()).SafeCast()); members.AddRange(type.GetProperties(context, memberFilter.SafeCast()).SafeCast()); members.AddRange(type.GetFields(context, memberFilter.SafeCast()).SafeCast()); members.AddRange(type.GetEvents(context, memberFilter.SafeCast()).SafeCast()); if (isInvocation) members.RemoveAll(m => !IsInvocable(m, context)); } else { // No need to check for isInvocation/isInvocable here: // we filter out all non-methods Predicate memberFilter = delegate(IMethod method) { return method.TypeParameters.Count == typeArgumentCount && !method.IsOverride && method.Name == name && IsAccessible(method, allowProtectedAccess); }; members.AddRange(type.GetMethods(context, memberFilter).SafeCast()); } // 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; } } }