// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    /// <summary>
    /// Represents a type other than an array, a pointer, a type parameter, and dynamic.
    /// </summary>
    internal abstract partial class NamedTypeSymbol : TypeSymbol, INamedTypeSymbolInternal
    {
        private bool _hasNoBaseCycles;

        private static readonly ImmutableSegmentedDictionary<string, Symbol> RequiredMembersErrorSentinel = ImmutableSegmentedDictionary<string, Symbol>.Empty.Add("<error sentinel>", null);

        /// <summary>
        /// <see langword="default"/> if uninitialized. <see cref="RequiredMembersErrorSentinel"/> if there are errors. <see cref="ImmutableSegmentedDictionary{TKey, TValue}.Empty"/> if
        /// there are no required members. Otherwise, the required members.
        /// </summary>
        private ImmutableSegmentedDictionary<string, Symbol> _lazyRequiredMembers = default;

#nullable enable
        private UncommonProperties? _lazyUncommonProperties;

        private class UnionData
        {
            public ImmutableArray<MethodSymbol> _lazyFactoryMethods;
            public ImmutableArray<TypeSymbol> _lazyCaseTypes;
            public Symbol? _lazyValueProperty = ErrorTypeSymbol.UnknownResultType;
            public StrongBox<NullableFlowState>? _lazyValueDeclaredNullableFlowState;
            public Symbol? _lazyHasValueProperty = ErrorTypeSymbol.UnknownResultType;
            public ImmutableArray<MethodSymbol> _lazyTryGetValueMethods;
            public ImmutableArray<TypeUnionValueSet.CaseInfo> _lazyTypeUnionValueSetCases;
        }

        private sealed class UnionDataForDefinition : UnionData
        {
            public NamedTypeSymbol? _lazyMemberProviderInterface = ErrorTypeSymbol.UnknownResultType;
            public ImmutableArray<NamedTypeSymbol> _lazyMemberProviderInterfaceAllInterfaces;
        }

        private sealed class ClosedClassData
        {
            /// <summary>
            /// This field is safe to read only after checking that <see cref="_lazySubtypes"/> has been initialized.
            /// Reading of <see cref="_lazySubtypes"/> for this purpose should be done
            /// using <see cref="RoslynImmutableInterlocked.VolatileRead{T}(ref readonly ImmutableArray{T})"/> API
            /// to enforce order of read operations between the fields.
            /// </summary>
            public bool _lazySubtypesIsComplete;
            public ImmutableArray<NamedTypeSymbol> _lazySubtypes;
            public ImmutableArray<TypeUnionValueSet.CaseInfo> _lazyTypeUnionValueSetCases;
        }

        private sealed partial class UncommonProperties
        {
            public UnionData? _lazyUnionData;
            public ClosedClassData? _lazyClosedClassData;
        }
#nullable disable

        // Only the compiler can create NamedTypeSymbols.
        internal NamedTypeSymbol(TupleExtraData tupleData = null)
        {
            if (tupleData is object)
            {
                _lazyUncommonProperties = new UncommonProperties() { _lazyTupleData = tupleData };
            }
        }

        // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        // Changes to the public interface of this class should remain synchronized with the VB version.
        // Do not make any changes to the public interface without making the corresponding change
        // to the VB version.
        // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        // TODO: How should anonymous types be represented? One possible options: have an
        // IsAnonymous on this type. The Name would then return a unique compiler-generated
        // type that matches the metadata name.

        /// <summary>
        /// Returns the arity of this type, or the number of type parameters it takes.
        /// A non-generic type has zero arity.
        /// </summary>
        public abstract int Arity { get; }

        /// <summary>
        /// Returns the type parameters that this type has. If this is a non-generic type,
        /// returns an empty ImmutableArray.  
        /// </summary>
        public abstract ImmutableArray<TypeParameterSymbol> TypeParameters { get; }

        /// <summary>
        /// Returns the type arguments that have been substituted for the type parameters. 
        /// If nothing has been substituted for a given type parameter,
        /// then the type parameter itself is consider the type argument.
        /// </summary>
        internal abstract ImmutableArray<TypeWithAnnotations> TypeArgumentsWithAnnotationsNoUseSiteDiagnostics { get; }

        internal ImmutableArray<TypeWithAnnotations> TypeArgumentsWithDefinitionUseSiteDiagnostics(ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            var result = TypeArgumentsWithAnnotationsNoUseSiteDiagnostics;

            foreach (var typeArgument in result)
            {
                typeArgument.Type.OriginalDefinition.AddUseSiteInfo(ref useSiteInfo);
            }

            return result;
        }

        internal TypeWithAnnotations TypeArgumentWithDefinitionUseSiteDiagnostics(int index, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            var result = TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[index];
            result.Type.OriginalDefinition.AddUseSiteInfo(ref useSiteInfo);
            return result;
        }

        /// <summary>
        /// Returns the type symbol that this type was constructed from. This type symbol
        /// has the same containing type (if any), but has type arguments that are the same
        /// as the type parameters (although its containing type might not).
        /// </summary>
        public abstract NamedTypeSymbol ConstructedFrom { get; }

        /// <summary>
        /// For enum types, gets the underlying type. Returns null on all other
        /// kinds of types.
        /// </summary>
        public virtual NamedTypeSymbol EnumUnderlyingType
        {
            get
            {
                return null;
            }
        }

        public override NamedTypeSymbol ContainingType
        {
            get
            {
                // we can do this since if a type does not live directly in a type
                // there is no containing type at all
                // NOTE: many derived types will override this with even better implementation
                //       since most know their containing types/symbols directly
                return this.ContainingSymbol as NamedTypeSymbol;
            }
        }

        /// <summary>
        /// Returns true for a struct type containing a cycle.
        /// This property is intended for flow analysis only
        /// since it is only implemented for source types.
        /// </summary>
        internal virtual bool KnownCircularStruct
        {
            get
            {
                return false;
            }
        }

        internal bool KnownToHaveNoDeclaredBaseCycles
        {
            get
            {
                return _hasNoBaseCycles;
            }
        }

        internal void SetKnownToHaveNoDeclaredBaseCycles()
        {
            _hasNoBaseCycles = true;
        }

        /// <summary>
        /// Is this a NoPia local type explicitly declared in source, i.e.
        /// top level type with a TypeIdentifier attribute on it?
        /// </summary>
        internal virtual bool IsExplicitDefinitionOfNoPiaLocalType
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Returns true and a string from the first GuidAttribute on the type, 
        /// the string might be null or an invalid guid representation. False, 
        /// if there is no GuidAttribute with string argument.
        /// </summary>
        internal abstract bool GetGuidString(out string guidString);

#nullable enable
        /// <summary>
        /// For delegate types, gets the delegate's invoke method.  Returns null on
        /// all other kinds of types.  Note that it is possible to have an ill-formed
        /// delegate type imported from metadata which does not have an Invoke method.
        /// Such a type will be classified as a delegate but its DelegateInvokeMethod
        /// would be null.
        /// </summary>
        public MethodSymbol? DelegateInvokeMethod
        {
            get
            {
                if (TypeKind != TypeKind.Delegate)
                {
                    return null;
                }

                var methods = GetMembers(WellKnownMemberNames.DelegateInvokeName);
                if (methods.Length != 1)
                {
                    return null;
                }

                var method = methods[0] as MethodSymbol;

                //EDMAURER we used to also check 'method.IsVirtual' because section 13.6
                //of the CLI spec dictates that it be virtual, but real world
                //working metadata has been found that contains an Invoke method that is
                //marked as virtual but not newslot (both of those must be combined to
                //meet the C# definition of virtual). Rather than weaken the check
                //I've removed it, as the Dev10 compiler makes no check, and we don't
                //stand to gain anything by having it.

                //return method != null && method.IsVirtual ? method : null;
                return method;
            }
        }
#nullable disable

        /// <summary>
        /// Adds the operators for this type by their metadata name to <paramref name="operators"/>
        /// </summary>
        internal void AddOperators(string name, ArrayBuilder<MethodSymbol> operators)
        {
            ImmutableArray<Symbol> candidates = GetSimpleNonTypeMembers(name);
            if (candidates.IsEmpty)
                return;

            AddOperators(operators, candidates);
        }

        internal static void AddOperators(ArrayBuilder<MethodSymbol> operators, ImmutableArray<Symbol> candidates)
        {
            foreach (var candidate in candidates)
            {
                if (candidate is MethodSymbol { MethodKind: MethodKind.UserDefinedOperator or MethodKind.Conversion } method)
                {
                    operators.Add(method);
                }
            }
        }

        internal static void AddOperators(ArrayBuilder<MethodSymbol> operators, ArrayBuilder<Symbol> candidates)
        {
            foreach (var candidate in candidates)
            {
                if (candidate is MethodSymbol { MethodKind: MethodKind.UserDefinedOperator or MethodKind.Conversion } method)
                {
                    operators.Add(method);
                }
            }
        }

        /// <summary>
        /// Get the instance constructors for this type.
        /// </summary>
        public ImmutableArray<MethodSymbol> InstanceConstructors
        {
            get
            {
                return GetConstructors(includeInstance: true, includeStatic: false);
            }
        }

        /// <summary>
        /// Get the static constructors for this type.
        /// </summary>
        public ImmutableArray<MethodSymbol> StaticConstructors
        {
            get
            {
                return GetConstructors(includeInstance: false, includeStatic: true);
            }
        }

        /// <summary>
        /// Get the instance and static constructors for this type.
        /// </summary>
        public ImmutableArray<MethodSymbol> Constructors
        {
            get
            {
                return GetConstructors(includeInstance: true, includeStatic: true);
            }
        }

        private ImmutableArray<MethodSymbol> GetConstructors(bool includeInstance, bool includeStatic)
        {
            Debug.Assert(includeInstance || includeStatic);

            ImmutableArray<Symbol> instanceCandidates = includeInstance
                ? GetMembers(WellKnownMemberNames.InstanceConstructorName)
                : ImmutableArray<Symbol>.Empty;
            ImmutableArray<Symbol> staticCandidates = includeStatic
                ? GetMembers(WellKnownMemberNames.StaticConstructorName)
                : ImmutableArray<Symbol>.Empty;

            if (instanceCandidates.IsEmpty && staticCandidates.IsEmpty)
            {
                return ImmutableArray<MethodSymbol>.Empty;
            }

            ArrayBuilder<MethodSymbol> constructors = ArrayBuilder<MethodSymbol>.GetInstance();
            foreach (Symbol candidate in instanceCandidates)
            {
                if (candidate is MethodSymbol method)
                {
                    Debug.Assert(method.MethodKind == MethodKind.Constructor);
                    constructors.Add(method);
                }
            }
            foreach (Symbol candidate in staticCandidates)
            {
                if (candidate is MethodSymbol method)
                {
                    Debug.Assert(method.MethodKind == MethodKind.StaticConstructor);
                    constructors.Add(method);
                }
            }
            return constructors.ToImmutableAndFree();
        }

        /// <summary>
        /// Get the indexers for this type.
        /// </summary>
        /// <remarks>
        /// Won't include indexers that are explicit interface implementations.
        /// </remarks>
        public ImmutableArray<PropertySymbol> Indexers
        {
            get
            {
                ImmutableArray<Symbol> candidates = GetSimpleNonTypeMembers(WellKnownMemberNames.Indexer);

                if (candidates.IsEmpty)
                {
                    return ImmutableArray<PropertySymbol>.Empty;
                }

                // The common case will be returning a list with the same elements as "candidates",
                // but we need a list of PropertySymbols, so we're stuck building a new list anyway.
                ArrayBuilder<PropertySymbol> indexers = ArrayBuilder<PropertySymbol>.GetInstance();
                foreach (Symbol candidate in candidates)
                {
                    if (candidate.Kind == SymbolKind.Property)
                    {
                        Debug.Assert(((PropertySymbol)candidate).IsIndexer);
                        indexers.Add((PropertySymbol)candidate);
                    }
                }
                return indexers.ToImmutableAndFree();
            }
        }

        /// <summary>
        /// Returns true if this type might contain extension members or methods. If this property
        /// returns false, there are no extension members or methods in this type.
        /// </summary>
        /// <remarks>
        /// This property allows the search for extension members or methods to be narrowed quickly.
        /// </remarks>
        public abstract bool MightContainExtensions { get; }

#nullable enable
        /// <remarks>Does not perform a full viability check</remarks>
        private void DoGetExtensionMethods(ArrayBuilder<Symbol> methods, string? nameOpt, int arity, LookupOptions options, PooledHashSet<MethodSymbol>? implementationsToShadow)
        {
            var members = nameOpt == null
                ? this.GetMembersUnordered()
                : this.GetSimpleNonTypeMembers(nameOpt);

            foreach (var member in members)
            {
                if (member.Kind == SymbolKind.Method)
                {
                    var method = (MethodSymbol)member;
                    if (method.IsExtensionMethod &&
                        ((options & LookupOptions.AllMethodsOnArityZero) != 0 || arity == method.Arity))
                    {
                        var thisParam = method.Parameters.First();
                        if (!IsValidExtensionReceiverParameter(thisParam))
                        {
                            continue;
                        }

                        if (implementationsToShadow is null || !implementationsToShadow.Remove(method.OriginalDefinition))
                        {
                            Debug.Assert(method.MethodKind != MethodKind.ReducedExtension);
                            methods.Add(method);
                        }
                    }
                }
            }
        }

        private static bool IsValidExtensionReceiverParameter(ParameterSymbol thisParam)
        {
            Debug.Assert(thisParam is not null);

            if (!thisParam.Type.IsValidExtensionParameterType())
            {
                return false;
            }

            // For ref and ref-readonly extension members and classic extension methods, receivers need to be of the correct types to be considered in lookup
            if (thisParam.RefKind == RefKind.Ref && !thisParam.Type.IsValueType)
            {
                return false;
            }

            if (thisParam.RefKind is RefKind.In or RefKind.RefReadOnlyParameter
                && !thisParam.Type.IsValidInOrRefReadonlyExtensionParameterType())
            {
                return false;
            }

            return true;
        }

        /// <remarks>Does not perform a full viability check</remarks>
        internal void GetAllExtensionMembers(ArrayBuilder<Symbol> members, string? name, string? alternativeName, int arity, LookupOptions options, ConsList<FieldSymbol> fieldsBeingBound)
        {
            Debug.Assert((options & ~(LookupOptions.AllMethodsOnArityZero
                | LookupOptions.MustBeInstance | LookupOptions.MustNotBeInstance | LookupOptions.MustBeInvocableIfMember
                | LookupOptions.MustBeOperator | LookupOptions.MustNotBeMethodTypeParameter)) == 0);

            Debug.Assert(name is not null || alternativeName is null);

            if (!MightContainExtensions)
                return;

            PooledHashSet<MethodSymbol>? implementationsToShadow = null;

            if (this.IsClassType() && IsStatic && !IsGenericType)
            {
                doGetExtensionMembers(members, name, alternativeName, arity, options, ref implementationsToShadow, fieldsBeingBound);
            }

            if (!options.HasFlag(LookupOptions.MustBeOperator))
            {
                DoGetExtensionMethods(members, name, arity, options, implementationsToShadow);
                if (alternativeName is not null)
                {
                    DoGetExtensionMethods(members, alternativeName, arity, options, implementationsToShadow);
                }
            }

            implementationsToShadow?.Free();

            return;

            void doGetExtensionMembers(ArrayBuilder<Symbol> members, string? name, string? alternativeName, int arity, LookupOptions options, ref PooledHashSet<MethodSymbol>? implementationsToShadow, ConsList<FieldSymbol> fieldsBeingBound)
            {
                foreach (NamedTypeSymbol nestedType in GetTypeMembers(name: ""))
                {
                    if (nestedType is not { IsExtension: true, ExtensionParameter: { } extensionParameter }
                        || !IsValidExtensionReceiverParameter(extensionParameter))
                    {
                        continue;
                    }

                    var candidates = name is null || alternativeName is not null
                        ? nestedType.GetMembersUnordered()
                        : nestedType.GetMembers(name);

                    foreach (var candidate in candidates)
                    {
                        if (!SourceMemberContainerTypeSymbol.IsAllowedExtensionMember(candidate, LanguageVersion.Preview))
                        {
                            // Not supported yet
                            continue;
                        }

                        if (extensionMemberMatches(candidate, name, alternativeName, arity, options, fieldsBeingBound))
                        {
                            members.Add(candidate);

                            if (candidate is MethodSymbol { IsStatic: false } shadows &&
                                shadows.OriginalDefinition.TryGetCorrespondingExtensionImplementationMethod() is { } toShadow)
                            {
                                implementationsToShadow ??= PooledHashSet<MethodSymbol>.GetInstance();
                                implementationsToShadow.Add(toShadow);
                            }
                        }
                    }
                }
            }

            static bool extensionMemberMatches(Symbol member, string? name, string? alternativeName, int arity, LookupOptions options, ConsList<FieldSymbol> fieldsBeingBound)
            {
                if ((options & LookupOptions.MustBeInstance) != 0 && member.IsStatic)
                {
                    return false;
                }

                if ((options & LookupOptions.MustNotBeInstance) != 0 && !member.IsStatic)
                {
                    return false;
                }

                if ((options & LookupOptions.MustBeOperator) != 0 && member is not MethodSymbol { MethodKind: MethodKind.UserDefinedOperator })
                {
                    return false;
                }

                Debug.Assert(member.Kind != SymbolKind.NamedType);
                if (Binder.WrongArity(member, arity, diagnose: false, options, out _))
                {
                    return false;
                }

                string memberName = member.Name;
                bool namesMatch = name is null
                    || memberName == name
                    || (alternativeName is not null && memberName == alternativeName);

                if (!namesMatch)
                {
                    return false;
                }

                if ((options & LookupOptions.MustBeInvocableIfMember) != 0
                    && !Binder.IsInvocableMember(member, fieldsBeingBound))
                {
                    return false;
                }

                return true;
            }
        }

        public virtual MethodSymbol? TryGetCorrespondingExtensionImplementationMethod(MethodSymbol method)
        {
            throw ExceptionUtilities.Unreachable();
        }

#nullable disable

        // TODO: Probably should provide similar accessors for static constructor, destructor, 
        // TODO: operators, conversions.

        /// <summary>
        /// Returns true if this type is known to be a reference type. It is never the case that
        /// IsReferenceType and IsValueType both return true. However, for an unconstrained type
        /// parameter, IsReferenceType and IsValueType will both return false.
        /// </summary>
        public override bool IsReferenceType
        {
            get
            {
                var kind = TypeKind;
                return kind != TypeKind.Enum && kind != TypeKind.Struct && kind != TypeKind.Error;
            }
        }

        /// <summary>
        /// Returns true if this type is known to be a value type. It is never the case that
        /// IsReferenceType and IsValueType both return true. However, for an unconstrained type
        /// parameter, IsReferenceType and IsValueType will both return false.
        /// </summary>
        public override bool IsValueType
        {
            get
            {
                var kind = TypeKind;
                return kind == TypeKind.Struct || kind == TypeKind.Enum;
            }
        }

        internal override ManagedKind GetManagedKind(ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            // CONSIDER: we could cache this, but it's only expensive for non-special struct types
            // that are pointed to.  For now, only cache on SourceMemberContainerSymbol since it fits
            // nicely into the flags variable.
            return BaseTypeAnalysis.GetManagedKind(this, ref useSiteInfo);
        }

        /// <summary>
        /// Gets the associated attribute usage info for an attribute type.
        /// </summary>
        internal abstract AttributeUsageInfo GetAttributeUsageInfo();

        /// <summary>
        /// Returns true if the type is a Script class. 
        /// It might be an interactive submission class or a Script class in a csx file.
        /// </summary>
        public virtual bool IsScriptClass
        {
            get
            {
                return false;
            }
        }

        internal bool IsSubmissionClass
        {
            get
            {
                return TypeKind == TypeKind.Submission;
            }
        }

        internal SynthesizedInstanceConstructor GetScriptConstructor()
        {
            Debug.Assert(IsScriptClass);
            return (SynthesizedInstanceConstructor)InstanceConstructors.Single();
        }

        internal SynthesizedInteractiveInitializerMethod GetScriptInitializer()
        {
            Debug.Assert(IsScriptClass);
            return (SynthesizedInteractiveInitializerMethod)GetMembers(SynthesizedInteractiveInitializerMethod.InitializerName).Single();
        }

        internal SynthesizedEntryPointSymbol GetScriptEntryPoint()
        {
            Debug.Assert(IsScriptClass);
            var name = (TypeKind == TypeKind.Submission) ? SynthesizedEntryPointSymbol.FactoryName : SynthesizedEntryPointSymbol.MainName;
            return (SynthesizedEntryPointSymbol)GetMembers(name).Single();
        }

        /// <summary>
        /// Returns true if the type is the implicit class that holds onto invalid global members (like methods or
        /// statements in a non script file).
        /// </summary>
        public virtual bool IsImplicitClass
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets the name of this symbol. Symbols without a name return the empty string; null is
        /// never returned.
        /// </summary>
        public abstract override string Name { get; }

#nullable enable
        /// <summary>
        /// Return the name including the metadata arity suffix.
        /// </summary>
        public override string MetadataName
        {
            get
            {
                var fileIdentifier = this.GetFileLocalTypeMetadataNamePrefix();
                // If we have a file prefix, the type will definitely use CLS arity encoding for nonzero arity.
                Debug.Assert(!(fileIdentifier != null && !MangleName && Arity > 0));
                return fileIdentifier != null || MangleName
                    ? MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, fileIdentifier)
                    : Name;
            }
        }

        internal abstract bool IsFileLocal { get; }

        /// <summary>
        /// If this type is a file-local type, returns an identifier for the file this type was declared in. Otherwise, returns null.
        /// </summary>
        internal abstract FileIdentifier? AssociatedFileIdentifier { get; }

        [MemberNotNullWhen(true, nameof(ExtensionGroupingName), nameof(ExtensionMarkerName))]
        public virtual bool IsExtension
            => TypeKind == TypeKind.Extension;

        /// <summary>
        /// For the type representing an extension declaration, returns the receiver parameter symbol.
        /// It may be unnamed.
        /// Note: this may be null even if <see cref="IsExtension"/> is true, in error cases.
        /// </summary>
        internal abstract ParameterSymbol? ExtensionParameter { get; }

        /// <summary>
        /// For extensions, returns the synthesized identifier for the grouping type.
        /// Returns null otherwise.
        /// </summary>
        internal abstract string? ExtensionGroupingName { get; }

        /// <summary>
        /// For extensions, returns the synthesized identifier for the marker type.
        /// Returns null otherwise.
        /// </summary>
        internal abstract string? ExtensionMarkerName { get; }
