X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fmcs%2Fdecl.cs;h=e293e0ad5a975543950a4bd67e4b065e7912c422;hb=968c2830f973d10a72e5d2907c42744331116299;hp=36235f0f8fc26b9993a2c4df09114f9e3bb879ef;hpb=2680ce338548529160f8d391d9cfe41bd7d85f60;p=mono.git diff --git a/mcs/mcs/decl.cs b/mcs/mcs/decl.cs index 36235f0f8fc..e293e0ad5a9 100755 --- a/mcs/mcs/decl.cs +++ b/mcs/mcs/decl.cs @@ -12,82 +12,294 @@ using System; using System.Collections; +using System.Reflection.Emit; +using System.Reflection; -namespace CIR { +namespace Mono.CSharp { - // - // Base class for structs, classes, enums and interfaces. They all create - // new declaration spaces. This provides the common foundation - // for managing those name spaces - // - - public abstract class DeclSpace { - string name, basename; - - // - // The result value from adding an declaration into - // a struct or a class - // - public enum AdditionResult { + /// + /// Base representation for members. This is only used to keep track + /// of Name, Location and Modifier flags. + /// + public abstract class MemberCore { + /// + /// Public name + /// + public string Name; + + /// + /// Modifier flags that the user specified in the source code + /// + public int ModFlags; + + /// + /// Location where this declaration happens + /// + public readonly Location Location; + + public MemberCore (string name, Location loc) + { + Name = name; + Location = loc; + } + + protected void WarningNotHiding (TypeContainer parent) + { + Report.Warning ( + 109, Location, + "The member " + parent.MakeName (Name) + " does not hide an " + + "inherited member. The keyword new is not required"); + + } + + void Error_CannotChangeAccessModifiers (TypeContainer parent, MethodInfo parent_method, + string name) + { // - // The declaration has been successfully - // added to the declation space. + // FIXME: report the old/new permissions? // - Success, + Report.Error ( + 507, Location, parent.MakeName (Name) + + ": can't change the access modifiers when overriding inherited " + + "member `" + name + "'"); + } + + // + // Performs various checks on the MethodInfo `mb' regarding the modifier flags + // that have been defined. + // + // `name' is the user visible name for reporting errors (this is used to + // provide the right name regarding method names and properties) + // + protected bool CheckMethodAgainstBase (TypeContainer parent, MethodAttributes my_attrs, + MethodInfo mb, string name) + { + bool ok = true; + + if ((ModFlags & Modifiers.OVERRIDE) != 0){ + if (!(mb.IsAbstract || mb.IsVirtual)){ + Report.Error ( + 506, Location, parent.MakeName (Name) + + ": cannot override inherited member `" + + name + "' because it is not " + + "virtual, abstract or override"); + ok = false; + } + + // Now we check that the overriden method is not final + + if (mb.IsFinal) { + Report.Error (239, Location, parent.MakeName (Name) + " : cannot " + + "override inherited member `" + name + + "' because it is sealed."); + ok = false; + } + // + // Check that the permissions are not being changed + // + MethodAttributes thisp = my_attrs & MethodAttributes.MemberAccessMask; + MethodAttributes parentp = mb.Attributes & MethodAttributes.MemberAccessMask; + + // + // special case for "protected internal" + // + + if ((parentp & MethodAttributes.FamORAssem) == MethodAttributes.FamORAssem){ + // + // when overriding protected internal, the method can be declared + // protected internal only within the same assembly + // + + if ((thisp & MethodAttributes.FamORAssem) == MethodAttributes.FamORAssem){ + if (parent.TypeBuilder.Assembly != mb.DeclaringType.Assembly){ + // + // assemblies differ - report an error + // + + Error_CannotChangeAccessModifiers (parent, mb, name); + ok = false; + } else if (thisp != parentp) { + // + // same assembly, but other attributes differ - report an error + // + + Error_CannotChangeAccessModifiers (parent, mb, name); + ok = false; + }; + } else if ((thisp & MethodAttributes.Family) != MethodAttributes.Family) { + // + // if it's not "protected internal", it must be "protected" + // + + Error_CannotChangeAccessModifiers (parent, mb, name); + ok = false; + } else if (parent.TypeBuilder.Assembly == mb.DeclaringType.Assembly) { + // + // protected within the same assembly - an error + // + Error_CannotChangeAccessModifiers (parent, mb, name); + ok = false; + } else if ((thisp & ~(MethodAttributes.Family | MethodAttributes.FamORAssem)) != + (parentp & ~(MethodAttributes.Family | MethodAttributes.FamORAssem))) { + // + // protected ok, but other attributes differ - report an error + // + Error_CannotChangeAccessModifiers (parent, mb, name); + ok = false; + } + } else { + if (thisp != parentp){ + Error_CannotChangeAccessModifiers (parent, mb, name); + ok = false; + } + } + } + if (mb.IsVirtual || mb.IsAbstract){ + if ((ModFlags & (Modifiers.NEW | Modifiers.OVERRIDE)) == 0){ + if (Name != "Finalize"){ + Report.Warning ( + 114, 2, Location, parent.MakeName (Name) + + " hides inherited member `" + name + + "'. To make the current member override that " + + "implementation, add the override keyword, " + + "otherwise use the new keyword"); + ModFlags |= Modifiers.NEW; + } + } + } else { + if ((ModFlags & (Modifiers.NEW | Modifiers.OVERRIDE)) == 0){ + if (Name != "Finalize"){ + Report.Warning ( + 108, 1, Location, "The keyword new is required on " + + parent.MakeName (Name) + " because it hides " + + "inherited member `" + name + "'"); + ModFlags |= Modifiers.NEW; + } + } + } + + return ok; + } + + public abstract bool Define (TypeContainer parent); + + // + // Whehter is it ok to use an unsafe pointer in this type container + // + public bool UnsafeOK (DeclSpace parent) + { // - // The symbol has already been defined. + // First check if this MemberCore modifier flags has unsafe set // + if ((ModFlags & Modifiers.UNSAFE) != 0) + return true; + + if (parent.UnsafeContext) + return true; + + Expression.UnsafeError (Location); + return false; + } + } + + // + // FIXME: This is temporary outside DeclSpace, because I have to fix a bug + // in MCS that makes it fail the lookup for the enum + // + + /// + /// The result value from adding an declaration into + /// a struct or a class + /// + public enum AdditionResult { + /// + /// The declaration has been successfully + /// added to the declation space. + /// + Success, + + /// + /// The symbol has already been defined. + /// NameExists, - // - // Returned if the declation being added to the - // name space clashes with its container name. - // - // The only exceptions for this are constructors - // and static constructors - // + /// + /// Returned if the declation being added to the + /// name space clashes with its container name. + /// + /// The only exceptions for this are constructors + /// and static constructors + /// EnclosingClash, - // - // Returned if a constructor was created (because syntactically - // it looked like a constructor) but was not (because the name - // of the method is not the same as the container class - // - NotAConstructor - } + /// + /// Returned if a constructor was created (because syntactically + /// it looked like a constructor) but was not (because the name + /// of the method is not the same as the container class + /// + NotAConstructor, - public string Name { - get { - return name; - } + /// + /// This is only used by static constructors to emit the + /// error 111, but this error for other things really + /// happens at another level for other functions. + /// + MethodExists } - public string Basename { - get { - return basename; - } - } + /// + /// Base class for structs, classes, enumerations and interfaces. + /// + /// + /// They all create new declaration spaces. This + /// provides the common foundation for managing those name + /// spaces. + /// + public abstract class DeclSpace : MemberCore { + /// + /// this points to the actual definition that is being + /// created with System.Reflection.Emit + /// + public TypeBuilder TypeBuilder; + + /// + /// This variable tracks whether we have Closed the type + /// + public bool Created = false; - // - // defined_names is used for toplevel objects - // + // + // This is the namespace in which this typecontainer + // was declared. We use this to resolve names. + // + public Namespace Namespace; + + public Hashtable Cache = new Hashtable (); + + public string Basename; + + /// + /// defined_names is used for toplevel objects + /// protected Hashtable defined_names; - public DeclSpace (string name) + TypeContainer parent; + + public DeclSpace (TypeContainer parent, string name, Location l) + : base (name, l) { - this.name = name; - this.basename = name.Substring (1 + name.LastIndexOf ('.')); + Basename = name.Substring (1 + name.LastIndexOf ('.')); defined_names = new Hashtable (); + this.parent = parent; } - // - // Returns a status code based purely on the name - // of the member being added - // - protected AdditionResult IsValid (string name) + /// + /// Returns a status code based purely on the name + /// of the member being added + /// + protected AdditionResult IsValid (string basename, string name) { - if (name == basename) + if (basename == Basename) return AdditionResult.EnclosingClash; if (defined_names.Contains (name)) @@ -96,22 +308,41 @@ namespace CIR { return AdditionResult.Success; } - // - // Introduce @name into this declaration space and - // associates it with the object @o. Note that for - // methods this will just point to the first method. o - // + public static int length; + public static int small; + + /// + /// Introduce @name into this declaration space and + /// associates it with the object @o. Note that for + /// methods this will just point to the first method. o + /// protected void DefineName (string name, object o) { defined_names.Add (name, o); + +#if DEBUGME + int p = name.LastIndexOf ("."); + int l = name.Length; + length += l; + small += l -p; +#endif } + /// + /// Returns the object associated with a given name in the declaration + /// space. This is the inverse operation of `DefineName' + /// + public object GetDefinition (string name) + { + return defined_names [name]; + } + bool in_transit = false; - // - // This function is used to catch recursive definitions - // in declarations. - // + /// + /// This function is used to catch recursive definitions + /// in declarations. + /// public bool InTransit { get { return in_transit; @@ -122,6 +353,1004 @@ namespace CIR { } } - abstract public Type Define (Tree tree); + public TypeContainer Parent { + get { + return parent; + } + } + + /// + /// Looks up the alias for the name + /// + public string LookupAlias (string name) + { + if (Namespace != null) + return Namespace.LookupAlias (name); + else + return null; + } + + // + // root_types contains all the types. All TopLevel types + // hence have a parent that points to `root_types', that is + // why there is a non-obvious test down here. + // + public bool IsTopLevel { + get { + if (parent != null){ + if (parent.parent == null) + return true; + } + return false; + } + } + + public virtual void CloseType () + { + if (!Created){ + try { + TypeBuilder.CreateType (); + } catch { + // + // The try/catch is needed because + // nested enumerations fail to load when they + // are defined. + // + // Even if this is the right order (enumerations + // declared after types). + // + // Note that this still creates the type and + // it is possible to save it + } + Created = true; + } + } + + /// + /// Should be overriten by the appropriate declaration space + /// + public abstract TypeBuilder DefineType (); + + /// + /// Define all members, but don't apply any attributes or do anything which may + /// access not-yet-defined classes. This method also creates the MemberCache. + /// + public abstract bool DefineMembers (TypeContainer parent); + + // + // Whether this is an `unsafe context' + // + public bool UnsafeContext { + get { + if ((ModFlags & Modifiers.UNSAFE) != 0) + return true; + if (parent != null) + return parent.UnsafeContext; + return false; + } + } + + public static string MakeFQN (string nsn, string name) + { + if (nsn == "") + return name; + return String.Concat (nsn, ".", name); + } + + EmitContext type_resolve_ec; + EmitContext GetTypeResolveEmitContext (TypeContainer parent, Location loc) + { + type_resolve_ec = new EmitContext (parent, this, loc, null, null, ModFlags, false); + type_resolve_ec.ResolvingTypeTree = true; + + return type_resolve_ec; + } + + // + // Looks up the type, as parsed into the expression `e' + // + public Type ResolveType (Expression e, bool silent, Location loc) + { + if (type_resolve_ec == null) + type_resolve_ec = GetTypeResolveEmitContext (parent, loc); + type_resolve_ec.loc = loc; + type_resolve_ec.ContainerType = TypeBuilder; + + int errors = Report.Errors; + Expression d = e.ResolveAsTypeTerminal (type_resolve_ec); + + if (d == null || d.eclass != ExprClass.Type){ + if (!silent && errors == Report.Errors){ + Report.Error (246, loc, "Cannot find type `"+ e.ToString () +"'"); + } + return null; + } + + if (!CheckAccessLevel (d.Type)) { + Report. Error (122, loc, "`" + d.Type + "' " + + "is inaccessible because of its protection level"); + return null; + } + + return d.Type; + } + + // + // Resolves the expression `e' for a type, and will recursively define + // types. + // + public Expression ResolveTypeExpr (Expression e, bool silent, Location loc) + { + if (type_resolve_ec == null) + type_resolve_ec = GetTypeResolveEmitContext (parent, loc); + type_resolve_ec.loc = loc; + type_resolve_ec.ContainerType = TypeBuilder; + + Expression d = e.ResolveAsTypeTerminal (type_resolve_ec); + + if (d == null || d.eclass != ExprClass.Type){ + if (!silent){ + Report.Error (246, loc, "Cannot find type `"+ e +"'"); + } + return null; + } + + return d; + } + + public bool CheckAccessLevel (Type check_type) + { + if (check_type == TypeBuilder) + return true; + + TypeAttributes check_attr = check_type.Attributes & TypeAttributes.VisibilityMask; + + // + // Broken Microsoft runtime, return public for arrays, no matter what + // the accessibility is for their underlying class, and they return + // NonPublic visibility for pointers + // + if (check_type.IsArray || check_type.IsPointer) + return CheckAccessLevel (check_type.GetElementType ()); + + if (check_attr == TypeAttributes.Public) + return true; + + if (check_attr == TypeAttributes.NestedPublic) + return true; + + if (check_attr == TypeAttributes.NestedPrivate){ + string check_type_name = check_type.FullName; + string type_name = TypeBuilder.FullName; + + int cio = check_type_name.LastIndexOf ("+"); + string container = check_type_name.Substring (0, cio); + + // + // Check if the check_type is a nested class of the current type + // + if (check_type_name.StartsWith (type_name + "+")){ + return true; + } + + if (type_name.StartsWith (container)){ + return true; + } + + return false; + } + + if (check_type.Assembly == TypeBuilder.Assembly){ + return true; + } + + return false; + + } + + + Type LookupInterfaceOrClass (string ns, string name, out bool error) + { + DeclSpace parent; + Type t; + + error = false; + + name = MakeFQN (ns, name); + + t = TypeManager.LookupType (name); + if (t != null) + return t; + + parent = (DeclSpace) RootContext.Tree.Decls [name]; + if (parent == null) + return null; + + t = parent.DefineType (); + if (t == null){ + Report.Error (146, Location, "Class definition is circular: `"+name+"'"); + error = true; + return null; + } + return t; + } + + public static void Error_AmbiguousTypeReference (Location loc, string name, Type t1, Type t2) + { + Report.Error (104, loc, + String.Format ("`{0}' is an ambiguous reference ({1} or {2}) ", name, + t1.FullName, t2.FullName)); + } + + /// + /// GetType is used to resolve type names at the DeclSpace level. + /// Use this to lookup class/struct bases, interface bases or + /// delegate type references + /// + /// + /// + /// Contrast this to LookupType which is used inside method bodies to + /// lookup types that have already been defined. GetType is used + /// during the tree resolution process and potentially define + /// recursively the type + /// + public Type FindType (Location loc, string name) + { + Type t; + bool error; + + // + // For the case the type we are looking for is nested within this one + // or is in any base class + // + DeclSpace containing_ds = this; + + while (containing_ds != null){ + Type current_type = containing_ds.TypeBuilder; + + while (current_type != null) { + string pre = current_type.FullName; + + t = LookupInterfaceOrClass (pre, name, out error); + if (error) + return null; + + if (t != null) + return t; + + current_type = current_type.BaseType; + } + containing_ds = containing_ds.Parent; + } + + // + // Attempt to lookup the class on our namespace and all it's implicit parents + // + for (string ns = Namespace.Name; ns != null; ns = RootContext.ImplicitParent (ns)) { + t = LookupInterfaceOrClass (ns, name, out error); + if (error) + return null; + + if (t != null) + return t; + } + + // + // Attempt to do a direct unqualified lookup + // + t = LookupInterfaceOrClass ("", name, out error); + if (error) + return null; + + if (t != null) + return t; + + // + // Attempt to lookup the class on any of the `using' + // namespaces + // + + for (Namespace ns = Namespace; ns != null; ns = ns.Parent){ + + t = LookupInterfaceOrClass (ns.Name, name, out error); + if (error) + return null; + + if (t != null) + return t; + + // + // Now check the using clause list + // + ArrayList using_list = ns.UsingTable; + + if (using_list == null) + continue; + + Type match = null; + foreach (Namespace.UsingEntry ue in using_list){ + match = LookupInterfaceOrClass (ue.Name, name, out error); + if (error) + return null; + + if (match != null){ + if (t != null){ + Error_AmbiguousTypeReference (loc, name, t, match); + return null; + } + + t = match; + } + } + if (t != null) + return t; + } + + //Report.Error (246, Location, "Can not find type `"+name+"'"); + return null; + } + + /// + /// This function is broken and not what you're looking for. It should only + /// be used while the type is still being created since it doesn't use the cache + /// and relies on the filter doing the member name check. + /// + public abstract MemberList FindMembers (MemberTypes mt, BindingFlags bf, + MemberFilter filter, object criteria); + + /// + /// If we have a MemberCache, return it. This property may return null if the + /// class doesn't have a member cache or while it's still being created. + /// + public abstract MemberCache MemberCache { + get; + } + } + + /// + /// This is a readonly list of MemberInfo's. + /// + public class MemberList : IList { + public readonly IList List; + int count; + + /// + /// Create a new MemberList from the given IList. + /// + public MemberList (IList list) + { + if (list != null) + this.List = list; + else + this.List = new ArrayList (); + count = List.Count; + } + + /// + /// Concatenate the ILists `first' and `second' to a new MemberList. + /// + public MemberList (IList first, IList second) + { + ArrayList list = new ArrayList (); + list.AddRange (first); + list.AddRange (second); + count = list.Count; + List = list; + } + + public static readonly MemberList Empty = new MemberList (new ArrayList ()); + + /// + /// Cast the MemberList into a MemberInfo[] array. + /// + /// + /// This is an expensive operation, only use it if it's really necessary. + /// + public static explicit operator MemberInfo [] (MemberList list) + { + Timer.StartTimer (TimerType.MiscTimer); + MemberInfo [] result = new MemberInfo [list.Count]; + list.CopyTo (result, 0); + Timer.StopTimer (TimerType.MiscTimer); + return result; + } + + // ICollection + + public int Count { + get { + return count; + } + } + + public bool IsSynchronized { + get { + return List.IsSynchronized; + } + } + + public object SyncRoot { + get { + return List.SyncRoot; + } + } + + public void CopyTo (Array array, int index) + { + List.CopyTo (array, index); + } + + // IEnumerable + + public IEnumerator GetEnumerator () + { + return List.GetEnumerator (); + } + + // IList + + public bool IsFixedSize { + get { + return true; + } + } + + public bool IsReadOnly { + get { + return true; + } + } + + object IList.this [int index] { + get { + return List [index]; + } + + set { + throw new NotSupportedException (); + } + } + + // FIXME: try to find out whether we can avoid the cast in this indexer. + public MemberInfo this [int index] { + get { + return (MemberInfo) List [index]; + } + } + + public int Add (object value) + { + throw new NotSupportedException (); + } + + public void Clear () + { + throw new NotSupportedException (); + } + + public bool Contains (object value) + { + return List.Contains (value); + } + + public int IndexOf (object value) + { + return List.IndexOf (value); + } + + public void Insert (int index, object value) + { + throw new NotSupportedException (); + } + + public void Remove (object value) + { + throw new NotSupportedException (); + } + + public void RemoveAt (int index) + { + throw new NotSupportedException (); + } + } + + /// + /// This interface is used to get all members of a class when creating the + /// member cache. It must be implemented by all DeclSpace derivatives which + /// want to support the member cache and by TypeHandle to get caching of + /// non-dynamic types. + /// + public interface IMemberContainer { + /// + /// The name of the IMemberContainer. This is only used for + /// debugging purposes. + /// + string Name { + get; + } + + /// + /// The type of this IMemberContainer. + /// + Type Type { + get; + } + + /// + /// Returns the IMemberContainer of the parent class or null if this + /// is an interface or TypeManger.object_type. + /// This is used when creating the member cache for a class to get all + /// members from the parent class. + /// + IMemberContainer Parent { + get; + } + + /// + /// Whether this is an interface. + /// + bool IsInterface { + get; + } + + /// + /// Returns all members of this class with the corresponding MemberTypes + /// and BindingFlags. + /// + /// + /// When implementing this method, make sure not to return any inherited + /// members and check the MemberTypes and BindingFlags properly. + /// Unfortunately, System.Reflection is lame and doesn't provide a way to + /// get the BindingFlags (static/non-static,public/non-public) in the + /// MemberInfo class, but the cache needs this information. That's why + /// this method is called multiple times with different BindingFlags. + /// + MemberList GetMembers (MemberTypes mt, BindingFlags bf); + + /// + /// Return the container's member cache. + /// + MemberCache MemberCache { + get; + } + } + + /// + /// The MemberCache is used by dynamic and non-dynamic types to speed up + /// member lookups. It has a member name based hash table; it maps each member + /// name to a list of CacheEntry objects. Each CacheEntry contains a MemberInfo + /// and the BindingFlags that were initially used to get it. The cache contains + /// all members of the current class and all inherited members. If this cache is + /// for an interface types, it also contains all inherited members. + /// + /// There are two ways to get a MemberCache: + /// * if this is a dynamic type, lookup the corresponding DeclSpace and then + /// use the DeclSpace.MemberCache property. + /// * if this not a dynamic type, call TypeHandle.GetTypeHandle() to get a + /// TypeHandle instance for the type and then use TypeHandle.MemberCache. + /// + public class MemberCache { + public readonly IMemberContainer Container; + protected Hashtable member_hash; + protected Hashtable method_hash; + protected Hashtable interface_hash; + + /// + /// Create a new MemberCache for the given IMemberContainer `container'. + /// + public MemberCache (IMemberContainer container) + { + this.Container = container; + + Timer.IncrementCounter (CounterType.MemberCache); + Timer.StartTimer (TimerType.CacheInit); + + interface_hash = new Hashtable (); + + // If we have a parent class (we have a parent class unless we're + // TypeManager.object_type), we deep-copy its MemberCache here. + if (Container.Parent != null) + member_hash = SetupCache (Container.Parent.MemberCache); + else if (Container.IsInterface) + member_hash = SetupCacheForInterface (); + else + member_hash = new Hashtable (); + + // If this is neither a dynamic type nor an interface, create a special + // method cache with all declared and inherited methods. + Type type = container.Type; + if (!(type is TypeBuilder) && !type.IsInterface) { + method_hash = new Hashtable (); + AddMethods (type); + } + + // Add all members from the current class. + AddMembers (Container); + + Timer.StopTimer (TimerType.CacheInit); + } + + /// + /// Bootstrap this member cache by doing a deep-copy of our parent. + /// + Hashtable SetupCache (MemberCache parent) + { + Hashtable hash = new Hashtable (); + + IDictionaryEnumerator it = parent.member_hash.GetEnumerator (); + while (it.MoveNext ()) { + hash [it.Key] = ((ArrayList) it.Value).Clone (); + } + + return hash; + } + + void AddInterfaces (MemberCache parent) + { + foreach (Type iface in parent.interface_hash.Keys) { + if (!interface_hash.Contains (iface)) + interface_hash.Add (iface, true); + } + } + + /// + /// Add the contents of `new_hash' to `hash'. + /// + void AddHashtable (Hashtable hash, Hashtable new_hash) + { + IDictionaryEnumerator it = new_hash.GetEnumerator (); + while (it.MoveNext ()) { + ArrayList list = (ArrayList) hash [it.Key]; + if (list != null) + list.AddRange ((ArrayList) it.Value); + else + hash [it.Key] = ((ArrayList) it.Value).Clone (); + } + } + + /// + /// Bootstrap the member cache for an interface type. + /// Type.GetMembers() won't return any inherited members for interface types, + /// so we need to do this manually. Interfaces also inherit from System.Object. + /// + Hashtable SetupCacheForInterface () + { + Hashtable hash = SetupCache (TypeHandle.ObjectType.MemberCache); + Type [] ifaces = TypeManager.GetInterfaces (Container.Type); + + foreach (Type iface in ifaces) { + if (interface_hash.Contains (iface)) + continue; + interface_hash.Add (iface, true); + + IMemberContainer iface_container = + TypeManager.LookupMemberContainer (iface); + + MemberCache iface_cache = iface_container.MemberCache; + AddHashtable (hash, iface_cache.member_hash); + AddInterfaces (iface_cache); + } + + return hash; + } + + /// + /// Add all members from class `container' to the cache. + /// + void AddMembers (IMemberContainer container) + { + // We need to call AddMembers() with a single member type at a time + // to get the member type part of CacheEntry.EntryType right. + AddMembers (MemberTypes.Constructor, container); + AddMembers (MemberTypes.Field, container); + AddMembers (MemberTypes.Method, container); + AddMembers (MemberTypes.Property, container); + AddMembers (MemberTypes.Event, container); + // Nested types are returned by both Static and Instance searches. + AddMembers (MemberTypes.NestedType, + BindingFlags.Static | BindingFlags.Public, container); + AddMembers (MemberTypes.NestedType, + BindingFlags.Static | BindingFlags.NonPublic, container); + } + + void AddMembers (MemberTypes mt, IMemberContainer container) + { + AddMembers (mt, BindingFlags.Static | BindingFlags.Public, container); + AddMembers (mt, BindingFlags.Static | BindingFlags.NonPublic, container); + AddMembers (mt, BindingFlags.Instance | BindingFlags.Public, container); + AddMembers (mt, BindingFlags.Instance | BindingFlags.NonPublic, container); + } + + /// + /// Add all members from class `container' with the requested MemberTypes and + /// BindingFlags to the cache. This method is called multiple times with different + /// MemberTypes and BindingFlags. + /// + void AddMembers (MemberTypes mt, BindingFlags bf, IMemberContainer container) + { + MemberList members = container.GetMembers (mt, bf); + BindingFlags new_bf = (container == Container) ? + bf | BindingFlags.DeclaredOnly : bf; + + foreach (MemberInfo member in members) { + string name = member.Name; + + // We use a name-based hash table of ArrayList's. + ArrayList list = (ArrayList) member_hash [name]; + if (list == null) { + list = new ArrayList (); + member_hash.Add (name, list); + } + + // When this method is called for the current class, the list will + // already contain all inherited members from our parent classes. + // We cannot add new members in front of the list since this'd be an + // expensive operation, that's why the list is sorted in reverse order + // (ie. members from the current class are coming last). + list.Add (new CacheEntry (container, member, mt, bf)); + } + } + + /// + /// Add all declared and inherited methods from class `type' to the method cache. + /// + void AddMethods (Type type) + { + AddMethods (BindingFlags.Static | BindingFlags.Public | + BindingFlags.FlattenHierarchy, type); + AddMethods (BindingFlags.Static | BindingFlags.NonPublic | + BindingFlags.FlattenHierarchy, type); + AddMethods (BindingFlags.Instance | BindingFlags.Public, type); + AddMethods (BindingFlags.Instance | BindingFlags.NonPublic, type); + } + + void AddMethods (BindingFlags bf, Type type) + { + MemberInfo [] members = type.GetMethods (bf); + + foreach (MethodBase member in members) { + string name = member.Name; + + // Varargs methods aren't allowed in C# code. + if ((member.CallingConvention & CallingConventions.VarArgs) != 0) + continue; + + // We use a name-based hash table of ArrayList's. + ArrayList list = (ArrayList) method_hash [name]; + if (list == null) { + list = new ArrayList (); + method_hash.Add (name, list); + } + + // Unfortunately, the elements returned by Type.GetMethods() aren't + // sorted so we need to do this check for every member. + BindingFlags new_bf = bf; + if (member.DeclaringType == type) + new_bf |= BindingFlags.DeclaredOnly; + + list.Add (new CacheEntry (Container, member, MemberTypes.Method, new_bf)); + } + } + + /// + /// Compute and return a appropriate `EntryType' magic number for the given + /// MemberTypes and BindingFlags. + /// + protected static EntryType GetEntryType (MemberTypes mt, BindingFlags bf) + { + EntryType type = EntryType.None; + + if ((mt & MemberTypes.Constructor) != 0) + type |= EntryType.Constructor; + if ((mt & MemberTypes.Event) != 0) + type |= EntryType.Event; + if ((mt & MemberTypes.Field) != 0) + type |= EntryType.Field; + if ((mt & MemberTypes.Method) != 0) + type |= EntryType.Method; + if ((mt & MemberTypes.Property) != 0) + type |= EntryType.Property; + // Nested types are returned by static and instance searches. + if ((mt & MemberTypes.NestedType) != 0) + type |= EntryType.NestedType | EntryType.Static | EntryType.Instance; + + if ((bf & BindingFlags.Instance) != 0) + type |= EntryType.Instance; + if ((bf & BindingFlags.Static) != 0) + type |= EntryType.Static; + if ((bf & BindingFlags.Public) != 0) + type |= EntryType.Public; + if ((bf & BindingFlags.NonPublic) != 0) + type |= EntryType.NonPublic; + if ((bf & BindingFlags.DeclaredOnly) != 0) + type |= EntryType.Declared; + + return type; + } + + /// + /// The `MemberTypes' enumeration type is a [Flags] type which means that it may + /// denote multiple member types. Returns true if the given flags value denotes a + /// single member types. + /// + public static bool IsSingleMemberType (MemberTypes mt) + { + switch (mt) { + case MemberTypes.Constructor: + case MemberTypes.Event: + case MemberTypes.Field: + case MemberTypes.Method: + case MemberTypes.Property: + case MemberTypes.NestedType: + return true; + + default: + return false; + } + } + + /// + /// We encode the MemberTypes and BindingFlags of each members in a "magic" + /// number to speed up the searching process. + /// + [Flags] + protected enum EntryType { + None = 0x000, + + Instance = 0x001, + Static = 0x002, + MaskStatic = Instance|Static, + + Public = 0x004, + NonPublic = 0x008, + MaskProtection = Public|NonPublic, + + Declared = 0x010, + + Constructor = 0x020, + Event = 0x040, + Field = 0x080, + Method = 0x100, + Property = 0x200, + NestedType = 0x400, + + MaskType = Constructor|Event|Field|Method|Property|NestedType + } + + protected struct CacheEntry { + public readonly IMemberContainer Container; + public readonly EntryType EntryType; + public readonly MemberInfo Member; + + public CacheEntry (IMemberContainer container, MemberInfo member, + MemberTypes mt, BindingFlags bf) + { + this.Container = container; + this.Member = member; + this.EntryType = GetEntryType (mt, bf); + } + } + + /// + /// This is called each time we're walking up one level in the class hierarchy + /// and checks whether we can abort the search since we've already found what + /// we were looking for. + /// + protected bool DoneSearching (ArrayList list) + { + // + // We've found exactly one member in the current class and it's not + // a method or constructor. + // + if (list.Count == 1 && !(list [0] is MethodBase)) + return true; + + // + // Multiple properties: we query those just to find out the indexer + // name + // + if ((list.Count > 0) && (list [0] is PropertyInfo)) + return true; + + return false; + } + + /// + /// Looks up members with name `name'. If you provide an optional + /// filter function, it'll only be called with members matching the + /// requested member name. + /// + /// This method will try to use the cache to do the lookup if possible. + /// + /// Unlike other FindMembers implementations, this method will always + /// check all inherited members - even when called on an interface type. + /// + /// If you know that you're only looking for methods, you should use + /// MemberTypes.Method alone since this speeds up the lookup a bit. + /// When doing a method-only search, it'll try to use a special method + /// cache (unless it's a dynamic type or an interface) and the returned + /// MemberInfo's will have the correct ReflectedType for inherited methods. + /// The lookup process will automatically restart itself in method-only + /// search mode if it discovers that it's about to return methods. + /// + public MemberList FindMembers (MemberTypes mt, BindingFlags bf, string name, + MemberFilter filter, object criteria) + { + bool declared_only = (bf & BindingFlags.DeclaredOnly) != 0; + bool method_search = mt == MemberTypes.Method; + // If we have a method cache and we aren't already doing a method-only search, + // then we restart a method search if the first match is a method. + bool do_method_search = !method_search && (method_hash != null); + + ArrayList applicable; + + // If this is a method-only search, we try to use the method cache if + // possible; a lookup in the method cache will return a MemberInfo with + // the correct ReflectedType for inherited methods. + + if (method_search && (method_hash != null)) + applicable = (ArrayList) method_hash [name]; + else + applicable = (ArrayList) member_hash [name]; + + if (applicable == null) + return MemberList.Empty; + + ArrayList list = new ArrayList (); + + Timer.StartTimer (TimerType.CachedLookup); + + EntryType type = GetEntryType (mt, bf); + + IMemberContainer current = Container; + + // `applicable' is a list of all members with the given member name `name' + // in the current class and all its parent classes. The list is sorted in + // reverse order due to the way how the cache is initialy created (to speed + // things up, we're doing a deep-copy of our parent). + + for (int i = applicable.Count-1; i >= 0; i--) { + CacheEntry entry = (CacheEntry) applicable [i]; + + // This happens each time we're walking one level up in the class + // hierarchy. If we're doing a DeclaredOnly search, we must abort + // the first time this happens (this may already happen in the first + // iteration of this loop if there are no members with the name we're + // looking for in the current class). + if (entry.Container != current) { + if (declared_only || DoneSearching (list)) + break; + + current = entry.Container; + } + + // Is the member of the correct type ? + if ((entry.EntryType & type & EntryType.MaskType) == 0) + continue; + + // Is the member static/non-static ? + if ((entry.EntryType & type & EntryType.MaskStatic) == 0) + continue; + + // Apply the filter to it. + if (filter (entry.Member, criteria)) { + if ((entry.EntryType & EntryType.MaskType) != EntryType.Method) + do_method_search = false; + list.Add (entry.Member); + } + } + + Timer.StopTimer (TimerType.CachedLookup); + + // If we have a method cache and we aren't already doing a method-only + // search, we restart in method-only search mode if the first match is + // a method. This ensures that we return a MemberInfo with the correct + // ReflectedType for inherited methods. + if (do_method_search && (list.Count > 0)){ + return FindMembers (MemberTypes.Method, bf, name, filter, criteria); + } + + return new MemberList (list); + } } }