File: Symbols\OverriddenOrHiddenMembersHelpers.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    /// <summary>
    /// Encapsulates the MakeOverriddenOrHiddenMembers functionality for methods, properties (including indexers), 
    /// and events.
    /// </summary>
    internal static class OverriddenOrHiddenMembersHelpers
    {
        internal static OverriddenOrHiddenMembersResult MakeOverriddenOrHiddenMembers(this MethodSymbol member)
        {
            return MakeOverriddenOrHiddenMembersWorker(member);
        }
 
        internal static OverriddenOrHiddenMembersResult MakeOverriddenOrHiddenMembers(this PropertySymbol member)
        {
            return MakeOverriddenOrHiddenMembersWorker(member);
        }
 
        internal static OverriddenOrHiddenMembersResult MakeOverriddenOrHiddenMembers(this EventSymbol member)
        {
            return MakeOverriddenOrHiddenMembersWorker(member);
        }
 
        /// <summary>
        /// Walk up the type hierarchy from ContainingType and list members that this
        /// member either overrides (accessible members with the same signature, if this
        /// member is declared "override") or hides (accessible members with the same name
        /// but different kinds, plus members that would be in the overrides list if
        /// this member were not declared "override").
        /// 
        /// Members in the overridden list may be non-virtual or may have different
        /// accessibilities, types, accessors, etc.  They are really candidates to be
        /// overridden.
        /// 
        /// Members in the hidden list are definitely hidden.
        /// 
        /// Members in the runtime overridden list are indistinguishable from the members
        /// in the overridden list from the point of view of the runtime (see
        /// FindOtherOverriddenMethodsInContainingType for details).
        /// </summary>
        /// <remarks>
        /// In the presence of non-C# types, the meaning of "same signature" is rather
        /// complicated.  If this member isn't from source, then it refers to the runtime's
        /// notion of signature (i.e. including return type, custom modifiers, etc).
        /// If this member is from source, then the process is (conceptually) as follows.
        /// 
        /// 1) Walk up the type hierarchy, recording all matching members with the same
        ///    signature, ignoring custom modifiers and return type.  Stop if a hidden
        ///    member is encountered.
        /// 2) Apply the following "tie-breaker" rules until you have at most one member,
        ///    a) Prefer members in more derived types.
        ///    b) Prefer an exact custom modifier match (i.e. none, for a source member).
        ///    c) Prefer fewer custom modifiers (values/positions don't matter, just count).
        ///    d) Prefer earlier in GetMembers order (within the same type).
        /// 3) If a member remains, search its containing type for other members that
        ///    have the same C# signature (overridden members) or runtime signature
        ///    (runtime overridden members).
        /// 
        /// In metadata, properties participate in overriding only through their accessors.
        /// That is, property/event accessors may implicitly or explicitly override other methods
        /// and a property/event can be considered to override another property/event if its accessors
        /// override those of the other property/event.
        /// This implementation (like Dev10) will not follow that approach.  Instead, it is
        /// based on spec section 10.7.5, which treats properties as entities in their own
        /// right.  If all property/event accessors have conventional names in metadata and nothing
        /// "unusual" is done with explicit overriding, this approach should produce the same
        /// results as an implementation based on accessor overriding.
        /// </remarks>
        private static OverriddenOrHiddenMembersResult MakeOverriddenOrHiddenMembersWorker(Symbol member)
        {
            Debug.Assert(member.Kind == SymbolKind.Method || member.Kind == SymbolKind.Property || member.Kind == SymbolKind.Event);
 
            if (!CanOverrideOrHide(member))
            {
                return OverriddenOrHiddenMembersResult.Empty;
            }
 
            if (member.IsAccessor())
            {
                // Accessors are handled specially - see MakePropertyAccessorOverriddenOrHiddenMembers for details.
                MethodSymbol accessor = member as MethodSymbol;
                Symbol associatedPropertyOrEvent = accessor.AssociatedSymbol;
                if ((object)associatedPropertyOrEvent != null)
                {
                    if (associatedPropertyOrEvent.Kind == SymbolKind.Property)
                    {
                        return MakePropertyAccessorOverriddenOrHiddenMembers(accessor, (PropertySymbol)associatedPropertyOrEvent);
                    }
                    else
                    {
                        Debug.Assert(associatedPropertyOrEvent.Kind == SymbolKind.Event);
                        return MakeEventAccessorOverriddenOrHiddenMembers(accessor, (EventSymbol)associatedPropertyOrEvent);
                    }
                }
            }
 
            Debug.Assert(!member.IsAccessor());
 
            NamedTypeSymbol containingType = member.ContainingType;
 
            // NOTE: In other areas of the compiler, we check whether the member is from a specific compilation.
            // We could do the same thing here, but that would mean that callers of the public API would have
            // to pass in a Compilation object when asking about overriding or hiding.  This extra cost eliminates
            // the small benefit of getting identical answers from "imported" symbols, regardless of whether they
            // are imported as source or metadata symbols.
            //
            // We believe that source and metadata behaviors agree for correct code, modulo accomodations for
            // runtime bugs (such as https://github.com/dotnet/roslyn/issues/45453) on older platforms.
            // In incorrect code,
            // the source behavior is somewhat more generous (e.g. accepting a method with the wrong return type),
            // but we do not guarantee that incorrect source will be treated in the same way as incorrect metadata.
            bool memberIsFromSomeCompilation = member.Dangerous_IsFromSomeCompilation;
 
            if (containingType.IsInterface)
            {
                return MakeInterfaceOverriddenOrHiddenMembers(member, memberIsFromSomeCompilation);
            }
 
            ArrayBuilder<Symbol> hiddenBuilder;
            ImmutableArray<Symbol> overriddenMembers;
            FindOverriddenOrHiddenMembers(member, containingType, memberIsFromSomeCompilation, out hiddenBuilder, out overriddenMembers);
 
            ImmutableArray<Symbol> hiddenMembers = hiddenBuilder == null ? ImmutableArray<Symbol>.Empty : hiddenBuilder.ToImmutableAndFree();
            return OverriddenOrHiddenMembersResult.Create(overriddenMembers, hiddenMembers);
        }
 
        private static void FindOverriddenOrHiddenMembers(Symbol member, NamedTypeSymbol containingType, bool memberIsFromSomeCompilation,
            out ArrayBuilder<Symbol> hiddenBuilder,
            out ImmutableArray<Symbol> overriddenMembers)
        {
            Symbol bestMatch = null;
            hiddenBuilder = null;
 
            // A specific override exact match candidate, if one is known. This supports covariant returns, for which signature
            // matching is not sufficient. This member is treated as being as good as an exact match.
            Symbol knownOverriddenMember = member switch
            {
                MethodSymbol method => KnownOverriddenClassMethod(method),
                PEPropertySymbol { GetMethod: PEMethodSymbol { ExplicitlyOverriddenClassMethod: { AssociatedSymbol: PropertySymbol overriddenProperty } } } => overriddenProperty,
                RetargetingPropertySymbol { GetMethod: RetargetingMethodSymbol { ExplicitlyOverriddenClassMethod: { AssociatedSymbol: PropertySymbol overriddenProperty } } } => overriddenProperty,
                _ => null
            };
 
            for (NamedTypeSymbol currType = containingType.BaseTypeNoUseSiteDiagnostics;
                (object)currType != null && (object)bestMatch == null && hiddenBuilder == null;
                currType = currType.BaseTypeNoUseSiteDiagnostics)
            {
                bool unused;
                FindOverriddenOrHiddenMembersInType(
                    member,
                    memberIsFromSomeCompilation,
                    containingType,
                    knownOverriddenMember,
                    currType,
                    out bestMatch,
                    out unused,
                    out hiddenBuilder);
            }
 
            // Based on bestMatch, find other methods that will be overridden, hidden, or runtime overridden
            // (in bestMatch.ContainingType).
            FindRelatedMembers(member.IsOverride, memberIsFromSomeCompilation, member.Kind, bestMatch, out overriddenMembers, ref hiddenBuilder);
        }
 
        public static Symbol FindFirstHiddenMemberIfAny(Symbol member, bool memberIsFromSomeCompilation)
        {
            ArrayBuilder<Symbol> hiddenBuilder;
            FindOverriddenOrHiddenMembers(member, member.ContainingType, memberIsFromSomeCompilation, out hiddenBuilder,
                overriddenMembers: out _);
 
            Symbol result = hiddenBuilder?.FirstOrDefault();
            hiddenBuilder?.Free();
 
            return result;
        }
 
        /// <summary>
        /// Compute a candidate overridden method when a method knows what method it is intended to
        /// override. This makes a particular difference when covariant returns are used, in which
        /// case the signature matching rules would not compute the correct overridden method.
        /// </summary>
        private static MethodSymbol KnownOverriddenClassMethod(MethodSymbol method) =>
            method switch
            {
                PEMethodSymbol m => m.ExplicitlyOverriddenClassMethod,
                RetargetingMethodSymbol m => m.ExplicitlyOverriddenClassMethod,
                _ => null
            };
 
        /// <summary>
        /// In the CLI, accessors are just regular methods and their overriding/hiding rules are the same as for
        /// regular methods.  In C#, however, accessors are intimately connected with their corresponding properties.
        /// Rather than walking up the type hierarchy from the containing type of this accessor, looking for members
        /// with the same name, MakePropertyAccessorOverriddenOrHiddenMembers delegates to the associated property.
        /// For an accessor to hide a member, the hidden member must be a corresponding accessor on a property hidden
        /// by the associated property.  For an accessor to override a member, the overridden member must be a
        /// corresponding accessor on a property (directly or indirectly) overridden by the associated property.
        /// 
        /// Example 1:
        /// 
        /// public class A { public virtual int P { get; set; } }
        /// public class B : A { public override int P { get { return 1; } } } //get only
        /// public class C : B { public override int P { set { } } } // set only
        /// 
        /// C.P.set overrides A.P.set because C.P.set is the setter of C.P, which overrides B.P,
        /// which overrides A.P, which has A.P.set as a setter.
        /// 
        /// Example 2:
        /// 
        /// public class A { public virtual int P { get; set; } }
        /// public class B : A { public new virtual int P { get { return 1; } } } //get only
        /// public class C : B { public override int P { set { } } } // set only
        /// 
        /// C.P.set does not override any method because C.P overrides B.P, which has no setter
        /// and does not override a property.
        /// </summary>
        /// <param name="accessor">This accessor.</param>
        /// <param name="associatedProperty">The property associated with this accessor.</param>
        /// <returns>Members overridden or hidden by this accessor.</returns>
        /// <remarks>
        /// This method is intended to return values consistent with the definition of C#, which
        /// may differ from the actual meaning at runtime.
        /// 
        /// Note: we don't need a different path for interfaces - Property.OverriddenOrHiddenMembers handles that.
        /// </remarks>
        private static OverriddenOrHiddenMembersResult MakePropertyAccessorOverriddenOrHiddenMembers(MethodSymbol accessor, PropertySymbol associatedProperty)
        {
            Debug.Assert(accessor.IsAccessor());
            Debug.Assert((object)associatedProperty != null);
 
            bool accessorIsGetter = accessor.MethodKind == MethodKind.PropertyGet;
 
            MethodSymbol overriddenAccessor = null;
            ArrayBuilder<Symbol> hiddenBuilder = null;
 
            OverriddenOrHiddenMembersResult hiddenOrOverriddenByProperty = associatedProperty.OverriddenOrHiddenMembers;
 
            foreach (Symbol hiddenByProperty in hiddenOrOverriddenByProperty.HiddenMembers)
            {
                if (hiddenByProperty.Kind == SymbolKind.Property)
                {
                    // If we're looking at the associated property of this method (vs a property
                    // it overrides), then record the corresponding accessor (if any) as hidden.
                    PropertySymbol propertyHiddenByProperty = (PropertySymbol)hiddenByProperty;
                    MethodSymbol correspondingAccessor = accessorIsGetter ? propertyHiddenByProperty.GetMethod : propertyHiddenByProperty.SetMethod;
                    if ((object)correspondingAccessor != null)
                    {
                        AccessOrGetInstance(ref hiddenBuilder).Add(correspondingAccessor);
                    }
                }
            }
 
            if (hiddenOrOverriddenByProperty.OverriddenMembers.Any())
            {
                // CONSIDER: Do something more sensible if there are multiple overridden members?  Already an error case.
                PropertySymbol propertyOverriddenByProperty = (PropertySymbol)hiddenOrOverriddenByProperty.OverriddenMembers[0];
                MethodSymbol correspondingAccessor = accessorIsGetter ?
                    propertyOverriddenByProperty.GetOwnOrInheritedGetMethod() :
                    propertyOverriddenByProperty.GetOwnOrInheritedSetMethod();
                if ((object)correspondingAccessor != null)
                {
                    overriddenAccessor = correspondingAccessor;
                }
            }
 
            // There's a detailed comment in MakeOverriddenOrHiddenMembersWorker(Symbol) concerning why this predicate is appropriate.
            bool accessorIsFromSomeCompilation = accessor.Dangerous_IsFromSomeCompilation;
            ImmutableArray<Symbol> overriddenAccessors = ImmutableArray<Symbol>.Empty;
            if ((object)overriddenAccessor != null && IsOverriddenSymbolAccessible(overriddenAccessor, accessor.ContainingType) &&
                isAccessorOverride(accessor, overriddenAccessor))
            {
                FindRelatedMembers(
                    accessor.IsOverride, accessorIsFromSomeCompilation, accessor.Kind, overriddenAccessor, out overriddenAccessors, ref hiddenBuilder);
            }
 
            ImmutableArray<Symbol> hiddenMembers = hiddenBuilder == null ? ImmutableArray<Symbol>.Empty : hiddenBuilder.ToImmutableAndFree();
            return OverriddenOrHiddenMembersResult.Create(overriddenAccessors, hiddenMembers);
 
            bool isAccessorOverride(MethodSymbol accessor, MethodSymbol overriddenAccessor)
            {
                if (accessorIsFromSomeCompilation)
                {
                    return MemberSignatureComparer.CSharpAccessorOverrideComparer.Equals(accessor, overriddenAccessor); //NB: custom comparer
                }
 
                if (overriddenAccessor.Equals(KnownOverriddenClassMethod(accessor), TypeCompareKind.AllIgnoreOptions))
                {
                    return true;
                }
 
                return MemberSignatureComparer.RuntimeSignatureComparer.Equals(accessor, overriddenAccessor);
            }
        }
 
        /// <summary>
        /// In the CLI, accessors are just regular methods and their overriding/hiding rules are the same as for
        /// regular methods.  In C#, however, accessors are intimately connected with their corresponding events.
        /// Rather than walking up the type hierarchy from the containing type of this accessor, looking for members
        /// with the same name, MakeEventAccessorOverriddenOrHiddenMembers delegates to the associated event.
        /// For an accessor to hide a member, the hidden member must be a corresponding accessor on a event hidden
        /// by the associated event.  For an accessor to override a member, the overridden member must be a
        /// corresponding accessor on a event (directly or indirectly) overridden by the associated event.
        /// </summary>
        /// <param name="accessor">This accessor.</param>
        /// <param name="associatedEvent">The event associated with this accessor.</param>
        /// <returns>Members overridden or hidden by this accessor.</returns>
        /// <remarks>
        /// This method is intended to return values consistent with the definition of C#, which
        /// may differ from the actual meaning at runtime.
        /// 
        /// Note: we don't need a different path for interfaces - Event.OverriddenOrHiddenMembers handles that.
        /// 
        /// CONSIDER: It is an error for an event to have only one accessor.  Currently, we mimic the behavior for
        /// properties, for consistency, but an alternative approach would be to say that nothing is overridden.
        /// 
        /// CONSIDER: is there a way to share code with MakePropertyAccessorOverriddenOrHiddenMembers?
        /// </remarks>
        private static OverriddenOrHiddenMembersResult MakeEventAccessorOverriddenOrHiddenMembers(MethodSymbol accessor, EventSymbol associatedEvent)
        {
            Debug.Assert(accessor.IsAccessor());
            Debug.Assert((object)associatedEvent != null);
 
            bool accessorIsAdder = accessor.MethodKind == MethodKind.EventAdd;
 
            MethodSymbol overriddenAccessor = null;
            ArrayBuilder<Symbol> hiddenBuilder = null;
 
            OverriddenOrHiddenMembersResult hiddenOrOverriddenByEvent = associatedEvent.OverriddenOrHiddenMembers;
 
            foreach (Symbol hiddenByEvent in hiddenOrOverriddenByEvent.HiddenMembers)
            {
                if (hiddenByEvent.Kind == SymbolKind.Event)
                {
                    // If we're looking at the associated event of this method (vs a event
                    // it overrides), then record the corresponding accessor (if any) as hidden.
                    EventSymbol eventHiddenByEvent = (EventSymbol)hiddenByEvent;
                    MethodSymbol correspondingAccessor = accessorIsAdder ? eventHiddenByEvent.AddMethod : eventHiddenByEvent.RemoveMethod;
                    if ((object)correspondingAccessor != null)
                    {
                        AccessOrGetInstance(ref hiddenBuilder).Add(correspondingAccessor);
                    }
                }
            }
 
            if (hiddenOrOverriddenByEvent.OverriddenMembers.Any())
            {
                // CONSIDER: Do something more sensible if there are multiple overridden members?  Already an error case.
                EventSymbol eventOverriddenByEvent = (EventSymbol)hiddenOrOverriddenByEvent.OverriddenMembers[0];
                MethodSymbol correspondingAccessor = eventOverriddenByEvent.GetOwnOrInheritedAccessor(accessorIsAdder);
                if ((object)correspondingAccessor != null)
                {
                    overriddenAccessor = correspondingAccessor;
                }
            }
 
            // There's a detailed comment in MakeOverriddenOrHiddenMembersWorker(Symbol) concerning why this predicate is appropriate.
            bool accessorIsFromSomeCompilation = accessor.Dangerous_IsFromSomeCompilation;
            ImmutableArray<Symbol> overriddenAccessors = ImmutableArray<Symbol>.Empty;
            if ((object)overriddenAccessor != null && IsOverriddenSymbolAccessible(overriddenAccessor, accessor.ContainingType) &&
                    (accessorIsFromSomeCompilation
                        ? MemberSignatureComparer.CSharpAccessorOverrideComparer.Equals(accessor, overriddenAccessor) //NB: custom comparer
                        : MemberSignatureComparer.RuntimeSignatureComparer.Equals(accessor, overriddenAccessor)))
            {
                FindRelatedMembers(
                    accessor.IsOverride, accessorIsFromSomeCompilation, accessor.Kind, overriddenAccessor, out overriddenAccessors, ref hiddenBuilder);
            }
 
            ImmutableArray<Symbol> hiddenMembers = hiddenBuilder == null ? ImmutableArray<Symbol>.Empty : hiddenBuilder.ToImmutableAndFree();
            return OverriddenOrHiddenMembersResult.Create(overriddenAccessors, hiddenMembers);
        }
 
        /// <summary>
        /// There are two key reasons why interface overriding/hiding is different from class overriding/hiding:
        ///   1) interface members never override other members; and
        ///   2) interfaces can extend multiple interfaces.
        /// The first difference doesn't require any special handling - as long as the members have IsOverride=false,
        /// the code for class overriding/hiding does the right thing.
        /// The second difference is more problematic.  For one thing, an interface member can hide a different member in
        /// each base interface.  We only report the first one, but we need to expose all of them in the API.  More importantly,
        /// multiple inheritance raises the possibility of diamond inheritance.  Spec section 13.2.5, Interface member access,
        /// says: "The intuitive rule for hiding in multiple-inheritance interfaces is simply this: If a member is hidden in any
        /// access path, it is hidden in all access paths."  For example, consider the following interfaces:
        /// 
        /// interface I0 { void M(); }
        /// interface I1 : I0 { void M(); }
        /// interface I2 : I0, I1 { void M(); }
        /// 
        /// I2.M does not hide I0.M, because it is already hidden by I1.M.  To make this work, we need to traverse the graph
        /// of ancestor interfaces in topological order and flag ones later in the enumeration that are hidden along some path.
        /// </summary>
        /// <remarks>
        /// See SymbolPreparer::checkIfaceHiding.
        /// </remarks>
        internal static OverriddenOrHiddenMembersResult MakeInterfaceOverriddenOrHiddenMembers(Symbol member, bool memberIsFromSomeCompilation)
        {
            Debug.Assert(!member.IsAccessor());
 
            NamedTypeSymbol containingType = member.ContainingType;
            Debug.Assert(containingType.IsInterfaceType());
 
            PooledHashSet<NamedTypeSymbol> membersOfOtherKindsHidden = PooledHashSet<NamedTypeSymbol>.GetInstance();
            PooledHashSet<NamedTypeSymbol> allMembersHidden = PooledHashSet<NamedTypeSymbol>.GetInstance(); // Implies membersOfOtherKindsHidden.
 
            ArrayBuilder<Symbol> hiddenBuilder = null;
 
            foreach (NamedTypeSymbol currType in containingType.AllInterfacesNoUseSiteDiagnostics) // NB: topologically sorted
            {
                if (allMembersHidden.Contains(currType))
                {
                    continue;
                }
 
                Symbol currTypeBestMatch;
                bool currTypeHasSameKindNonMatch;
                ArrayBuilder<Symbol> currTypeHiddenBuilder;
 
                FindOverriddenOrHiddenMembersInType(
                    member,
                    memberIsFromSomeCompilation,
                    containingType,
                    knownOverriddenMember: null,
                    currType,
                    out currTypeBestMatch,
                    out currTypeHasSameKindNonMatch,
                    out currTypeHiddenBuilder);
 
                bool haveBestMatch = (object)currTypeBestMatch != null;
 
                if (haveBestMatch)
                {
                    // If our base interface contains a matching member of the same kind, 
                    // then we don't need to look any further up this subtree.
                    foreach (var hidden in currType.AllInterfacesNoUseSiteDiagnostics)
                    {
                        allMembersHidden.Add(hidden);
                    }
 
                    AccessOrGetInstance(ref hiddenBuilder).Add(currTypeBestMatch);
                }
 
                if (currTypeHiddenBuilder != null)
                {
                    // If our base interface contains a matching member of a different kind, then
                    // it will hide all members that aren't of that kind further up the chain.
                    // As a result, nothing of our kind will be visible and we can stop looking.
                    if (!membersOfOtherKindsHidden.Contains(currType))
                    {
                        if (!haveBestMatch)
                        {
                            foreach (var hidden in currType.AllInterfacesNoUseSiteDiagnostics)
                            {
                                allMembersHidden.Add(hidden);
                            }
                        }
 
                        AccessOrGetInstance(ref hiddenBuilder).AddRange(currTypeHiddenBuilder);
                    }
 
                    currTypeHiddenBuilder.Free();
                }
                else if (currTypeHasSameKindNonMatch && !haveBestMatch)
                {
                    // If our base interface contains a (non-matching) member of the same kind, then
                    // it will hide all members that aren't of that kind further up the chain.
                    foreach (var hidden in currType.AllInterfacesNoUseSiteDiagnostics)
                    {
                        membersOfOtherKindsHidden.Add(hidden);
                    }
                }
            }
 
            membersOfOtherKindsHidden.Free();
            allMembersHidden.Free();
 
            // Based on bestMatch, find other methods that will be overridden, hidden, or runtime overridden
            // (in bestMatch.ContainingType).
            ImmutableArray<Symbol> overriddenMembers = ImmutableArray<Symbol>.Empty;
 
            if (hiddenBuilder != null)
            {
                ArrayBuilder<Symbol> hiddenAndRelatedBuilder = null;
                foreach (Symbol hidden in hiddenBuilder)
                {
                    FindRelatedMembers(member.IsOverride, memberIsFromSomeCompilation, member.Kind, hidden, out overriddenMembers, ref hiddenAndRelatedBuilder);
                    Debug.Assert(overriddenMembers.Length == 0);
                }
                hiddenBuilder.Free();
                hiddenBuilder = hiddenAndRelatedBuilder;
            }
 
            Debug.Assert(overriddenMembers.IsEmpty);
 
            ImmutableArray<Symbol> hiddenMembers = hiddenBuilder == null ? ImmutableArray<Symbol>.Empty : hiddenBuilder.ToImmutableAndFree();
            return OverriddenOrHiddenMembersResult.Create(overriddenMembers, hiddenMembers);
        }
 
        /// <summary>
        /// Look for overridden or hidden members in a specific type.
        /// </summary>
        /// <param name="member">Member that is hiding or overriding.</param>
        /// <param name="memberIsFromSomeCompilation">True if member is from the current compilation.</param>
        /// <param name="memberContainingType">The type that contains member (member.ContainingType).</param>
        /// <param name="knownOverriddenMember">The known overridden member (e.g. in the presence of a metadata methodimpl).</param>
        /// <param name="currType">The type to search.</param>
        /// <param name="currTypeBestMatch">
        /// A member with the same signature if currTypeHasExactMatch is true,
        /// a member with (a minimal number of) different custom modifiers if there is one,
        /// and null otherwise.</param>
        /// <param name="currTypeHasSameKindNonMatch">True if there's a member with the same name and kind that is not a match.</param>
        /// <param name="hiddenBuilder">Hidden members (same name, different kind) will be added to this builder.</param>
        /// <remarks>
        /// There is some similarity between this member and TypeSymbol.FindPotentialImplicitImplementationMemberDeclaredInType.
        /// When making changes to this member, think about whether or not they should also be applied in TypeSymbol.
        /// 
        /// In incorrect or imported code, it is possible that both currTypeBestMatch and hiddenBuilder will be populated.
        /// </remarks>
        private static void FindOverriddenOrHiddenMembersInType(
            Symbol member,
            bool memberIsFromSomeCompilation,
            NamedTypeSymbol memberContainingType,
            Symbol knownOverriddenMember,
            NamedTypeSymbol currType,
            out Symbol currTypeBestMatch,
            out bool currTypeHasSameKindNonMatch,
            out ArrayBuilder<Symbol> hiddenBuilder)
        {
            Debug.Assert(!member.IsAccessor());
 
            currTypeBestMatch = null;
            currTypeHasSameKindNonMatch = false;
            hiddenBuilder = null;
 
            bool currTypeHasExactMatch = false;
            int minCustomModifierCount = int.MaxValue;
 
            IEqualityComparer<Symbol> exactMatchComparer = memberIsFromSomeCompilation
                ? MemberSignatureComparer.CSharpCustomModifierOverrideComparer
                : MemberSignatureComparer.RuntimePlusRefOutSignatureComparer;
 
            IEqualityComparer<Symbol> fallbackComparer = memberIsFromSomeCompilation
                ? MemberSignatureComparer.CSharpOverrideComparer
                : MemberSignatureComparer.RuntimeSignatureComparer;
 
            SymbolKind memberKind = member.Kind;
            int memberArity = member.GetMemberArity();
 
            foreach (Symbol otherMember in currType.GetMembers(member.Name))
            {
                if (!IsOverriddenSymbolAccessible(otherMember, memberContainingType))
                {
                    //do nothing
                }
                else if (otherMember.IsAccessor() && !((MethodSymbol)otherMember).IsIndexedPropertyAccessor())
                {
                    //Indexed property accessors can be overridden or hidden by non-accessors.
                    //do nothing - no interaction between accessors and non-accessors
                }
                else if (otherMember.Kind != memberKind)
                {
                    // NOTE: generic methods can hide things with arity 0.
                    // From CSemanticChecker::FindSymHiddenByMethPropAgg
                    int otherMemberArity = otherMember.GetMemberArity();
                    if (otherMemberArity == memberArity || (memberKind == SymbolKind.Method && otherMemberArity == 0))
                    {
                        AddHiddenMemberIfApplicable(ref hiddenBuilder, memberKind, otherMember);
                    }
                }
                else if (!currTypeHasExactMatch)
                {
                    switch (memberKind)
                    {
                        case SymbolKind.Field:
                            currTypeHasExactMatch = true;
                            currTypeBestMatch = otherMember;
                            break;
                        case SymbolKind.NamedType:
                            if (otherMember.GetMemberArity() == memberArity)
                            {
                                currTypeHasExactMatch = true;
                                currTypeBestMatch = otherMember;
                            }
                            break;
 
                        default:
                            if (otherMember.Equals(knownOverriddenMember, TypeCompareKind.AllIgnoreOptions))
                            {
                                currTypeHasExactMatch = true;
                                currTypeBestMatch = otherMember;
                            }
 
                            // We do not perform signature matching in the presence of a methodimpl
                            else if (knownOverriddenMember == null)
                            {
                                if (exactMatchComparer.Equals(member, otherMember))
                                {
                                    currTypeHasExactMatch = true;
                                    currTypeBestMatch = otherMember;
                                }
                                else if (fallbackComparer.Equals(member, otherMember))
                                {
                                    // If this method is from source, we'll also consider methods that match
                                    // without regard to custom modifiers.  If there's more than one, we'll
                                    // choose the one with the fewest custom modifiers.
                                    int methodCustomModifierCount = CustomModifierCount(otherMember);
                                    if (methodCustomModifierCount < minCustomModifierCount)
                                    {
                                        Debug.Assert(memberIsFromSomeCompilation || minCustomModifierCount == int.MaxValue, "Metadata members require exact custom modifier matches.");
                                        minCustomModifierCount = methodCustomModifierCount;
                                        currTypeBestMatch = otherMember;
                                    }
                                }
                                else
                                {
                                    currTypeHasSameKindNonMatch = true;
                                }
                            }
 
                            break;
                    }
                }
            }
 
            switch (memberKind)
            {
                case SymbolKind.Field:
                case SymbolKind.NamedType:
                    break;
 
                default:
                    // If the member is from metadata, then even a fallback match is an exact match.
                    // We just declined to set the flag at the time in case there was a "better" exact match.
                    // Having said that, there's no reason to update the flag, since no-one will consume it.
                    // if (!memberIsFromSomeCompilation && ((object)currTypeBestMatch != null)) currTypeHasExactMatch = true;
 
                    // There's a special case where we have to go back and fix up our best match.
                    // If member is from source, then it has no custom modifiers (at least,
                    // until it copies them from the member it overrides, which we're trying to
                    // compute now).  Therefore, an exact match will be one that has no custom
                    // modifiers in/on its parameters.  The best match may, however, have custom
                    // modifiers in/on its (return) type, since that wasn't considered during the
                    // signature comparison.  If that is the case, then we need to make sure that
                    // there isn't another inexact match (i.e. same signature ignoring custom 
                    // modifiers) with fewer custom modifiers.
                    // NOTE: If member is constructed, then it has already inherited custom modifiers
                    // from the underlying member and this cleanup is unnecessary.  That's why we're
                    // checking member.IsDefinition in addition to memberIsFromSomeCompilation.
                    if (currTypeHasExactMatch && memberIsFromSomeCompilation && member.IsDefinition && TypeOrReturnTypeHasCustomModifiers(currTypeBestMatch))
                    {
#if DEBUG
                        // If there were custom modifiers on the parameters, then the match wouldn't have been
                        // exact and so we would already have applied the custom modifier count as a tie-breaker.
                        foreach (ParameterSymbol param in currTypeBestMatch.GetParameters())
                        {
                            Debug.Assert(!(param.TypeWithAnnotations.CustomModifiers.Any() || param.RefCustomModifiers.Any()));
                            Debug.Assert(!param.Type.HasCustomModifiers(flagNonDefaultArraySizesOrLowerBounds: false));
                        }
#endif
 
                        Symbol minCustomModifierMatch = currTypeBestMatch;
 
                        foreach (Symbol otherMember in currType.GetMembers(member.Name))
                        {
                            if (otherMember.Kind == currTypeBestMatch.Kind && !ReferenceEquals(otherMember, currTypeBestMatch))
                            {
                                if (MemberSignatureComparer.CSharpOverrideComparer.Equals(otherMember, currTypeBestMatch))
                                {
                                    int customModifierCount = CustomModifierCount(otherMember);
                                    if (customModifierCount < minCustomModifierCount)
                                    {
                                        minCustomModifierCount = customModifierCount;
                                        minCustomModifierMatch = otherMember;
                                    }
                                }
                            }
                        }
 
                        currTypeBestMatch = minCustomModifierMatch;
                    }
                    break;
            }
        }
 
        /// <summary>
        /// If representative member is non-null and is contained in a constructed type, then find
        /// other members in the same type with the same signature.  If this is an override member,
        /// add them to the overridden and runtime overridden lists.  Otherwise, add them to the
        /// hidden list.
        /// </summary>
        private static void FindRelatedMembers(
            bool isOverride,
            bool overridingMemberIsFromSomeCompilation,
            SymbolKind overridingMemberKind,
            Symbol representativeMember,
            out ImmutableArray<Symbol> overriddenMembers,
            ref ArrayBuilder<Symbol> hiddenBuilder)
        {
            overriddenMembers = ImmutableArray<Symbol>.Empty;
 
            if ((object)representativeMember != null)
            {
                bool needToSearchForRelated = representativeMember.Kind != SymbolKind.Field && representativeMember.Kind != SymbolKind.NamedType &&
                                              (!representativeMember.ContainingType.IsDefinition || representativeMember.IsIndexer());
 
                if (isOverride)
                {
                    if (needToSearchForRelated)
                    {
                        ArrayBuilder<Symbol> overriddenBuilder = ArrayBuilder<Symbol>.GetInstance();
 
                        overriddenBuilder.Add(representativeMember);
 
                        FindOtherOverriddenMethodsInContainingType(representativeMember, overridingMemberIsFromSomeCompilation, overriddenBuilder);
 
                        overriddenMembers = overriddenBuilder.ToImmutableAndFree();
                    }
                    else
                    {
                        overriddenMembers = ImmutableArray.Create<Symbol>(representativeMember);
                    }
                }
                else
                {
                    AddHiddenMemberIfApplicable(ref hiddenBuilder, overridingMemberKind, representativeMember);
 
                    if (needToSearchForRelated)
                    {
                        FindOtherHiddenMembersInContainingType(overridingMemberKind, representativeMember, ref hiddenBuilder);
                    }
                }
            }
        }
 
        /// <summary>
        /// Some kinds of methods are not considered to be hideable by certain kinds of members.
        /// Specifically, methods, properties, and types cannot hide constructors, destructors,
        /// operators, conversions, or accessors.
        /// </summary>
        private static void AddHiddenMemberIfApplicable(ref ArrayBuilder<Symbol> hiddenBuilder, SymbolKind hidingMemberKind, Symbol hiddenMember)
        {
            Debug.Assert((object)hiddenMember != null);
            if (hiddenMember.Kind != SymbolKind.Method || ((MethodSymbol)hiddenMember).CanBeHiddenByMemberKind(hidingMemberKind))
            {
                AccessOrGetInstance(ref hiddenBuilder).Add(hiddenMember);
            }
        }
 
        private static ArrayBuilder<T> AccessOrGetInstance<T>(ref ArrayBuilder<T> builder)
        {
            if (builder == null)
            {
                builder = ArrayBuilder<T>.GetInstance();
            }
 
            return builder;
        }
 
        /// <summary>
        /// Having found the best member to override, we want to find members with the same signature on the
        /// best member's containing type.
        /// </summary>
        /// <param name="representativeMember">
        /// The member that we consider to be overridden (may have different custom modifiers from the overriding member).
        /// Assumed to already be in the overridden and runtime overridden lists.
        /// </param>
        /// <param name="overridingMemberIsFromSomeCompilation">
        /// If the best match was based on the custom modifier count, rather than the custom modifiers themselves 
        /// (because the overriding member is in the current compilation), then we should use the count when determining
        /// whether the override is ambiguous.
        /// </param>
        /// <param name="overriddenBuilder">
        /// If the declaring type is constructed, it's possible that two (or more) members have the same signature
        /// (including custom modifiers).  Return a list of such members so that we can report the ambiguity.
        /// </param>
        private static void FindOtherOverriddenMethodsInContainingType(Symbol representativeMember, bool overridingMemberIsFromSomeCompilation, ArrayBuilder<Symbol> overriddenBuilder)
        {
            Debug.Assert((object)representativeMember != null);
            Debug.Assert(representativeMember.Kind == SymbolKind.Property || !representativeMember.ContainingType.IsDefinition);
 
            int representativeCustomModifierCount = -1;
 
            foreach (Symbol otherMember in representativeMember.ContainingType.GetMembers(representativeMember.Name))
            {
                if (otherMember.Kind == representativeMember.Kind)
                {
                    if (otherMember != representativeMember)
                    {
                        // NOTE: If the overriding member is from source, then we compared *counts* of custom modifiers, rather
                        // than actually comparing custom modifiers.  Hence, we should do the same thing when looking for
                        // ambiguous overrides.
                        if (overridingMemberIsFromSomeCompilation)
                        {
                            if (representativeCustomModifierCount < 0)
                            {
                                representativeCustomModifierCount = representativeMember.CustomModifierCount();
                            }
 
                            if (MemberSignatureComparer.CSharpOverrideComparer.Equals(otherMember, representativeMember) &&
                                otherMember.CustomModifierCount() == representativeCustomModifierCount)
                            {
                                overriddenBuilder.Add(otherMember);
                            }
                        }
                        else
                        {
                            if (MemberSignatureComparer.CSharpCustomModifierOverrideComparer.Equals(otherMember, representativeMember))
                            {
                                overriddenBuilder.Add(otherMember);
                            }
                        }
                    }
                }
            }
        }
 
        /// <summary>
        /// Having found that we are hiding a method with exactly the same signature
        /// (including custom modifiers), we want to find methods with the same signature
        /// on the declaring type because they will also be hidden.
        /// (If the declaring type is constructed, it's possible that two or more
        /// methods have the same signature (including custom modifiers).)
        /// (If the representative member is an indexer, it's possible that two or more
        /// properties have the same signature (including custom modifiers, even in a
        /// non-generic type).
        /// </summary>
        /// <param name="hidingMemberKind">
        /// This kind of the hiding member.
        /// </param>
        /// <param name="representativeMember">
        /// The member that we consider to be hidden (must have exactly the same custom modifiers as the hiding member).
        /// Assumed to already be in hiddenBuilder.
        /// </param>
        /// <param name="hiddenBuilder">
        /// Will have all other members with the same signature (including custom modifiers) as 
        /// representativeMember added.
        /// </param>
        private static void FindOtherHiddenMembersInContainingType(SymbolKind hidingMemberKind, Symbol representativeMember, ref ArrayBuilder<Symbol> hiddenBuilder)
        {
            Debug.Assert((object)representativeMember != null);
            Debug.Assert(representativeMember.Kind != SymbolKind.Field);
            Debug.Assert(representativeMember.Kind != SymbolKind.NamedType);
            Debug.Assert(representativeMember.Kind == SymbolKind.Property || !representativeMember.ContainingType.IsDefinition);
 
            IEqualityComparer<Symbol> comparer = MemberSignatureComparer.CSharpCustomModifierOverrideComparer;
            foreach (Symbol otherMember in representativeMember.ContainingType.GetMembers(representativeMember.Name))
            {
                if (otherMember.Kind == representativeMember.Kind)
                {
                    if (otherMember != representativeMember && comparer.Equals(otherMember, representativeMember))
                    {
                        AddHiddenMemberIfApplicable(ref hiddenBuilder, hidingMemberKind, otherMember);
                    }
                }
            }
        }
 
        private static bool CanOverrideOrHide(Symbol member)
        {
            switch (member.Kind)
            {
                case SymbolKind.Property:
                case SymbolKind.Event:
                    // Explicit interface impls don't override or hide.
                    return !member.IsExplicitInterfaceImplementation();
                case SymbolKind.Method:
                    MethodSymbol methodSymbol = (MethodSymbol)member;
                    return MethodSymbol.CanOverrideOrHide(methodSymbol.MethodKind) && ReferenceEquals(methodSymbol, methodSymbol.ConstructedFrom);
                default:
                    throw ExceptionUtilities.UnexpectedValue(member.Kind);
            }
        }
 
        private static bool TypeOrReturnTypeHasCustomModifiers(Symbol member)
        {
            switch (member.Kind)
            {
                case SymbolKind.Method:
                    MethodSymbol method = (MethodSymbol)member;
                    var methodReturnType = method.ReturnTypeWithAnnotations;
                    return methodReturnType.CustomModifiers.Any() || method.RefCustomModifiers.Any() ||
                           methodReturnType.Type.HasCustomModifiers(flagNonDefaultArraySizesOrLowerBounds: false);
                case SymbolKind.Property:
                    PropertySymbol property = (PropertySymbol)member;
                    var propertyType = property.TypeWithAnnotations;
                    return propertyType.CustomModifiers.Any() || property.RefCustomModifiers.Any() ||
                           propertyType.Type.HasCustomModifiers(flagNonDefaultArraySizesOrLowerBounds: false);
                case SymbolKind.Event:
                    EventSymbol @event = (EventSymbol)member;
                    return @event.Type.HasCustomModifiers(flagNonDefaultArraySizesOrLowerBounds: false); //can't have custom modifiers on (vs in) type
                default:
                    throw ExceptionUtilities.UnexpectedValue(member.Kind);
            }
        }
 
        private static int CustomModifierCount(Symbol member)
        {
            switch (member.Kind)
            {
                case SymbolKind.Method:
                    MethodSymbol method = (MethodSymbol)member;
                    return method.CustomModifierCount();
                case SymbolKind.Property:
                    PropertySymbol property = (PropertySymbol)member;
                    return property.CustomModifierCount();
                case SymbolKind.Event:
                    EventSymbol @event = (EventSymbol)member;
                    return @event.Type.CustomModifierCount();
                default:
                    throw ExceptionUtilities.UnexpectedValue(member.Kind);
            }
        }
 
        /// <summary>
        /// Determine if this method requires a methodimpl table entry to inform the runtime of the override relationship.
        /// </summary>
        /// <param name="warnAmbiguous">True if we should produce an ambiguity warning per https://github.com/dotnet/roslyn/issues/45453 .</param>
        internal static bool RequiresExplicitOverride(this MethodSymbol method, out bool warnAmbiguous)
        {
            warnAmbiguous = false;
            if (!method.IsOverride)
                return false;
 
            MethodSymbol csharpOverriddenMethod = method.OverriddenMethod;
            if (csharpOverriddenMethod is null)
                return false;
 
            MethodSymbol runtimeOverriddenMethod = method.GetFirstRuntimeOverriddenMethodIgnoringNewSlot(out bool wasAmbiguous);
            if (csharpOverriddenMethod == runtimeOverriddenMethod && !wasAmbiguous)
                return false;
 
            // See https://github.com/dotnet/roslyn/issues/45453. No need to warn when the runtime
            // supports covariant returns because any methodimpl we produce to identify the specific
            // overridden method is unambiguously understood by the runtime.
            if (method.ContainingAssembly.RuntimeSupportsCovariantReturnsOfClasses)
                return true;
 
            // If the method was declared as a covariant return, there will be a compile-time error since the runtime
            // does not support covariant returns. In this case we do not warn about runtime ambiguity and pretend that
            // we can use a methodimpl (even though it is of a form not supported by the runtime and would result in a
            // loader error) so that the symbol APIs produce the most useful result.
            if (!method.ReturnType.Equals(csharpOverriddenMethod.ReturnType, TypeCompareKind.AllIgnoreOptions))
                return true;
 
            // Due to https://github.com/dotnet/runtime/issues/38119 the methodimpl would
            // appear to the runtime to be ambiguous in some cases.
            bool methodimplWouldBeAmbiguous = csharpOverriddenMethod.MethodHasRuntimeCollision();
            if (!methodimplWouldBeAmbiguous)
                return true;
 
            Debug.Assert(runtimeOverriddenMethod is { });
 
            // We produce the warning when a methodimpl would be required but would be ambiguous to the runtime.
            // However, if there was a duplicate definition for the runtime signature of the overridden
            // method where it was originally declared, that would have been an error.  In that case we suppress
            // the warning as a cascaded diagnostic.
            bool originalOverriddenMethodWasAmbiguous =
                csharpOverriddenMethod.IsDefinition || csharpOverriddenMethod.OriginalDefinition.MethodHasRuntimeCollision();
            warnAmbiguous = !originalOverriddenMethodWasAmbiguous;
 
            bool overriddenMethodContainedInSameTypeAsRuntimeOverriddenMethod =
                csharpOverriddenMethod.ContainingType.Equals(runtimeOverriddenMethod.ContainingType, TypeCompareKind.CLRSignatureCompareOptions);
 
            // If the overridden method is on a different (e.g. base) type compared to the runtime overridden
            // method, then the runtime overridden method could not possibly resolve correctly to the overridden method.
            // In this case we might as well produce a methodimpl. At least it has a chance of being correctly resolved
            // by the runtime, where the runtime resolution without the methodimpl would definitely be wrong.
            if (!overriddenMethodContainedInSameTypeAsRuntimeOverriddenMethod)
                return true;
 
            // This is the historical test, preserved since the days of the native compiler in case it turns out to affect compatibility.
            // However, this test cannot be true in a program free of errors.
            return csharpOverriddenMethod != runtimeOverriddenMethod && method.IsAccessor() != runtimeOverriddenMethod.IsAccessor();
        }
 
        internal static bool MethodHasRuntimeCollision(this MethodSymbol method)
        {
            foreach (Symbol otherMethod in method.ContainingType.GetMembers(method.Name))
            {
                if (otherMethod != method && MemberSignatureComparer.RuntimeSignatureComparer.Equals(otherMethod, method))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        /// <summary>
        /// Given a method, find the first method that it overrides from the perspective of the CLI.
        /// Key differences from C#: non-virtual methods are ignored, the RuntimeSignatureComparer
        /// is used (i.e. consider return types, ignore ref/out distinction).  Sets <paramref name="wasAmbiguous"/>
        /// to true if more than one method is overridden by CLI rules.
        /// </summary>
        /// <remarks>
        /// WARN: Must not check method.MethodKind - PEMethodSymbol.ComputeMethodKind uses this method.
        /// NOTE: Does not check whether the given method will be marked "newslot" in metadata (as
        /// "newslot" is used for covariant method overrides).
        /// </remarks>
        internal static MethodSymbol GetFirstRuntimeOverriddenMethodIgnoringNewSlot(this MethodSymbol method, out bool wasAmbiguous)
        {
            // WARN: If the method may override a source method and declaration diagnostics have yet to
            // be computed, then it is important for us to pass ignoreInterfaceImplementationChanges: true
            // (see MethodSymbol.IsMetadataVirtual for details).
            // Since we are only concerned with overrides (of class methods), interface implementations can be ignored.
            const MethodSymbol.IsMetadataVirtualOption ignoreInterfaceImplementationChanges = MethodSymbol.IsMetadataVirtualOption.IgnoreInterfaceImplementationChanges;
 
            wasAmbiguous = false;
            if (!method.IsMetadataVirtual(ignoreInterfaceImplementationChanges) || method.IsStatic)
            {
                return null;
            }
 
            NamedTypeSymbol containingType = method.ContainingType;
 
            for (NamedTypeSymbol currType = containingType.BaseTypeNoUseSiteDiagnostics; !ReferenceEquals(currType, null); currType = currType.BaseTypeNoUseSiteDiagnostics)
            {
                MethodSymbol candidate = null;
                foreach (Symbol otherMember in currType.GetMembers(method.Name))
                {
                    if (otherMember.Kind == SymbolKind.Method &&
                        IsOverriddenSymbolAccessible(otherMember, containingType) &&
                        MemberSignatureComparer.RuntimeSignatureComparer.Equals(method, otherMember))
                    {
                        MethodSymbol overridden = (MethodSymbol)otherMember;
 
                        // NOTE: The runtime doesn't consider non-virtual methods during override resolution.
                        if (overridden.IsMetadataVirtual(ignoreInterfaceImplementationChanges))
                        {
                            if (candidate is { })
                            {
                                // found more than one possible override in this type
                                wasAmbiguous = true;
                                return candidate;
                            }
 
                            candidate = overridden;
                        }
                    }
                }
 
                if (candidate is { })
                {
                    return candidate;
                }
            }
 
            return null;
        }
 
        /// <remarks>
        /// Note that the access check is done using the original definitions.  This is because we want to avoid
        /// reductions in accessibility that result from type argument substitution (e.g. if an inaccessible type
        /// has been passed as a type argument).
        /// See DevDiv #11967 for an example.
        /// </remarks>
        private static bool IsOverriddenSymbolAccessible(Symbol overridden, NamedTypeSymbol overridingContainingType)
        {
            var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
            return AccessCheck.IsSymbolAccessible(overridden.OriginalDefinition, overridingContainingType.OriginalDefinition, ref discardedUseSiteInfo);
        }
    }
}