#nullable disable

        /// <summary>
        /// Should the name returned by Name property be mangled with [`arity] suffix in order to get metadata name.
        /// Must return False for a type with Arity == 0.
        /// </summary>
        /// <remarks>
        /// Some types with Arity > 0 still have MangleName == false. For example, EENamedTypeSymbol.
        /// Note that other differences between source names and metadata names exist and are not controlled by this property,
        /// such as the 'AssociatedFileIdentifier' prefix for file types.
        /// </remarks>
        internal abstract bool MangleName
        {
            // Intentionally no default implementation to force consideration of appropriate implementation for each new subclass
            get;
        }

        /// <summary>
        /// Collection of names of members declared within this type. May return duplicates.
        /// </summary>
        public abstract IEnumerable<string> MemberNames { get; }

        /// <summary>
        /// True if this type declares any required members. It does not recursively check up the tree for _all_ required members.
        /// </summary>
        internal abstract bool HasDeclaredRequiredMembers { get; }

#nullable enable
        /// <summary>Returns true if the type is 'closed', i.e. an abstract class where subtyping is only permitted in the current module.</summary>
        internal abstract bool IsClosed { get; }

        /// <summary>
        /// Tries to get the set of possible subtypes of a closed type.
        /// </summary>
        /// <remarks>
        /// When a closed class contains type parameters, it's possible that some subtype may or
        /// may not apply, depending on what type substitution is ultimately performed at a later stage.
        /// This call will return <see langword="false"/> and only the subtypes which are speakable in terms of type parameters on <see langword="this"/> in that situation.
        /// </remarks>
        internal bool TryGetClosedSubtypes(out ImmutableArray<NamedTypeSymbol> subtypes, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (!IsClosed)
            {
                subtypes = [];
                return false;
            }

            ClosedClassData lazyClosedClassData = GetClosedClassData();
            ImmutableArray<NamedTypeSymbol> lazySubtypes = RoslynImmutableInterlocked.VolatileRead(in lazyClosedClassData._lazySubtypes);
            if (!lazySubtypes.IsDefault)
            {
                subtypes = lazySubtypes;
                return lazyClosedClassData._lazySubtypesIsComplete;
            }

            (lazyClosedClassData._lazySubtypesIsComplete, lazySubtypes) = calculateClosedSubtypes(cancellationToken);
            ImmutableInterlocked.InterlockedInitialize(ref lazyClosedClassData._lazySubtypes, lazySubtypes);

            subtypes = lazyClosedClassData._lazySubtypes;
            return lazyClosedClassData._lazySubtypesIsComplete;

            (bool, ImmutableArray<NamedTypeSymbol>) calculateClosedSubtypes(CancellationToken cancellationToken)
            {
                var candidateSubtypes = CandidateClosedSubtypeDefinitions;
                if (!IsGenericType && candidateSubtypes.All(subtype => !subtype.IsGenericType))
                {
                    return (true, candidateSubtypes);
                }

                var resultBuilder = ArrayBuilder<NamedTypeSymbol>.GetInstance(candidateSubtypes.Length);
                var baseTypeTypeParameters = PooledHashSet<TypeParameterSymbol>.GetInstance();
                this.FindTypeParameters(baseTypeTypeParameters);

                var success = tryGetSpeakableSubtypes(this, candidateSubtypes, resultBuilder, baseTypeTypeParameters, cancellationToken);
                baseTypeTypeParameters.Free();

                return (success, resultBuilder.ToImmutableAndFree());
            }

            static bool tryGetSpeakableSubtypes(NamedTypeSymbol @this, ImmutableArray<NamedTypeSymbol> candidateSubtypes, ArrayBuilder<NamedTypeSymbol> resultBuilder, HashSet<TypeParameterSymbol> baseTypeTypeParameters, CancellationToken cancellationToken)
            {
                bool allSubtypesAreSpeakable = true;
                foreach (var candidateSubtype in candidateSubtypes)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    if (TypeUnification.TryUnifyClosedSubtype(candidateSubtype, closedType: @this) is { } unifiedSubtype)
                    {
                        if (unifiedSubtype.IsGenericType && unifiedSubtype.ContainsAdditionalTypeParameter(allowedTypeParameters: baseTypeTypeParameters))
                        {
                            // If 'unifiedSubtype' contains type parameters which are not present in '@this',
                            // it implies 'unifiedSubtype' was able to unify but is not speakable at the use site.
                            allSubtypesAreSpeakable = false;
                            continue;
                        }

                        resultBuilder.Add(unifiedSubtype);
                    }
                }

                return allSubtypesAreSpeakable;
            }
        }

        internal ImmutableArray<TypeUnionValueSet.CaseInfo> ClosedClassTypeUnionValueSetCases()
        {
            ClosedClassData lazyClosedClassData = GetClosedClassData();
            ImmutableArray<TypeUnionValueSet.CaseInfo> lazyTypeUnionValueSetCases = lazyClosedClassData._lazyTypeUnionValueSetCases;
            if (!lazyTypeUnionValueSetCases.IsDefault)
            {
                return lazyTypeUnionValueSetCases;
            }

            var builder = ArrayBuilder<TypeUnionValueSet.CaseInfo>.GetInstance();
            var setBuilder = AllIgnoreOptionsSetPool.Allocate();
            ValueSetFactory.ClosedClassTypeUnionValueSetFactory.ExpandClosedSubtypes(this, builder, setBuilder);
            setBuilder.Free();

            ImmutableInterlocked.InterlockedInitialize(ref lazyClosedClassData._lazyTypeUnionValueSetCases, builder.ToImmutableAndFree());
            return lazyClosedClassData._lazyTypeUnionValueSetCases;
        }

        /// <summary>
        /// Gets the set of subtype definitions in the same module whose base type has same original definition as 'this'.
        /// </summary>
        internal abstract ImmutableArray<NamedTypeSymbol> CandidateClosedSubtypeDefinitions { get; }

        /// <summary>
        /// Whether the type encountered an error while trying to build its complete list of required members.
        /// </summary>
        internal bool HasRequiredMembersError
        {
            get
            {
                EnsureRequiredMembersCalculated();
                Debug.Assert(!_lazyRequiredMembers.IsDefault);
                return _lazyRequiredMembers == RequiredMembersErrorSentinel;
            }
        }

        /// <summary>
        /// Returns true if there are any required members. Prefer calling this over checking <see cref="AllRequiredMembers"/> for empty, as
        /// this will avoid calculating base type requirements if not necessary.
        /// </summary>
        internal bool HasAnyRequiredMembers => HasDeclaredRequiredMembers || !AllRequiredMembers.IsEmpty;

        /// <summary>
        /// The full list of all required members for this type, including from base classes. If <see cref="HasRequiredMembersError"/> is true,
        /// this returns empty.
        /// </summary>
        /// <remarks>
        /// Do not call this API if all you need are the required members declared on this type. Use <see cref="GetMembers()"/> instead, filtering for
        /// required members, instead of calling this API. If you only need to determine whether this type or any base types have required members, call
        /// <see cref="HasAnyRequiredMembers"/>, which will avoid calling this API if not required.
        /// </remarks>
        internal ImmutableSegmentedDictionary<string, Symbol> AllRequiredMembers
        {
            get
            {
                EnsureRequiredMembersCalculated();
                Debug.Assert(!_lazyRequiredMembers.IsDefault);
                if (_lazyRequiredMembers == RequiredMembersErrorSentinel)
                {
                    return ImmutableSegmentedDictionary<string, Symbol>.Empty;
                }

                return _lazyRequiredMembers;
            }
        }

        private void EnsureRequiredMembersCalculated()
        {
            if (!_lazyRequiredMembers.IsDefault)
            {
                return;
            }

            bool success = tryCalculateRequiredMembers(out ImmutableSegmentedDictionary<string, Symbol>.Builder? builder);

            var requiredMembers = success
                ? builder?.ToImmutable() ?? BaseTypeNoUseSiteDiagnostics?.AllRequiredMembers ?? ImmutableSegmentedDictionary<string, Symbol>.Empty
                : RequiredMembersErrorSentinel;

            RoslynImmutableInterlocked.InterlockedInitialize(ref _lazyRequiredMembers, requiredMembers);

            bool tryCalculateRequiredMembers(out ImmutableSegmentedDictionary<string, Symbol>.Builder? requiredMembersBuilder)
            {
                requiredMembersBuilder = null;
                if (BaseTypeNoUseSiteDiagnostics?.HasRequiredMembersError == true)
                {
                    return false;
                }

                var baseAllRequiredMembers = BaseTypeNoUseSiteDiagnostics?.AllRequiredMembers ?? ImmutableSegmentedDictionary<string, Symbol>.Empty;
                var hasDeclaredRequiredMembers = HasDeclaredRequiredMembers;

                foreach (var member in GetMembersUnordered())
                {
                    if (member is PropertySymbol { ParameterCount: > 0 } prop)
                    {
                        if (prop.IsRequired)
                        {
                            // Bad metadata. Indexed properties cannot be required.
                            return false;
                        }
                        else
                        {
                            continue;
                        }
                    }

                    if (baseAllRequiredMembers.TryGetValue(member.Name, out var existingMember))
                    {
                        // This is only permitted if the member is an override of a required member from a base type, and is required itself.
                        if (!member.IsRequired()
                            || member.GetOverriddenMember() is not { } overriddenMember
                            || !overriddenMember.Equals(existingMember, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.AllNullableIgnoreOptions))
                        {
                            return false;
                        }
                    }

                    if (!member.IsRequired())
                    {
                        continue;
                    }

                    if (!hasDeclaredRequiredMembers)
                    {
                        // Bad metadata. Type claimed it didn't declare any required members, but we found one.
                        return false;
                    }

                    requiredMembersBuilder ??= baseAllRequiredMembers.ToBuilder();

                    requiredMembersBuilder[member.Name] = member;
                }

                return true;
            }
        }
