// Copyright (c) AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. 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; IType returnType = member.ReturnType.Resolve(context); if (returnType == SharedTypes.Dynamic) return true; return returnType.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: // check for members of outer classes (private members of outer classes can be accessed) var lookupTypeDefinition = currentTypeDefinition; while (lookupTypeDefinition != null) { if (entity.DeclaringTypeDefinition.Equals (lookupTypeDefinition)) return true; lookupTypeDefinition = lookupTypeDefinition.DeclaringTypeDefinition; } return false; 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.Equals (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, bool parameterizeResultType = true) { 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, parameterizeResultType); 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, bool parameterizeResultType) { if (parameterizeResultType && typeArguments.Count > 0) { // Complete the partial parameterization ParameterizedType pt = returnedType as ParameterizedType; if (pt != null) { IType[] newTypeArguments = new IType[pt.TypeParameterCount]; pt.TypeArguments.CopyTo(newTypeArguments, 0); typeArguments.CopyTo(newTypeArguments, newTypeArguments.Length - typeArguments.Count); returnedType = new ParameterizedType(pt.GetDefinition(), newTypeArguments); } } if (isAmbiguous) return new AmbiguousTypeResolveResult(returnedType); else return new TypeResolveResult(returnedType); } /// /// Performs a member lookup. /// public ResolveResult Lookup(ResolveResult targetResolveResult, string name, IList typeArguments, bool isInvocation) { IType type = targetResolveResult.Type; 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) { // inner types contain the type parameters of outer types. therefore this count has to been adjusted. int correctedCount = d.TypeParameterCount - (d.DeclaringType != null ? d.DeclaringType.TypeParameterCount : 0); return correctedCount == 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.GetMembers(context, memberFilter)); 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)); } ParameterListComparer parameterListComparer = new ParameterListComparer(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.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--) { if (members[i].DeclaringTypeDefinition.Kind != TypeKind.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.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, true); } 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(targetResolveResult, firstNonMethod, context); if (firstNonMethod == null) return new MethodGroupResolveResult(targetResolveResult, name, members.ConvertAll(m => (IMethod)m), typeArguments); return new AmbiguousMemberResultResult(targetResolveResult, firstNonMethod, firstNonMethod.ReturnType.Resolve(context)); } static bool IsNonInterfaceType(ITypeDefinition def) { // return type if def is neither an interface nor System.Object return def.Kind != TypeKind.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; } } }