#nullable disable

        /// <summary>
        /// Get all the members of this symbol.
        /// </summary>
        /// <returns>An ImmutableArray containing all the members of this symbol. If this symbol has no members,
        /// returns an empty ImmutableArray. Never returns null.</returns>
        public abstract override ImmutableArray<Symbol> GetMembers();

        /// <summary>
        /// Get all the members of this symbol that have a particular name.
        /// </summary>
        /// <returns>An ImmutableArray containing all the members of this symbol with the given name. If there are
        /// no members with this name, returns an empty ImmutableArray. Never returns null.</returns>
        public abstract override ImmutableArray<Symbol> GetMembers(string name);

        /// <summary>
        /// A lightweight check for whether this type has a possible clone method. This is less costly than GetMembers,
        /// particularly for PE symbols, and can be used as a cheap heuristic for whether to fully search through all
        /// members of this type for a valid clone method.
        /// </summary>
        internal abstract bool HasPossibleWellKnownCloneMethod();

        internal virtual ImmutableArray<Symbol> GetSimpleNonTypeMembers(string name)
        {
            return GetMembers(name);
        }

        /// <summary>
        /// Get all the members of this symbol that are types.
        /// </summary>
        /// <returns>An ImmutableArray containing all the types that are members of this symbol. If this symbol has no type members,
        /// returns an empty ImmutableArray. Never returns null.</returns>
        public abstract override ImmutableArray<NamedTypeSymbol> GetTypeMembers();

        /// <summary>
        /// Get all the members of this symbol that are types that have a particular name and arity
        /// </summary>
        /// <returns>An ImmutableArray containing all the types that are members of this symbol with the given name and arity.
        /// If this symbol has no type members with this name and arity,
        /// returns an empty ImmutableArray. Never returns null.</returns>
        public abstract override ImmutableArray<NamedTypeSymbol> GetTypeMembers(ReadOnlyMemory<char> name, int arity);

        /// <summary>
        /// Get all instance field and event members.
        /// </summary>
        /// <remarks>
        /// For source symbols may be called while calculating
        /// <see cref="NamespaceOrTypeSymbol.GetMembersUnordered"/>.
        /// </remarks>
        internal virtual IEnumerable<Symbol> GetInstanceFieldsAndEvents()
        {
            return GetMembersUnordered().Where(IsInstanceFieldOrEvent);
        }

        protected static Func<Symbol, bool> IsInstanceFieldOrEvent = symbol =>
        {
            if (!symbol.IsStatic)
            {
                switch (symbol.Kind)
                {
                    case SymbolKind.Field:
                    case SymbolKind.Event:
                        return true;
                }
            }
            return false;
        };

        /// <summary>
        /// Get this accessibility that was declared on this symbol. For symbols that do not have
        /// accessibility declared on them, returns NotApplicable.
        /// </summary>
        public abstract override Accessibility DeclaredAccessibility { get; }

        /// <summary>
        /// Used to implement visitor pattern.
        /// </summary>
        internal override TResult Accept<TArgument, TResult>(CSharpSymbolVisitor<TArgument, TResult> visitor, TArgument argument)
        {
            return visitor.VisitNamedType(this, argument);
        }

        public override void Accept(CSharpSymbolVisitor visitor)
        {
            visitor.VisitNamedType(this);
        }

        public override TResult Accept<TResult>(CSharpSymbolVisitor<TResult> visitor)
        {
            return visitor.VisitNamedType(this);
        }

        /// <summary>
        /// During early attribute decoding, we consider a safe subset of all members that will not
        /// cause cyclic dependencies.  Get all such members for this symbol.
        /// </summary>
        /// <remarks>
        /// Never returns null (empty instead).
        /// Expected implementations: for source, return type and field members; for metadata, return all members.
        /// </remarks>
        internal abstract ImmutableArray<Symbol> GetEarlyAttributeDecodingMembers();

        /// <summary>
        /// During early attribute decoding, we consider a safe subset of all members that will not
        /// cause cyclic dependencies.  Get all such members for this symbol that have a particular name.
        /// </summary>
        /// <remarks>
        /// Never returns null (empty instead).
        /// Expected implementations: for source, return type and field members; for metadata, return all members.
        /// </remarks>
        internal abstract ImmutableArray<Symbol> GetEarlyAttributeDecodingMembers(string name);

        /// <summary>
        /// Gets the kind of this symbol.
        /// </summary>
        public override SymbolKind Kind // Cannot seal this method because of the ErrorSymbol.
        {
            get
            {
                return SymbolKind.NamedType;
            }
        }

        internal abstract NamedTypeSymbol GetDeclaredBaseType(ConsList<TypeSymbol> basesBeingResolved);

        internal abstract ImmutableArray<NamedTypeSymbol> GetDeclaredInterfaces(ConsList<TypeSymbol> basesBeingResolved);

        public override int GetHashCode()
        {
            // return a distinguished value for 'object' so we can return the same value for 'dynamic'.
            // That's because the hash code ignores the distinction between dynamic and object.  It also
            // ignores custom modifiers.
            if (this.SpecialType == SpecialType.System_Object)
            {
                return (int)SpecialType.System_Object;
            }

            // OriginalDefinition must be object-equivalent.
            return RuntimeHelpers.GetHashCode(OriginalDefinition);
        }

        /// <summary>
        /// Compares this type to another type.
        /// </summary>
        internal override bool Equals(TypeSymbol t2, TypeCompareKind comparison)
        {
            if ((object)t2 == this) return true;
            if ((object)t2 == null) return false;

            if ((comparison & TypeCompareKind.IgnoreDynamic) != 0)
            {
                if (t2.TypeKind == TypeKind.Dynamic)
                {
                    // if ignoring dynamic, then treat dynamic the same as the type 'object'
                    if (this.SpecialType == SpecialType.System_Object)
                    {
                        return true;
                    }
                }
            }

            NamedTypeSymbol other = t2 as NamedTypeSymbol;
            if ((object)other == null) return false;

            // Compare OriginalDefinitions.
            var thisOriginalDefinition = this.OriginalDefinition;
            var otherOriginalDefinition = other.OriginalDefinition;

            bool thisIsOriginalDefinition = ((object)this == (object)thisOriginalDefinition);
            bool otherIsOriginalDefinition = ((object)other == (object)otherOriginalDefinition);

            if (thisIsOriginalDefinition && otherIsOriginalDefinition)
            {
                // If we continue, we either return false, or get into a cycle.
                return false;
            }

            if ((thisIsOriginalDefinition || otherIsOriginalDefinition) &&
                (comparison & (TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds | TypeCompareKind.AllNullableIgnoreOptions | TypeCompareKind.IgnoreTupleNames)) == 0)
            {
                return false;
            }

            // CONSIDER: original definitions are not unique for missing metadata type
            // symbols.  Therefore this code may not behave correctly if 'this' is List<int>
            // where List`1 is a missing metadata type symbol, and other is similarly List<int>
            // but for a reference-distinct List`1.
            if (!Equals(thisOriginalDefinition, otherOriginalDefinition, comparison))
            {
                return false;
            }

            // The checks above are supposed to handle the vast majority of cases.
            // More complicated cases are handled in a special helper to make the common case scenario simple/fast (fewer locals and smaller stack frame)
            return EqualsComplicatedCases(other, comparison);
        }

        /// <summary>
        /// Helper for more complicated cases of Equals like when we have generic instantiations or types nested within them.
        /// </summary>
        private bool EqualsComplicatedCases(NamedTypeSymbol other, TypeCompareKind comparison)
        {
            if ((object)this.ContainingType != null &&
                !this.ContainingType.Equals(other.ContainingType, comparison))
            {
                return false;
            }

            var thisIsNotConstructed = ReferenceEquals(ConstructedFrom, this);
            var otherIsNotConstructed = ReferenceEquals(other.ConstructedFrom, other);

            if (thisIsNotConstructed && otherIsNotConstructed)
            {
                // Note that the arguments might appear different here due to alpha-renaming.  For example, given
                // class A<T> { class B<U> {} }
                // The type A<int>.B<int> is "constructed from" A<int>.B<1>, which may be a distinct type object
                // with a different alpha-renaming of B's type parameter every time that type expression is bound,
                // but these should be considered the same type each time.
                return true;
            }

            if (this.IsUnboundGenericType != other.IsUnboundGenericType)
            {
                return false;
            }

            if ((thisIsNotConstructed || otherIsNotConstructed) &&
                 (comparison & (TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds | TypeCompareKind.AllNullableIgnoreOptions | TypeCompareKind.IgnoreTupleNames)) == 0)
            {
                return false;
            }

            var typeArguments = this.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics;
            var otherTypeArguments = other.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics;
            int count = typeArguments.Length;

            // since both are constructed from the same (original) type, they must have the same arity
            Debug.Assert(count == otherTypeArguments.Length);

            for (int i = 0; i < count; i++)
            {
                var typeArgument = typeArguments[i];
                var otherTypeArgument = otherTypeArguments[i];
                if (!typeArgument.Equals(otherTypeArgument, comparison))
                {
                    return false;
                }
            }

            if (this.IsTupleType && !tupleNamesEquals(other, comparison))
            {
                return false;
            }

            return true;

            bool tupleNamesEquals(NamedTypeSymbol other, TypeCompareKind comparison)
            {
                // Make sure field names are the same.
                if ((comparison & TypeCompareKind.IgnoreTupleNames) == 0)
                {
                    var elementNames = TupleElementNames;
                    var otherElementNames = other.TupleElementNames;
                    return elementNames.IsDefault ? otherElementNames.IsDefault : !otherElementNames.IsDefault && elementNames.SequenceEqual(otherElementNames);
                }

                return true;
            }
        }

        internal override void AddNullableTransforms(ArrayBuilder<byte> transforms)
        {
            ContainingType?.AddNullableTransforms(transforms);

            foreach (TypeWithAnnotations arg in this.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics)
            {
                arg.AddNullableTransforms(transforms);
            }
        }

        internal override bool ApplyNullableTransforms(byte defaultTransformFlag, ImmutableArray<byte> transforms, ref int position, out TypeSymbol result)
        {
            if (!IsGenericType)
            {
                result = this;
                return true;
            }

            var allTypeArguments = ArrayBuilder<TypeWithAnnotations>.GetInstance();
            GetAllTypeArgumentsNoUseSiteDiagnostics(allTypeArguments);

            bool haveChanges = false;
            for (int i = 0; i < allTypeArguments.Count; i++)
            {
                TypeWithAnnotations oldTypeArgument = allTypeArguments[i];
                TypeWithAnnotations newTypeArgument;
                if (!oldTypeArgument.ApplyNullableTransforms(defaultTransformFlag, transforms, ref position, out newTypeArgument))
                {
                    allTypeArguments.Free();
                    result = this;
                    return false;
                }
                else if (!oldTypeArgument.IsSameAs(newTypeArgument))
                {
                    allTypeArguments[i] = newTypeArgument;
                    haveChanges = true;
                }
            }

            result = haveChanges ? this.WithTypeArguments(allTypeArguments.ToImmutable()) : this;
            allTypeArguments.Free();
            return true;
        }

        internal override TypeSymbol SetNullabilityForReferenceTypes(Func<TypeWithAnnotations, TypeWithAnnotations> transform)
        {
            if (!IsGenericType)
            {
                return this;
            }

            var allTypeArguments = ArrayBuilder<TypeWithAnnotations>.GetInstance();
            GetAllTypeArgumentsNoUseSiteDiagnostics(allTypeArguments);

            bool haveChanges = false;
            for (int i = 0; i < allTypeArguments.Count; i++)
            {
                TypeWithAnnotations oldTypeArgument = allTypeArguments[i];
                TypeWithAnnotations newTypeArgument = transform(oldTypeArgument);
                if (!oldTypeArgument.IsSameAs(newTypeArgument))
                {
                    allTypeArguments[i] = newTypeArgument;
                    haveChanges = true;
                }
            }

            NamedTypeSymbol result = haveChanges ? this.WithTypeArguments(allTypeArguments.ToImmutable()) : this;
            allTypeArguments.Free();
            return result;
        }

        internal NamedTypeSymbol WithTypeArguments(ImmutableArray<TypeWithAnnotations> allTypeArguments)
        {
            var definition = this.OriginalDefinition;
            TypeMap substitution = new TypeMap(definition.GetAllTypeParameters(), allTypeArguments);
            return substitution.SubstituteNamedType(definition).WithTupleDataFrom(this);
        }

        internal override TypeSymbol MergeEquivalentTypes(TypeSymbol other, VarianceKind variance)
        {
            Debug.Assert(this.Equals(other, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes));

            if (!IsGenericType)
            {
                return other.IsDynamic() ? other : this;
            }

            var allTypeParameters = ArrayBuilder<TypeParameterSymbol>.GetInstance();
            var allTypeArguments = ArrayBuilder<TypeWithAnnotations>.GetInstance();
            bool haveChanges = MergeEquivalentTypeArguments(this, (NamedTypeSymbol)other, variance, allTypeParameters, allTypeArguments);

            NamedTypeSymbol result;
            if (haveChanges)
            {
                TypeMap substitution = new TypeMap(allTypeParameters.ToImmutable(), allTypeArguments.ToImmutable());
                result = substitution.SubstituteNamedType(this.OriginalDefinition);
            }
            else
            {
                result = this;
            }

            allTypeArguments.Free();
            allTypeParameters.Free();

            return IsTupleType ? MergeTupleNames((NamedTypeSymbol)other, result) : result;
        }

        /// <summary>
        /// Merges nullability of all type arguments from the `typeA` and `typeB`.
        /// The type parameters are added to `allTypeParameters`; the merged
        /// type arguments are added to `allTypeArguments`; and the method
        /// returns true if there were changes from the original `typeA`.
        /// </summary>
        private static bool MergeEquivalentTypeArguments(
            NamedTypeSymbol typeA,
            NamedTypeSymbol typeB,
            VarianceKind variance,
            ArrayBuilder<TypeParameterSymbol> allTypeParameters,
            ArrayBuilder<TypeWithAnnotations> allTypeArguments)
        {
            Debug.Assert(typeA.IsGenericType);
            Debug.Assert(typeA.Equals(typeB, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes));

            // Tuple types act as covariant when merging equivalent types.
            bool isTuple = typeA.IsTupleType;

            var definition = typeA.OriginalDefinition;
            bool haveChanges = false;

            while (true)
            {
                var typeParameters = definition.TypeParameters;
                if (typeParameters.Length > 0)
                {
                    var typeArgumentsA = typeA.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics;
                    var typeArgumentsB = typeB.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics;
                    allTypeParameters.AddRange(typeParameters);
                    for (int i = 0; i < typeArgumentsA.Length; i++)
                    {
                        TypeWithAnnotations typeArgumentA = typeArgumentsA[i];
                        TypeWithAnnotations typeArgumentB = typeArgumentsB[i];
                        VarianceKind typeArgumentVariance = GetTypeArgumentVariance(variance, isTuple ? VarianceKind.Out : typeParameters[i].Variance);
                        TypeWithAnnotations merged = typeArgumentA.MergeEquivalentTypes(typeArgumentB, typeArgumentVariance);
                        allTypeArguments.Add(merged);
                        if (!typeArgumentA.IsSameAs(merged))
                        {
                            haveChanges = true;
                        }
                    }
                }
                definition = definition.ContainingType;
                if (definition is null)
                {
                    break;
                }
                typeA = typeA.ContainingType;
                typeB = typeB.ContainingType;
                variance = VarianceKind.None;
            }

            return haveChanges;
        }

        private static VarianceKind GetTypeArgumentVariance(VarianceKind typeVariance, VarianceKind typeParameterVariance)
        {
            switch (typeVariance)
            {
                case VarianceKind.In:
                    switch (typeParameterVariance)
                    {
                        case VarianceKind.In:
                            return VarianceKind.Out;
                        case VarianceKind.Out:
                            return VarianceKind.In;
                        default:
                            return VarianceKind.None;
                    }
                case VarianceKind.Out:
                    return typeParameterVariance;
                default:
                    return VarianceKind.None;
            }
        }

        /// <summary>
        /// Returns a constructed type given its type arguments.
        /// </summary>
        /// <param name="typeArguments">The immediate type arguments to be replaced for type
        /// parameters in the type.</param>
        public NamedTypeSymbol Construct(params TypeSymbol[] typeArguments)
        {
            // https://github.com/dotnet/roslyn/issues/30064: We should fix the callers to pass TypeWithAnnotations[] instead of TypeSymbol[].
            return ConstructWithoutModifiers(typeArguments.AsImmutableOrNull(), false);
        }

        /// <summary>
        /// Returns a constructed type given its type arguments.
        /// </summary>
        /// <param name="typeArguments">The immediate type arguments to be replaced for type
        /// parameters in the type.</param>
        public NamedTypeSymbol Construct(ImmutableArray<TypeSymbol> typeArguments)
        {
            // https://github.com/dotnet/roslyn/issues/30064: We should fix the callers to pass ImmutableArray<TypeWithAnnotations> instead of ImmutableArray<TypeSymbol>.
            return ConstructWithoutModifiers(typeArguments, false);
        }

        /// <summary>
        /// Returns a constructed type given its type arguments.
        /// </summary>
        /// <param name="typeArguments"></param>
        public NamedTypeSymbol Construct(IEnumerable<TypeSymbol> typeArguments)
        {
            // https://github.com/dotnet/roslyn/issues/30064: We should fix the callers to pass IEnumerable<TypeWithAnnotations> instead of IEnumerable<TypeSymbol>.
            return ConstructWithoutModifiers(typeArguments.AsImmutableOrNull(), false);
        }

        /// <summary>
        /// Returns an unbound generic type of this named type.
        /// </summary>
        public NamedTypeSymbol ConstructUnboundGenericType()
        {
            return OriginalDefinition.AsUnboundGenericType();
        }

        internal NamedTypeSymbol GetUnboundGenericTypeOrSelf()
        {
            if (!this.IsGenericType)
            {
                return this;
            }

            return this.ConstructUnboundGenericType();
        }

        /// <summary>
        /// Gets a value indicating whether this type has an EmbeddedAttribute or not.
        /// </summary>
        internal abstract bool HasCodeAnalysisEmbeddedAttribute { get; }

        internal abstract bool HasAsyncMethodBuilderAttribute(out TypeSymbol builderArgument);

        internal abstract bool HasCompilerLoweringPreserveAttribute { get; }

        /// <summary>
        /// Gets a value indicating whether this type has System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute or not.
        /// </summary>
        internal abstract bool IsInterpolatedStringHandlerType { get; }

        internal static readonly Func<TypeWithAnnotations, bool> TypeWithAnnotationsIsNullFunction = type => !type.HasType;

        internal static readonly Func<TypeWithAnnotations, bool> TypeWithAnnotationsIsErrorType = type => type.HasType && type.Type.IsErrorType();

        private NamedTypeSymbol ConstructWithoutModifiers(ImmutableArray<TypeSymbol> typeArguments, bool unbound)
        {
            ImmutableArray<TypeWithAnnotations> modifiedArguments;

            if (typeArguments.IsDefault)
            {
                modifiedArguments = default(ImmutableArray<TypeWithAnnotations>);
            }
            else
            {
                modifiedArguments = typeArguments.SelectAsArray(t => TypeWithAnnotations.Create(t));
            }

            return Construct(modifiedArguments, unbound);
        }

        internal NamedTypeSymbol Construct(ImmutableArray<TypeWithAnnotations> typeArguments)
        {
            return Construct(typeArguments, unbound: false);
        }

        internal NamedTypeSymbol Construct(ImmutableArray<TypeWithAnnotations> typeArguments, bool unbound)
        {
            if (!ReferenceEquals(this, ConstructedFrom))
            {
                throw new InvalidOperationException(CSharpResources.CannotCreateConstructedFromConstructed);
            }

            if (this.Arity == 0)
            {
                throw new InvalidOperationException(CSharpResources.CannotCreateConstructedFromNongeneric);
            }

            if (typeArguments.IsDefault)
            {
                throw new ArgumentNullException(nameof(typeArguments));
            }

            if (typeArguments.Any(TypeWithAnnotationsIsNullFunction))
            {
                throw new ArgumentException(CSharpResources.TypeArgumentCannotBeNull, nameof(typeArguments));
            }

            if (typeArguments.Length != this.Arity)
            {
                throw new ArgumentException(CSharpResources.WrongNumberOfTypeArguments, nameof(typeArguments));
            }

            Debug.Assert(!unbound || typeArguments.All(TypeWithAnnotationsIsErrorType));

            if (ConstructedNamedTypeSymbol.TypeParametersMatchTypeArguments(this.TypeParameters, typeArguments))
            {
                return this;
            }

            return this.ConstructCore(typeArguments, unbound);
        }

        protected virtual NamedTypeSymbol ConstructCore(ImmutableArray<TypeWithAnnotations> typeArguments, bool unbound)
        {
            return new ConstructedNamedTypeSymbol(this, typeArguments, unbound);
        }

        /// <summary>
        /// True if this type or some containing type has type parameters.
        /// </summary>
        public bool IsGenericType
        {
            get
            {
                for (var current = this; !ReferenceEquals(current, null); current = current.ContainingType)
                {
                    if (current.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Length != 0)
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        /// <summary>
        /// True if this is a reference to an <em>unbound</em> generic type.  These occur only
        /// within a <c>typeof</c> expression.  A generic type is considered <em>unbound</em>
        /// if all of the type argument lists in its fully qualified name are empty.
        /// Note that the type arguments of an unbound generic type will be returned as error
        /// types because they do not really have type arguments.  An unbound generic type
        /// yields null for its BaseType and an empty result for its Interfaces.
        /// </summary>
        public virtual bool IsUnboundGenericType
        {
            get
            {
                return false;
            }
        }

        // Given C<int>.D<string, double>, yields { int, string, double }
        internal void GetAllTypeArguments(ref TemporaryArray<TypeSymbol> builder, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            var outer = ContainingType;
            if (!ReferenceEquals(outer, null))
            {
                outer.GetAllTypeArguments(ref builder, ref useSiteInfo);
            }

            foreach (var argument in TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo))
            {
                builder.Add(argument.Type);
            }
        }

        internal ImmutableArray<TypeWithAnnotations> GetAllTypeArguments(ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            ArrayBuilder<TypeWithAnnotations> builder = ArrayBuilder<TypeWithAnnotations>.GetInstance();
            GetAllTypeArguments(builder, ref useSiteInfo);
            return builder.ToImmutableAndFree();
        }

        internal void GetAllTypeArguments(ArrayBuilder<TypeWithAnnotations> builder, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            var outer = ContainingType;
            if (!ReferenceEquals(outer, null))
            {
                outer.GetAllTypeArguments(builder, ref useSiteInfo);
            }

            builder.AddRange(TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo));
        }

        internal void GetAllTypeArgumentsNoUseSiteDiagnostics(ArrayBuilder<TypeWithAnnotations> builder)
        {
            ContainingType?.GetAllTypeArgumentsNoUseSiteDiagnostics(builder);
            builder.AddRange(TypeArgumentsWithAnnotationsNoUseSiteDiagnostics);
        }

        internal int AllTypeArgumentCount()
        {
            int count = TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Length;

            var outer = ContainingType;
            if (!ReferenceEquals(outer, null))
            {
                count += outer.AllTypeArgumentCount();
            }

            return count;
        }

        internal ImmutableArray<TypeWithAnnotations> GetTypeParametersAsTypeArguments()
        {
            return TypeMap.TypeParametersAsTypeSymbolsWithAnnotations(this.TypeParameters);
        }

        /// <summary>
        /// The original definition of this symbol. If this symbol is constructed from another
        /// symbol by type substitution then OriginalDefinition gets the original symbol as it was defined in
        /// source or metadata.
        /// </summary>
        public new virtual NamedTypeSymbol OriginalDefinition
        {
            get
            {
                return this;
            }
        }

        protected sealed override TypeSymbol OriginalTypeSymbolDefinition
        {
            get
            {
                return this.OriginalDefinition;
            }
        }

        /// <summary>
        /// Returns the map from type parameters to type arguments.
        /// If this is not a generic type instantiation, returns null.
        /// The map targets the original definition of the type.
        /// </summary>
        internal virtual TypeMap TypeSubstitution
        {
            get { return null; }
        }

        internal virtual NamedTypeSymbol AsMember(NamedTypeSymbol newOwner)
        {
            Debug.Assert(this.IsDefinition);
            Debug.Assert(ReferenceEquals(newOwner.OriginalDefinition, this.ContainingSymbol.OriginalDefinition));
            return newOwner.IsDefinition ? this : new SubstitutedNestedTypeSymbol((SubstitutedNamedTypeSymbol)newOwner, this);
        }

        #region Use-Site Diagnostics

        internal override UseSiteInfo<AssemblySymbol> GetUseSiteInfo()
        {
            UseSiteInfo<AssemblySymbol> result = new UseSiteInfo<AssemblySymbol>(PrimaryDependency);

            if (this.IsDefinition)
            {
                return result;
            }

            // Check definition, type arguments 
            if (!DeriveUseSiteInfoFromType(ref result, this.OriginalDefinition))
            {
                DeriveUseSiteDiagnosticFromTypeArguments(ref result);
            }

            return result;
        }

        private bool DeriveUseSiteDiagnosticFromTypeArguments(ref UseSiteInfo<AssemblySymbol> result)
        {
            NamedTypeSymbol currentType = this;

            do
            {
                foreach (TypeWithAnnotations arg in currentType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics)
                {
                    if (DeriveUseSiteInfoFromType(ref result, arg, AllowedRequiredModifierType.None))
                    {
                        return true;
                    }
                }

                currentType = currentType.ContainingType;
            }
            while (currentType?.IsDefinition == false);

            return false;
        }

        internal DiagnosticInfo CalculateUseSiteDiagnostic()
        {
            DiagnosticInfo result = null;

            // Check base type.
            if (MergeUseSiteDiagnostics(ref result, DeriveUseSiteDiagnosticFromBase()))
            {
                return result;
            }

            // If we reach a type (Me) that is in an assembly with unified references, 
            // we check if that type definition depends on a type from a unified reference.
            if (this.ContainingModule.HasUnifiedReferences)
            {
                HashSet<TypeSymbol> unificationCheckedTypes = null;
                if (GetUnificationUseSiteDiagnosticRecursive(ref result, this, ref unificationCheckedTypes))
                {
                    return result;
                }
            }

            return result;
        }

        private DiagnosticInfo DeriveUseSiteDiagnosticFromBase()
        {
            NamedTypeSymbol @base = this.BaseTypeNoUseSiteDiagnostics;

            while ((object)@base != null)
            {
                if (@base.IsErrorType() && @base is NoPiaIllegalGenericInstantiationSymbol)
                {
                    return @base.GetUseSiteInfo().DiagnosticInfo;
                }

                @base = @base.BaseTypeNoUseSiteDiagnostics;
            }

            return null;
        }

        internal override bool GetUnificationUseSiteDiagnosticRecursive(ref DiagnosticInfo result, Symbol owner, ref HashSet<TypeSymbol> checkedTypes)
        {
            if (!this.MarkCheckedIfNecessary(ref checkedTypes))
            {
                return false;
            }

            Debug.Assert(owner.ContainingModule.HasUnifiedReferences);
            if (owner.ContainingModule.GetUnificationUseSiteDiagnostic(ref result, this))
            {
                return true;
            }

            // We recurse into base types, interfaces and type *parameters* to check for
            // problems with constraints. We recurse into type *arguments* in the overload
            // in ConstructedNamedTypeSymbol.
            //
            // When we are binding a name with a nested type, Goo.Bar, then we ask for
            // use-site errors to be reported on both Goo and Goo.Bar. Therefore we should
            // not recurse into the containing type here; doing so will result in errors
            // being reported twice if Goo is bad.

            var @base = this.BaseTypeNoUseSiteDiagnostics;
            if ((object)@base != null && @base.GetUnificationUseSiteDiagnosticRecursive(ref result, owner, ref checkedTypes))
            {
                return true;
            }

            return GetUnificationUseSiteDiagnosticRecursive(ref result, this.InterfacesNoUseSiteDiagnostics(), owner, ref checkedTypes) ||
                   GetUnificationUseSiteDiagnosticRecursive(ref result, this.TypeParameters, owner, ref checkedTypes);
        }

        #endregion

        /// <summary>
        /// True if the type itself is excluded from code coverage instrumentation.
        /// True for source types marked with <see cref="AttributeDescription.ExcludeFromCodeCoverageAttribute"/>.
        /// </summary>
        internal virtual bool IsDirectlyExcludedFromCodeCoverage { get => false; }

        /// <summary>
        /// True if this symbol has a special name (metadata flag SpecialName is set).
        /// </summary>
        internal abstract bool HasSpecialName { get; }

        /// <summary>
        /// Returns a flag indicating whether this symbol is ComImport.
        /// </summary>
        /// <remarks>
        /// A type can me marked as a ComImport type in source by applying the <see cref="System.Runtime.InteropServices.ComImportAttribute"/>
        /// </remarks>
        internal abstract bool IsComImport { get; }

        /// <summary>
        /// True if the type is a Windows runtime type.
        /// </summary>
        /// <remarks>
        /// A type can me marked as a Windows runtime type in source by applying the WindowsRuntimeImportAttribute.
        /// WindowsRuntimeImportAttribute is a pseudo custom attribute defined as an internal class in System.Runtime.InteropServices.WindowsRuntime namespace.
        /// This is needed to mark Windows runtime types which are redefined in mscorlib.dll and System.Runtime.WindowsRuntime.dll.
        /// These two assemblies are special as they implement the CLR's support for WinRT.
        /// </remarks>
        internal abstract bool IsWindowsRuntimeImport { get; }

        /// <summary>
        /// True if the type should have its WinRT interfaces projected onto .NET types and
        /// have missing .NET interface members added to the type.
        /// </summary>
        internal abstract bool ShouldAddWinRTMembers { get; }

        /// <summary>
        /// Returns a flag indicating whether this symbol has at least one applied/inherited conditional attribute.
        /// </summary>
        /// <remarks>
        /// Forces binding and decoding of attributes.
        /// </remarks>
        internal bool IsConditional
        {
            get
            {
                if (this.GetAppliedConditionalSymbols().Any())
                {
                    return true;
                }

                // Conditional attributes are inherited by derived types.
                var baseType = this.BaseTypeNoUseSiteDiagnostics;
                return (object)baseType != null ? baseType.IsConditional : false;
            }
        }

        /// <summary>
        /// True if the type is serializable (has Serializable metadata flag).
        /// </summary>
        public abstract bool IsSerializable { get; }

        /// <summary>
        /// Returns true if locals are to be initialized
        /// </summary>
        public abstract bool AreLocalsZeroed { get; }

        /// <summary>
        /// Type layout information (ClassLayout metadata and layout kind flags).
        /// </summary>
        internal abstract TypeLayout Layout { get; }

        /// <summary>
        /// The default charset used for type marshalling. 
        /// Can be changed via <see cref="DefaultCharSetAttribute"/> applied on the containing module.
        /// </summary>
        protected CharSet DefaultMarshallingCharSet
        {
            get
            {
                return this.GetEffectiveDefaultMarshallingCharSet() ?? CharSet.Ansi;
            }
        }

        /// <summary>
        /// Marshalling charset of string data fields within the type (string formatting flags in metadata).
        /// </summary>
        internal abstract CharSet MarshallingCharSet { get; }

        /// <summary>
        /// True if the type has declarative security information (HasSecurity flags).
        /// </summary>
        internal abstract bool HasDeclarativeSecurity { get; }

        /// <summary>
        /// Declaration security information associated with this type, or null if there is none.
        /// </summary>
        internal abstract IEnumerable<Cci.SecurityAttribute> GetSecurityInformation();

        /// <summary>
        /// Returns a sequence of preprocessor symbols specified in <see cref="ConditionalAttribute"/> applied on this symbol, or null if there are none.
        /// </summary>
        internal abstract ImmutableArray<string> GetAppliedConditionalSymbols();

        /// <summary>
        /// If <see cref="CoClassAttribute"/> was applied to the type and the attribute argument is a valid named type argument, i.e. accessible class type, then it returns the type symbol for the argument.
        /// Otherwise, returns null.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This property invokes force completion of attributes. If you are accessing this property
        /// from the binder, make sure that we are not binding within an Attribute context.
        /// This could lead to a possible cycle in attribute binding.
        /// We can avoid this cycle by first checking if we are within the context of an Attribute argument,
        /// i.e. if(!binder.InAttributeArgument) { ...  namedType.ComImportCoClass ... }
        /// </para>
        /// <para>
        /// CONSIDER: We can remove the above restriction and possibility of cycle if we do an
        /// early binding of some well known attributes.
        /// </para>
        /// </remarks>
        internal virtual NamedTypeSymbol ComImportCoClass
        {
            get
            {
                return null;
            }
        }

        /// <summary>
        /// If class represents fixed buffer, this property returns the FixedElementField
        /// </summary>
        internal virtual FieldSymbol FixedElementField
        {
            get
            {
                return null;
            }
        }

#nullable enable
        internal abstract bool HasCollectionBuilderAttribute(out TypeSymbol? builderType, out string? methodName);
#nullable disable

        /// <summary>
        /// Requires less computation than <see cref="TypeSymbol.TypeKind"/> == <see cref="TypeKind.Interface"/>.
        /// </summary>
        /// <remarks>
        /// Metadata types need to compute their base types in order to know their TypeKinds, and that can lead
        /// to cycles if base types are already being computed.
        /// </remarks>
        /// <returns>True if this is an interface type.</returns>
        internal abstract bool IsInterface { get; }

        internal bool IsUnionType
        {
            get
            {
                return TypeKind is TypeKind.Class or TypeKind.Struct &&
                       IsUnionTypeCore;
            }
        }

        internal abstract bool IsUnionTypeCore { get; }

#nullable enable

        internal ImmutableArray<TypeSymbol> UnionCaseTypesNoUseSiteDiagnostics
        {
            get
            {
                var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
                return UnionCaseTypes(ref discardedUseSiteInfo);
            }
        }

        internal ImmutableArray<TypeSymbol> UnionCaseTypes(ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            if (!IsUnionType)
            {
                return [];
            }

            UnionData lazyUnionData = GetUnionData();
            ImmutableArray<TypeSymbol> lazyCaseTypes = lazyUnionData._lazyCaseTypes;

            if (!lazyCaseTypes.IsDefault)
            {
                addUseSiteInfoForCachedResult(ref useSiteInfo);
                return lazyCaseTypes;
            }

            // Any change in collection of 'useSiteInfo' below this point
            // should be reflected in implementation of 'addUseSiteInfoForCachedResult'.

            var builder = ArrayBuilder<TypeSymbol>.GetInstance();
            var set = TypeSymbol.AllIgnoreOptionsSetPool.Allocate();

            if (!this.IsDefinition)
            {
                ImmutableArray<TypeSymbol> definitionCaseTypes = this.OriginalDefinition.UnionCaseTypes(ref useSiteInfo);
                TypeMap typeSubstitution = this.TypeSubstitution;

                foreach (var definitionCaseType in definitionCaseTypes)
                {
                    TypeSymbol substitutedCaseType = typeSubstitution.SubstituteType(definitionCaseType).Type;
                    if (set.Add(substitutedCaseType))
                    {
                        builder.Add(substitutedCaseType);
                    }
                }
            }
            else
            {
                foreach (var factory in this.UnionFactoryMethods(ref useSiteInfo))
                {
                    var candidate = factory.Parameters[0].Type;
                    if (set.Add(candidate))
                    {
                        builder.Add(candidate);
                    }
                }
            }

            set.Free();

            ImmutableInterlocked.InterlockedInitialize(ref lazyUnionData._lazyCaseTypes, builder.ToImmutableAndFree());
            return lazyUnionData._lazyCaseTypes;

            void addUseSiteInfoForCachedResult(ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
            {
                AddUseSiteInfoForCachedUnionFactoryMethodsResult(GetMemberProviderInterfaceAllInterfacesForDefinition(), ref useSiteInfo);
            }
        }

        internal ImmutableArray<TypeUnionValueSet.CaseInfo> UnionTypeUnionValueSetCases()
        {
            UnionData lazyUnionData = GetUnionData();
            ImmutableArray<TypeUnionValueSet.CaseInfo> lazyTypeUnionValueSetCases = lazyUnionData._lazyTypeUnionValueSetCases;
            if (!lazyTypeUnionValueSetCases.IsDefault)
            {
                return lazyTypeUnionValueSetCases;
            }

            var builder = ArrayBuilder<TypeUnionValueSet.CaseInfo>.GetInstance();
            var setBuilder = AllIgnoreOptionsSetPool.Allocate();
            foreach (var caseType in UnionCaseTypesNoUseSiteDiagnostics)
            {
                ValueSetFactory.ClosedClassTypeUnionValueSetFactory.ExpandClosedSubtypes(caseType.StrippedType(), builder, setBuilder);
            }

            setBuilder.Free();

            ImmutableInterlocked.InterlockedInitialize(ref lazyUnionData._lazyTypeUnionValueSetCases, builder.ToImmutableAndFree());
            return lazyUnionData._lazyTypeUnionValueSetCases;
        }

        internal ImmutableArray<MethodSymbol> UnionFactoryMethods(ref CompoundUseSiteInfo<AssemblySymbol> membersInterfaceForDefinitionInterfacesUseSiteInfo)
        {
            Debug.Assert(IsUnionType);

            UnionData lazyUnionData = GetUnionData();
            ImmutableArray<MethodSymbol> lazyFactoryMethods = lazyUnionData._lazyFactoryMethods;

            if (!lazyFactoryMethods.IsDefault)
            {
                AddUseSiteInfoForCachedUnionFactoryMethodsResult(GetMemberProviderInterfaceAllInterfacesForDefinition(), ref membersInterfaceForDefinitionInterfacesUseSiteInfo);
                return lazyFactoryMethods;
            }

            // Any change in collection of 'membersInterfaceForDefinitionInterfacesUseSiteInfo' below this point
            // should be reflected in implementation of 'AddUseSiteInfoForCachedUnionFactoryMethodsResult'.
            var result = ArrayBuilder<MethodSymbol>.GetInstance();

            if (!this.IsDefinition)
            {
                ImmutableArray<MethodSymbol> definitionFactoryMethods = this.OriginalDefinition.UnionFactoryMethods(ref membersInterfaceForDefinitionInterfacesUseSiteInfo);

                if (!definitionFactoryMethods.IsEmpty)
                {
                    if (definitionFactoryMethods[0].MethodKind != MethodKind.Constructor)
                    {
                        TypeMap typeSubstitution = this.TypeSubstitution;
                        ArrayBuilder<MethodSymbol>? shadowingMethods = null;

                        for (int i = 0; i < definitionFactoryMethods.Length; i++)
                        {
                            NamedTypeSymbol interfaceForDefinition = definitionFactoryMethods[i].ContainingType;
                            Debug.Assert(interfaceForDefinition.IsInterface);
                            NamedTypeSymbol constructedOrSubstitutedInterface = typeSubstitution.SubstituteNamedType(interfaceForDefinition);
                            bool canShadow = !interfaceForDefinition.OriginalDefinition.InterfacesNoUseSiteDiagnostics().IsEmpty;

                            // Process methods in groups of methods with the same containing type, so that we can deal with shadowing.
                            for (int j = i; j < definitionFactoryMethods.Length; j++)
                            {
                                if (definitionFactoryMethods[j].ContainingType != (object)interfaceForDefinition)
                                {
                                    Debug.Assert(!interfaceForDefinition.Equals(definitionFactoryMethods[j].ContainingType, TypeCompareKind.ConsiderEverything));
                                    break;
                                }

                                i = j;
                                MethodSymbol factoryMethod = definitionFactoryMethods[i].OriginalDefinition.AsMember(constructedOrSubstitutedInterface);

                                if (shadowingMethods is not null &&
                                    isShadowed(factoryMethod, shadowingMethods, constructedOrSubstitutedInterface))
                                {
                                    // Shadowed
                                    continue;
                                }

                                result.Add(factoryMethod);

                                if (canShadow)
                                {
                                    shadowingMethods ??= ArrayBuilder<MethodSymbol>.GetInstance();
                                    shadowingMethods.Add(factoryMethod);
                                }
                            }
                        }

                        shadowingMethods?.Free();
                    }
                    else
                    {
                        foreach (var definitionFactoryMethod in definitionFactoryMethods)
                        {
                            Debug.Assert(definitionFactoryMethod.IsDefinition);
                            Debug.Assert(definitionFactoryMethod.MethodKind == MethodKind.Constructor);
                            result.Add(definitionFactoryMethod.AsMember(this));
                        }
                    }
                }
            }
            else
            {
                NamedTypeSymbol? membersInterfaceForDefinition = GetMemberProviderInterfaceForDefinition();

                if (membersInterfaceForDefinition is not null)
                {
                    ArrayBuilder<MethodSymbol>? shadowingMethods = membersInterfaceForDefinition.InterfacesNoUseSiteDiagnostics().IsEmpty ? null : ArrayBuilder<MethodSymbol>.GetInstance();

                    foreach (var member in membersInterfaceForDefinition.GetMembers(WellKnownMemberNames.UnionFactoryMethodName))
                    {
                        if (member is MethodSymbol method && isSuitableUnionFactory(this, method))
                        {
                            result.Add(method);
                            shadowingMethods?.Add(method);
                        }
                    }

                    ImmutableArray<NamedTypeSymbol> memberProviderInterfaceAllInterfaces = GetMemberProviderInterfaceAllInterfacesForDefinition();
                    AddUseSiteInfoForCachedUnionFactoryMethodsResult(memberProviderInterfaceAllInterfaces, ref membersInterfaceForDefinitionInterfacesUseSiteInfo);

                    foreach (var baseInterfaceForDefinition in memberProviderInterfaceAllInterfaces)
                    {
                        Debug.Assert(shadowingMethods is not null);
                        bool canShadow = !baseInterfaceForDefinition.OriginalDefinition.InterfacesNoUseSiteDiagnostics().IsEmpty;

                        foreach (var member in baseInterfaceForDefinition.GetMembers(WellKnownMemberNames.UnionFactoryMethodName))
                        {
                            if (member is MethodSymbol method && isSuitableUnionFactory(this, method) &&
                                !isShadowed(method, shadowingMethods, baseInterfaceForDefinition))
                            {
                                Debug.Assert(result.Count == 0 ||
                                             !method.ContainingType.Equals(result[result.Count - 1].ContainingType, TypeCompareKind.ConsiderEverything) ||
                                             method.ContainingType == (object)result[result.Count - 1].ContainingType);
                                result.Add(method);

                                if (canShadow)
                                {
                                    shadowingMethods.Add(method);
                                }
                            }
                        }
                    }

                    shadowingMethods?.Free();
                }
                else
                {
                    foreach (var ctor in this.InstanceConstructors)
                    {
                        if (IsSuitableUnionConstructor(ctor))
                        {
                            result.Add(ctor);
                        }
                    }
                }
            }

            ImmutableInterlocked.InterlockedInitialize(ref lazyUnionData._lazyFactoryMethods, result.ToImmutableAndFree());
            return lazyUnionData._lazyFactoryMethods;

            static bool isSuitableUnionFactory(NamedTypeSymbol unionType, MethodSymbol factory)
            {
                Debug.Assert(unionType.IsDefinition);
                Debug.Assert(unionType.IsUnionType);
                Debug.Assert(factory.IsDefinition || factory.ContainingType.IsInterface);

                return factory is
                {
                    IsStatic: true,
                    Arity: 0,
                    MethodKind: MethodKind.Ordinary,
                    DeclaredAccessibility: Accessibility.Public,
                    ReturnsVoid: false,
                    RefKind: RefKind.None,
                    ParameterCount: 1,
                    Parameters: [{ RefKind: RefKind.In or RefKind.None }]
                } &&
                unionType.Equals(factory.ReturnType, TypeCompareKind.AllIgnoreOptions);
            }

            static bool isShadowed(MethodSymbol method, ArrayBuilder<MethodSymbol> shadowingMethods, NamedTypeSymbol methodContainingType)
            {
                for (int i = 0; i < shadowingMethods.Count; i++)
                {
                    MethodSymbol shadowingMethod = shadowingMethods[i];
                    if (shadowingMethod.ContainingType == (object)methodContainingType)
                    {
                        continue;
                    }

                    if (MemberSignatureComparer.CSharpOverrideComparer.Equals(shadowingMethod, method) &&
                        shadowingMethod.ContainingType.AllInterfacesNoUseSiteDiagnostics.Contains(methodContainingType, Symbols.SymbolEqualityComparer.AllIgnoreOptions))
                    {
                        // Shadowed
                        return true;
                    }
                }

                return false;
            }
        }

        /// <summary>
        /// Add use site info that would be added during calculation of non-cached result of
        /// <see cref="UnionFactoryMethods"/>.
        /// </summary>
        private void AddUseSiteInfoForCachedUnionFactoryMethodsResult(ImmutableArray<NamedTypeSymbol> memberProviderInterfaceAllInterfaces, ref CompoundUseSiteInfo<AssemblySymbol> membersInterfaceForDefinitionInterfacesUseSiteInfo)
        {
            if (!memberProviderInterfaceAllInterfaces.IsDefault &&
                (membersInterfaceForDefinitionInterfacesUseSiteInfo.AccumulatesDependencies || membersInterfaceForDefinitionInterfacesUseSiteInfo.AccumulatesDiagnostics))
            {
                foreach (var iface in memberProviderInterfaceAllInterfaces)
                {
                    iface.OriginalDefinition.AddUseSiteInfo(ref membersInterfaceForDefinitionInterfacesUseSiteInfo);
                }
            }
        }

        internal NamedTypeSymbol? GetMemberProviderInterfaceForDefinition()
        {
            Debug.Assert(IsUnionType);

            if (!this.IsDefinition)
            {
                return this.OriginalDefinition.GetMemberProviderInterfaceForDefinition();
            }

            Debug.Assert(this.IsDefinition);

            var lazyUnionData = (UnionDataForDefinition)GetUnionData();
            NamedTypeSymbol? lazyMemberProviderInterface = lazyUnionData._lazyMemberProviderInterface;

            if (lazyMemberProviderInterface != (object)ErrorTypeSymbol.UnknownResultType)
            {
                return lazyMemberProviderInterface;
            }

            Interlocked.CompareExchange(ref lazyUnionData._lazyMemberProviderInterface, calculateMemberProviderInterface(), ErrorTypeSymbol.UnknownResultType);
            return lazyUnionData._lazyMemberProviderInterface;

            NamedTypeSymbol? calculateMemberProviderInterface()
            {
                foreach (var type in this.GetTypeMembers(WellKnownMemberNames.UnionMembersInterfaceName))
                {
                    if (type.Arity != 0)
                    {
                        continue;
                    }

                    if (type.DeclaredAccessibility != Accessibility.Public ||
                        !type.IsInterface ||
                        !this.OriginalDefinition.AllInterfacesNoUseSiteDiagnostics.Contains(type, Symbols.SymbolEqualityComparer.AllIgnoreOptions))
                    {
                        return null;
                    }

                    Debug.Assert(type.IsDefinition);
                    return type;
                }

                return null;
            }
        }

        internal ImmutableArray<NamedTypeSymbol> GetMemberProviderInterfaceAllInterfacesForDefinition()
        {
            if (!this.IsDefinition)
            {
                return this.OriginalDefinition.GetMemberProviderInterfaceAllInterfacesForDefinition();
            }

            var lazyUnionData = (UnionDataForDefinition)GetUnionData();
            ImmutableArray<NamedTypeSymbol> lazyAllInterfaces = lazyUnionData._lazyMemberProviderInterfaceAllInterfaces;

            if (!lazyAllInterfaces.IsDefault)
            {
                return lazyAllInterfaces;
            }

            NamedTypeSymbol? memberProviderInterface = GetMemberProviderInterfaceForDefinition();
            if (memberProviderInterface is null)
            {
                return default;
            }

            ImmutableInterlocked.InterlockedInitialize(ref lazyUnionData._lazyMemberProviderInterfaceAllInterfaces, makeAllInterfaces(memberProviderInterface));
            return lazyUnionData._lazyMemberProviderInterfaceAllInterfaces;

            // Produce all implemented interfaces in topologically sorted order.
            static ImmutableArray<NamedTypeSymbol> makeAllInterfaces(NamedTypeSymbol memberProviderInterface)
            {
                var result = ArrayBuilder<NamedTypeSymbol>.GetInstance();
                var visited = TypeSymbol.AllIgnoreOptionsSetPool.Allocate();

                var interfaces = memberProviderInterface.GetInterfacesToEmit();
                for (int i = interfaces.Length - 1; i >= 0; i--)
                {
                    addAllInterfaces(interfaces[i], visited, result);
                }

                visited.Free();
                result.ReverseContents();
                return result.ToImmutableAndFree();

                static void addAllInterfaces(NamedTypeSymbol @interface, HashSet<TypeSymbol> visited, ArrayBuilder<NamedTypeSymbol> result)
                {
                    if (visited.Add(@interface))
                    {
                        ImmutableArray<NamedTypeSymbol> baseInterfaces = @interface.OriginalDefinition.GetInterfacesToEmit();
                        for (int i = baseInterfaces.Length - 1; i >= 0; i--)
                        {
                            var baseInterface = baseInterfaces[i];

                            if (!@interface.IsDefinition)
                            {
                                baseInterface = @interface.TypeSubstitution.SubstituteNamedType(baseInterface);
                            }

                            addAllInterfaces(baseInterface, visited, result);
                        }

                        result.Add(@interface);
                    }
                }
            }
        }

        private UncommonProperties GetUncommonProperties()
        {
            UncommonProperties? lazyUncommonProperties = _lazyUncommonProperties;
            if (lazyUncommonProperties is null)
            {
                Interlocked.CompareExchange(ref _lazyUncommonProperties, new UncommonProperties(), null);
                return _lazyUncommonProperties;
            }

            return lazyUncommonProperties;
        }

        private UnionData GetUnionData()
        {
            UncommonProperties lazyUncommonProperties = GetUncommonProperties();

            UnionData? lazyUnionData = lazyUncommonProperties._lazyUnionData;
            if (lazyUnionData is null)
            {
                Debug.Assert(IsUnionType);
                Interlocked.CompareExchange(ref lazyUncommonProperties._lazyUnionData, IsDefinition ? new UnionDataForDefinition() : new UnionData(), null);
                Debug.Assert(lazyUncommonProperties._lazyUnionData is not null);
                return lazyUncommonProperties._lazyUnionData;
            }

            return lazyUnionData;
        }

        private ClosedClassData GetClosedClassData()
        {
            UncommonProperties lazyUncommonProperties = GetUncommonProperties();

            ClosedClassData? lazyClosedClassData = lazyUncommonProperties._lazyClosedClassData;
            if (lazyClosedClassData is null)
            {
                Debug.Assert(IsClosed);
                Interlocked.CompareExchange(ref lazyUncommonProperties._lazyClosedClassData, new ClosedClassData(), null);
                Debug.Assert(lazyUncommonProperties._lazyClosedClassData is not null);
                return lazyUncommonProperties._lazyClosedClassData;
            }

            return lazyClosedClassData;
        }

        internal static bool IsSuitableUnionConstructor(MethodSymbol ctor)
        {
            Debug.Assert(ctor.MethodKind is MethodKind.Constructor);
            return ctor is { DeclaredAccessibility: Accessibility.Public, ParameterCount: 1, Parameters: [{ RefKind: RefKind.In or RefKind.None }] };
        }

        internal PropertySymbol? UnionValueProperty(ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            UnionData lazyUnionData = GetUnionData();
            Symbol? lazyValueProperty = lazyUnionData._lazyValueProperty;

            if (lazyValueProperty != (object)ErrorTypeSymbol.UnknownResultType)
            {
                var valueProperty = (PropertySymbol?)lazyValueProperty;
                addUseSiteInfoForCachedResult(valueProperty, ref useSiteInfo);
                return reportDiagnosticAndReturnProperty(
                    this.OriginalDefinition,
                    valueProperty,
                    ref useSiteInfo);
            }

            PropertySymbol? result;

            if (!this.IsDefinition)
            {
                result = this.OriginalDefinition.UnionValueProperty(ref useSiteInfo);
                if (result is not null)
                {
                    NamedTypeSymbol containingType = result.ContainingType;
                    result = result.OriginalDefinition.AsMember(
                        containingType == (object)this.OriginalDefinition ? this : this.TypeSubstitution.SubstituteNamedType(containingType));
                }
            }
            else
            {
                PropertySymbol? valueProperty = TryGetOwnOrInheritedUnionProperty(WellKnownMemberNames.ValuePropertyName, isSuitableProperty, ref useSiteInfo);
                result = reportDiagnosticAndReturnProperty(
                    this,
                    valueProperty,
                    ref useSiteInfo);
            }

            Interlocked.CompareExchange(ref lazyUnionData._lazyValueProperty, result, ErrorTypeSymbol.UnknownResultType);

            return (PropertySymbol?)lazyUnionData._lazyValueProperty;

            static PropertySymbol? reportDiagnosticAndReturnProperty(NamedTypeSymbol unionTypeDefinition, PropertySymbol? valueProperty, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
            {
                Debug.Assert(unionTypeDefinition.IsDefinition);

                if (useSiteInfo.AccumulatesDependencies || useSiteInfo.AccumulatesDiagnostics)
                {
                    if (valueProperty is not null)
                    {
                        Debug.Assert(valueProperty.Type.IsObjectType());
                        useSiteInfo.Add(valueProperty.OriginalDefinition.GetUseSiteInfo());
                    }
                    else
                    {
                        useSiteInfo.AddDiagnosticInfo(
                            new CSDiagnosticInfo(ErrorCode.ERR_MissingPredefinedMember,
                            unionTypeDefinition.GetMemberProviderInterfaceForDefinition() ?? unionTypeDefinition,
                            WellKnownMemberNames.ValuePropertyName));
                    }
                }

                return valueProperty;
            }

            static bool isSuitableProperty(Symbol m, [NotNullWhen(true)] out PropertySymbol? valueProperty)
            {
                if (m is PropertySymbol prop && hasUnionValueSignature(prop))
                {
                    valueProperty = prop;
                    return true;
                }

                valueProperty = null;
                return false;
            }

            static bool hasUnionValueSignature(PropertySymbol property)
            {
                return property is
                {
                    IsStatic: false,
                    GetMethod: { DeclaredAccessibility: Accessibility.Public },
                    RefKind: RefKind.None,
                    ParameterCount: 0,
                    Type.SpecialType: SpecialType.System_Object
                };
            }

            void addUseSiteInfoForCachedResult(PropertySymbol? valueProperty, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
            {
                if (useSiteInfo.AccumulatesDependencies || useSiteInfo.AccumulatesDiagnostics)
                {
                    if (GetMemberProviderInterfaceForDefinition() is { } memberProviderInterface)
                    {
                        if (valueProperty?.ContainingType.OriginalDefinition != (object)memberProviderInterface)
                        {
                            foreach (var iface in GetMemberProviderInterfaceAllInterfacesForDefinition())
                            {
                                iface.OriginalDefinition.AddUseSiteInfo(ref useSiteInfo);
                            }
                        }
                    }
                    else
                    {
                        NamedTypeSymbol? containingTypeOriginalDefinition = valueProperty?.ContainingType.OriginalDefinition;

                        for (NamedTypeSymbol declaringType = this.OriginalDefinition;
                             declaringType is not null;
                             declaringType = declaringType.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo))
                        {
                            if (containingTypeOriginalDefinition is not null &&
                                containingTypeOriginalDefinition == (object)declaringType.OriginalDefinition)
                            {
                                break;
                            }
                        }
                    }
                }
            }
        }

        internal PropertySymbol? UnionValuePropertyNoUseSiteDiagnostics()
        {
            var useSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
            return this.UnionValueProperty(ref useSiteInfo);
        }

        private delegate bool IsSuitableUnionProperty(Symbol m, [NotNullWhen(true)] out PropertySymbol? member);

        private PropertySymbol? TryGetOwnOrInheritedUnionProperty(string memberName, IsSuitableUnionProperty isSuitableUnionMember, ref CompoundUseSiteInfo<AssemblySymbol> membersProviderForDefinitionBasesUseSiteInfo)
        {
            // Any change in collection of 'membersProviderForDefinitionBasesUseSiteInfo'
            // should be reflected in implementation of 'UnionValueProperty.addUseSiteInfoForCachedResult'.
            Debug.Assert(this.IsUnionType);
            Debug.Assert(this.IsDefinition);

            NamedTypeSymbol? membersInterfaceForDefinition = this.GetMemberProviderInterfaceForDefinition();
            PropertySymbol? member;

            if (membersInterfaceForDefinition is not null)
            {
                if (getMemberDeclaredInType(membersInterfaceForDefinition, memberName, isSuitableUnionMember, out member))
                {
                    return member;
                }

                PropertySymbol? match = null;
                ImmutableArray<NamedTypeSymbol> memberProviderInterfaceAllInterfaces = GetMemberProviderInterfaceAllInterfacesForDefinition();

                foreach (var iface in memberProviderInterfaceAllInterfaces)
                {
                    iface.OriginalDefinition.AddUseSiteInfo(ref membersProviderForDefinitionBasesUseSiteInfo);
                }

                foreach (var baseInterfaceForDefinition in memberProviderInterfaceAllInterfaces)
                {
                    if (getMemberDeclaredInType(baseInterfaceForDefinition, memberName, isSuitableUnionMember, out member))
                    {
                        if (match is null)
                        {
                            match = member;
                        }
                        else if (!match.ContainingType.AllInterfacesNoUseSiteDiagnostics.Contains(baseInterfaceForDefinition, Symbols.SymbolEqualityComparer.AllIgnoreOptions))
                        {
                            // Ambiguity
                            return null;
                        }
                        else
                        {
                            // Shadowed
                        }
                    }
                }

                return match;
            }
            else
            {
                for (NamedTypeSymbol declaringType = this;
                     declaringType is not null;
                     declaringType = declaringType.BaseTypeWithDefinitionUseSiteDiagnostics(ref membersProviderForDefinitionBasesUseSiteInfo))
                {
                    if (getMemberDeclaredInType(declaringType, memberName, isSuitableUnionMember, out member))
                    {
                        return member;
                    }
                }

                return null;
            }

            static bool getMemberDeclaredInType(NamedTypeSymbol declaringType, string memberName, IsSuitableUnionProperty isSuitableUnionMember, [NotNullWhen(true)] out PropertySymbol? member)
            {
                foreach (var m in declaringType.GetMembers(memberName))
                {
                    if (isSuitableUnionMember(m, out member))
                    {
                        return true;
                    }
                }

                member = null;
                return false;
            }
        }

        internal PropertySymbol? UnionHasValueProperty()
        {
            UnionData lazyUnionData = GetUnionData();
            Symbol? lazyHasValueProperty = lazyUnionData._lazyHasValueProperty;

            if (lazyHasValueProperty != (object)ErrorTypeSymbol.UnknownResultType)
            {
                return (PropertySymbol?)lazyHasValueProperty;
            }

            PropertySymbol? result;

            if (!this.IsDefinition)
            {
                result = this.OriginalDefinition.UnionHasValueProperty();
                if (result is not null)
                {
                    NamedTypeSymbol containingType = result.ContainingType;
                    result = result.OriginalDefinition.AsMember(
                        containingType == (object)this.OriginalDefinition ? this : this.TypeSubstitution.SubstituteNamedType(containingType));
                }
            }
            else
            {
                var useSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
                PropertySymbol? hasValueProperty = TryGetOwnOrInheritedUnionProperty(WellKnownMemberNames.HasValuePropertyName, isSuitableProperty, ref useSiteInfo);
                result = checkAndReturnProperty(hasValueProperty);
            }

            Interlocked.CompareExchange(ref lazyUnionData._lazyHasValueProperty, result, ErrorTypeSymbol.UnknownResultType);

            return (PropertySymbol?)lazyUnionData._lazyHasValueProperty;

            static PropertySymbol? checkAndReturnProperty(PropertySymbol? hasValueProperty)
            {
                if (hasValueProperty?.GetUseSiteInfo().DiagnosticInfo?.DefaultSeverity == DiagnosticSeverity.Error)
                {
                    return null;
                }

                return hasValueProperty;
            }

            static bool isSuitableProperty(Symbol m, [NotNullWhen(true)] out PropertySymbol? hasValueProperty)
            {
                if (m is PropertySymbol prop && hasHasValueSignature(prop))
                {
                    hasValueProperty = prop;
                    return true;
                }

                hasValueProperty = null;
                return false;
            }

            static bool hasHasValueSignature(PropertySymbol property)
            {
                return property is
                {
                    IsStatic: false,
                    GetMethod: { DeclaredAccessibility: Accessibility.Public },
                    RefKind: RefKind.None,
                    ParameterCount: 0,
                    Type.SpecialType: SpecialType.System_Boolean
                };
            }
        }

        internal NullableFlowState UnionValueDeclaredNullableFlowState
        {
            get
            {
                UnionData lazyUnionData = GetUnionData();
                StrongBox<NullableFlowState>? lazyNullableFlowState = lazyUnionData._lazyValueDeclaredNullableFlowState;

                if (lazyNullableFlowState is not null)
                {
                    return lazyNullableFlowState.Value;
                }

                // For union types where none of the case types are nullable, the default state for Value is "not null" rather than "maybe null".
                var result = NullableFlowState.NotNull;
                var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;

                foreach (var factory in this.UnionFactoryMethods(ref discardedUseSiteInfo))
                {
                    var parameter = factory.Parameters[0];
                    result = result.Join(NullableWalker.GetParameterState(parameter.TypeWithAnnotations, parameter.FlowAnalysisAnnotations).State);
                }

                lazyUnionData._lazyValueDeclaredNullableFlowState = new StrongBox<NullableFlowState>(result);
                return result;
            }
        }

        internal MethodSymbol? GetUnionTypeTryGetValueMethod(ConversionsBase conversions, TypeSymbol type)
        {
            Debug.Assert(this.IsUnionType);

            MethodSymbol? bestMatch = null;
            Conversion bestMatchConversion = Conversion.NoConversion;
            ImmutableArray<MethodSymbol> tryGetValueMethods = this.UnionTryGetValueMethods();
            var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;

            foreach (var candidate in tryGetValueMethods)
            {
                Conversion conversion = conversions.ClassifyBuiltInConversion(type, candidate.Parameters[0].Type, isChecked: false, ref discardedUseSiteInfo);

                if (!conversion.Exists || !conversion.IsImplicit ||
                    !(conversion.IsIdentity || conversion.IsReference || conversion.IsBoxing ||
                      (conversion.IsNullable && conversion.UnderlyingConversions[0].IsIdentity)))
                {
                    continue;
                }

                if (conversion.IsIdentity)
                {
                    return candidate;
                }
                else
                {
                    Debug.Assert(bestMatch is null ||
                                 bestMatchConversion.IsReference || bestMatchConversion.IsBoxing ||
                                 (bestMatchConversion.IsNullable && bestMatchConversion.UnderlyingConversions[0].IsIdentity));
                    Debug.Assert(!bestMatchConversion.IsReference || !conversion.IsNullable);
                    Debug.Assert(!conversion.IsReference || !bestMatchConversion.IsNullable);
                    Debug.Assert(!bestMatchConversion.IsReference || !conversion.IsBoxing);
                    Debug.Assert(!conversion.IsReference || !bestMatchConversion.IsBoxing);

                    if (bestMatch is null || (!conversion.IsBoxing && bestMatchConversion.IsBoxing))
                    {
                        bestMatch = candidate;
                        bestMatchConversion = conversion;
                    }
                }
            }

            return bestMatch;
        }

        internal ImmutableArray<MethodSymbol> UnionTryGetValueMethods()
        {
            UnionData lazyUnionData = GetUnionData();
            ImmutableArray<MethodSymbol> lazyTryGetValueMethods = lazyUnionData._lazyTryGetValueMethods;

            if (!lazyTryGetValueMethods.IsDefault)
            {
                return lazyTryGetValueMethods;
            }

            var result = ArrayBuilder<MethodSymbol>.GetInstance();

            if (!this.IsDefinition)
            {
                ImmutableArray<MethodSymbol> definitionTryGetValueMethods = this.OriginalDefinition.UnionTryGetValueMethods();
                TypeMap typeSubstitution = this.TypeSubstitution;

                for (int i = 0; i < definitionTryGetValueMethods.Length; i++)
                {
                    NamedTypeSymbol containerForDefinition = definitionTryGetValueMethods[i].ContainingType;
                    NamedTypeSymbol constructedOrSubstitutedContainer = containerForDefinition == (object)this.OriginalDefinition ? this : typeSubstitution.SubstituteNamedType(containerForDefinition);

                    for (int j = i; j < definitionTryGetValueMethods.Length; j++)
                    {
                        if (definitionTryGetValueMethods[j].ContainingType != (object)containerForDefinition)
                        {
                            Debug.Assert(!containerForDefinition.Equals(definitionTryGetValueMethods[j].ContainingType, TypeCompareKind.ConsiderEverything));
                            break;
                        }

                        i = j;
                        MethodSymbol tryGetValueMethod = definitionTryGetValueMethods[i].OriginalDefinition.AsMember(constructedOrSubstitutedContainer);
                        result.Add(tryGetValueMethod);
                    }
                }
            }
            else
            {
                PooledHashSet<TypeSymbol>? typeSet = null;
                NamedTypeSymbol? membersInterfaceForDefinition = GetMemberProviderInterfaceForDefinition();

                if (membersInterfaceForDefinition is not null)
                {
                    addCandidates(membersInterfaceForDefinition, ref typeSet, result);

                    foreach (var declaringType in GetMemberProviderInterfaceAllInterfacesForDefinition())
                    {
                        addCandidates(declaringType, ref typeSet, result);
                    }
                }
                else
                {
                    for (NamedTypeSymbol declaringType = this;
                         declaringType is not null;
                         declaringType = declaringType.BaseTypeNoUseSiteDiagnostics)
                    {
                        addCandidates(declaringType, ref typeSet, result);
                    }
                }

                typeSet?.Free();
            }

            ImmutableInterlocked.InterlockedInitialize(ref lazyUnionData._lazyTryGetValueMethods, result.ToImmutableAndFree());
            return lazyUnionData._lazyTryGetValueMethods;

            void addCandidates(
                NamedTypeSymbol declaringType,
                ref PooledHashSet<TypeSymbol>? typeSet,
                ArrayBuilder<MethodSymbol> result)
            {
                var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;

                foreach (var m in declaringType.GetMembers(WellKnownMemberNames.TryGetValueMethodName))
                {
                    if (m is MethodSymbol candidate && HasTryGetValueSignature(candidate) &&
                        candidate.GetUseSiteInfo().DiagnosticInfo?.DefaultSeverity != DiagnosticSeverity.Error)
                    {
                        if (typeSet is null)
                        {
                            typeSet = TypeSymbol.AllIgnoreOptionsSetPool.Allocate();

                            foreach (var caseType in this.UnionCaseTypes(ref discardedUseSiteInfo))
                            {
                                typeSet.Add(caseType);

                                if (caseType.IsNullableType())
                                {
                                    typeSet.Add(caseType.GetNullableUnderlyingType());
                                }

                            }
                        }

                        if (typeSet.Contains(candidate.Parameters[0].Type))
                        {
                            result.Add(candidate);
                        }
                    }
                }
            }
        }

        internal static bool HasTryGetValueSignature(MethodSymbol method)
        {
            return method is
            {
                IsStatic: false,
                DeclaredAccessibility: Accessibility.Public,
                Arity: 0,
                RefKind: RefKind.None,
                Parameters: [{ RefKind: RefKind.Out }],
                ReturnType.SpecialType: SpecialType.System_Boolean
            };
        }

        internal bool IsUnionTypeTryGetValueMethod(MethodSymbol method)
        {
            Debug.Assert(this.IsUnionType);

            if (method.Name is not WellKnownMemberNames.TryGetValueMethodName)
            {
                return false;
            }

            MethodSymbol originalDefinition = method.OriginalDefinition;
            NamedTypeSymbol? membersInterfaceForDefinition = this.GetMemberProviderInterfaceForDefinition();

            if (membersInterfaceForDefinition is not null == originalDefinition.ContainingType.IsInterface)
            {
                foreach (var candidate in this.UnionTryGetValueMethods())
                {
                    if (candidate.OriginalDefinition == (object)originalDefinition &&
                        candidate.Equals(method, TypeCompareKind.AllIgnoreOptions))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

#nullable disable

        /// <summary>
        /// Verify if the given type can be used to back a tuple type 
        /// and return cardinality of that tuple type in <paramref name="tupleCardinality"/>. 
        /// </summary>
        /// <param name="tupleCardinality">If method returns true, contains cardinality of the compatible tuple type.</param>
        /// <returns></returns>
        internal bool IsTupleTypeOfCardinality(out int tupleCardinality)
        {
            // Should this be optimized for perf (caching for VT<0> to VT<7>, etc.)?
            if (!IsUnboundGenericType &&
                ContainingSymbol?.Kind == SymbolKind.Namespace &&
                ContainingNamespace.ContainingNamespace?.IsGlobalNamespace == true &&
                Name == ValueTupleTypeName &&
                ContainingNamespace.Name == MetadataHelpers.SystemString)
            {
                int arity = Arity;

                if (arity >= 0 && arity < ValueTupleRestPosition)
                {
                    tupleCardinality = arity;
                    return true;
                }
                else if (arity == ValueTupleRestPosition && !IsDefinition)
                {
                    // Skip through "Rest" extensions
                    TypeSymbol typeToCheck = this;
                    int levelsOfNesting = 0;

                    do
                    {
                        levelsOfNesting++;
                        typeToCheck = ((NamedTypeSymbol)typeToCheck).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[ValueTupleRestPosition - 1].Type;
                    }
                    while (Equals(typeToCheck.OriginalDefinition, this.OriginalDefinition, TypeCompareKind.ConsiderEverything) && !typeToCheck.IsDefinition);

                    arity = (typeToCheck as NamedTypeSymbol)?.Arity ?? 0;

                    if (arity > 0 && arity < ValueTupleRestPosition && ((NamedTypeSymbol)typeToCheck).IsTupleTypeOfCardinality(out tupleCardinality))
                    {
                        Debug.Assert(tupleCardinality < ValueTupleRestPosition);
                        tupleCardinality += (ValueTupleRestPosition - 1) * levelsOfNesting;
                        return true;
                    }
                }
            }

            tupleCardinality = 0;
            return false;
        }

        /// <summary>
        /// Returns an instance of a symbol that represents a native integer
        /// if this underlying symbol represents System.IntPtr or System.UIntPtr.
        /// For platforms that support numeric IntPtr/UIntPtr, those types are returned as-is.
        /// For other symbols, throws <see cref="System.InvalidOperationException"/>.
        /// </summary>
        internal abstract NamedTypeSymbol AsNativeInteger();

        /// <summary>
        /// If this is a native integer, returns the symbol for the underlying type,
        /// either <see cref="System.IntPtr"/> or <see cref="System.UIntPtr"/>.
        /// Otherwise, returns null.
        /// </summary>
        internal abstract NamedTypeSymbol NativeIntegerUnderlyingType { get; }

        protected override ISymbol CreateISymbol()
        {
            return new PublicModel.NonErrorNamedTypeSymbol(this, DefaultNullableAnnotation);
        }

        protected override ITypeSymbol CreateITypeSymbol(CodeAnalysis.NullableAnnotation nullableAnnotation)
        {
            Debug.Assert(nullableAnnotation != DefaultNullableAnnotation);
            return new PublicModel.NonErrorNamedTypeSymbol(this, nullableAnnotation);
        }

        INamedTypeSymbolInternal INamedTypeSymbolInternal.EnumUnderlyingType
            => EnumUnderlyingType;

        ImmutableArray<ISymbolInternal> INamedTypeSymbolInternal.GetMembers()
            => GetMembers().CastArray<ISymbolInternal>();

        ImmutableArray<ISymbolInternal> INamedTypeSymbolInternal.GetMembers(string name)
            => GetMembers(name).CastArray<ISymbolInternal>();

    }
}
