File: Symbols\Source\ExtensionGroupingInfo.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using Microsoft.Cci;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    internal sealed class ExtensionGroupingInfo
    {
        private readonly ImmutableArray<ExtensionGroupingType> _groupingTypes;
 
        public ExtensionGroupingInfo(SourceMemberContainerTypeSymbol container)
        {
            // Extension block symbols declared in a class are grouped by their corresponding grouping type metadata name (top level key),
            // then grouped by their corresponding extension marker type metadata name (the secondary key used by MultiDictionary).
            // SourceNamedTypeSymbols are the extension blocks.
            var groupingMap = new Dictionary<string, MultiDictionary<string, SourceNamedTypeSymbol>>(EqualityComparer<string>.Default);
 
            foreach (var type in container.GetTypeMembers(""))
            {
                if (!type.IsExtension)
                {
                    continue;
                }
 
                var sourceNamedType = (SourceNamedTypeSymbol)type;
                var groupingMetadataName = sourceNamedType.ExtensionGroupingName;
 
                MultiDictionary<string, SourceNamedTypeSymbol>? markerMap;
 
                if (!groupingMap.TryGetValue(groupingMetadataName, out markerMap))
                {
                    markerMap = new MultiDictionary<string, SourceNamedTypeSymbol>(EqualityComparer<string>.Default, ReferenceEqualityComparer.Instance);
                    groupingMap.Add(groupingMetadataName, markerMap);
                }
 
                markerMap.Add(sourceNamedType.ExtensionMarkerName, sourceNamedType);
            }
 
            var builder = ArrayBuilder<ExtensionGroupingType>.GetInstance(groupingMap.Count);
 
            foreach (KeyValuePair<string, MultiDictionary<string, SourceNamedTypeSymbol>> pair in groupingMap)
            {
                builder.Add(new ExtensionGroupingType(pair.Key, pair.Value));
            }
 
            builder.Sort();
 
            _groupingTypes = builder.ToImmutableAndFree();
            AssertInvariants(container);
        }
 
        [Conditional("DEBUG")]
        private void AssertInvariants(SourceMemberContainerTypeSymbol container)
        {
            ImmutableArray<NamedTypeSymbol> typeMembers = container.GetTypeMembers("");
 
            for (int i = 0; i < typeMembers.Length; i++)
            {
                var type1 = (SourceNamedTypeSymbol)typeMembers[i];
                if (!type1.IsExtension)
                {
                    continue;
                }
 
                for (int j = i + 1; j < typeMembers.Length; j++)
                {
                    var type2 = (SourceNamedTypeSymbol)typeMembers[j];
                    if (!type2.IsExtension)
                    {
                        continue;
                    }
 
                    bool groupingNamesMatch = type1.ComputeExtensionGroupingRawName() == type2.ComputeExtensionGroupingRawName();
                    Debug.Assert(groupingNamesMatch || !HaveSameILSignature(type1, type2),
                            "If the IL-level comparer considers two extensions equal, then they must have the same grouping name.");
 
                    bool markerNamesMatch = type1.ComputeExtensionMarkerRawName() == type2.ComputeExtensionMarkerRawName();
                    Debug.Assert(markerNamesMatch || !HaveSameCSharpSignature(type1, type2),
                            "If the C#-level comparer considers two extensions equal, then they must have the same marker name.");
 
                    Debug.Assert(groupingNamesMatch || !markerNamesMatch, "If the marker names are equal, then the grouping names must also be equal.");
                }
            }
        }
 
        public ImmutableArray<Cci.INestedTypeDefinition> GetGroupingTypes()
        {
            return ImmutableArray<Cci.INestedTypeDefinition>.CastUp(_groupingTypes);
        }
 
        public Cci.ITypeDefinition GetCorrespondingMarkerType(SynthesizedExtensionMarker markerMethod)
        {
            return GetCorrespondingMarkerType((SourceNamedTypeSymbol)markerMethod.ContainingType);
        }
 
        private ExtensionMarkerType GetCorrespondingMarkerType(SourceNamedTypeSymbol extension)
        {
            Debug.Assert(extension.IsExtension);
 
            // Tracked by https://github.com/dotnet/roslyn/issues/78827 : Optimize lookup with side dictionaries?
            var groupingName = extension.ExtensionGroupingName;
            var markerName = extension.ExtensionMarkerName;
 
            foreach (var groupingType in _groupingTypes)
            {
                if (groupingType.Name != groupingName)
                {
                    continue;
                }
 
                foreach (var markerType in groupingType.ExtensionMarkerTypes)
                {
                    if (markerType.Name == markerName)
                    {
                        return markerType;
                    }
                }
 
                break;
            }
 
            throw ExceptionUtilities.Unreachable();
        }
 
        public Cci.TypeMemberVisibility GetCorrespondingMarkerMethodVisibility(SynthesizedExtensionMarker marker)
        {
            Debug.Assert(Cci.TypeMemberVisibility.Public > Cci.TypeMemberVisibility.Assembly);
            Debug.Assert(Cci.TypeMemberVisibility.Assembly > Cci.TypeMemberVisibility.Private);
 
            var result = Cci.TypeMemberVisibility.Private;
 
            foreach (var extension in GetCorrespondingMarkerType((SourceNamedTypeSymbol)marker.ContainingType).UnderlyingExtensions)
            {
                foreach (var symbol in extension.GetMembers())
                {
                    var memberVisibility = symbol.MetadataVisibility;
                    Debug.Assert(memberVisibility is Cci.TypeMemberVisibility.Public or Cci.TypeMemberVisibility.Assembly or Cci.TypeMemberVisibility.Private);
 
                    if (memberVisibility == Cci.TypeMemberVisibility.Public)
                    {
                        return TypeMemberVisibility.Public;
                    }
 
                    if (result < memberVisibility)
                    {
                        // If we have a more visible member, use its visibility.
                        result = memberVisibility;
                    }
                }
            }
 
            // Tracked by https://github.com/dotnet/roslyn/issues/78827 : optimization, Is there a real need to cache this result for reuse?
            return result;
        }
 
        public Cci.ITypeDefinition GetCorrespondingGroupingType(SourceNamedTypeSymbol extension)
        {
            Debug.Assert(extension.IsExtension);
 
            // Tracked by https://github.com/dotnet/roslyn/issues/78827 : Optimize lookup with a side dictionary?
            var groupingName = extension.ExtensionGroupingName;
 
            foreach (var groupingType in _groupingTypes)
            {
                if (groupingType.Name == groupingName)
                {
                    return groupingType;
                }
            }
 
            throw ExceptionUtilities.Unreachable();
        }
 
        /// <summary>
        /// Given an extension block, returns all the extensions that are grouped together with it
        /// </summary>
        internal ImmutableArray<SourceNamedTypeSymbol> GetMergedExtensions(SourceNamedTypeSymbol extension)
        {
            Debug.Assert(extension.IsExtension);
            return GetCorrespondingMarkerType(extension).UnderlyingExtensions;
        }
 
        /// <summary>
        /// Returns all the extension blocks but grouped/merged by equivalency (ie. same marker name)
        /// </summary>
        internal IEnumerable<ImmutableArray<SourceNamedTypeSymbol>> EnumerateMergedExtensionBlocks()
        {
            foreach (var groupingType in _groupingTypes)
            {
                foreach (var markerType in groupingType.ExtensionMarkerTypes)
                {
                    yield return markerType.UnderlyingExtensions;
                }
            }
        }
 
        internal static bool HaveSameILSignature(SourceNamedTypeSymbol extension1, SourceNamedTypeSymbol extension2)
        {
            Debug.Assert(extension1.IsExtension);
            Debug.Assert(extension2.IsExtension);
 
            if (extension1.Arity != extension2.Arity)
            {
                return false;
            }
 
            TypeMap? typeMap1 = MemberSignatureComparer.GetTypeMap(extension1);
            TypeMap? typeMap2 = MemberSignatureComparer.GetTypeMap(extension2);
            if (extension1.Arity > 0
                && !MemberSignatureComparer.HaveSameConstraints(extension1.TypeParameters, typeMap1, extension2.TypeParameters, typeMap2, TypeCompareKind.AllIgnoreOptions))
            {
                return false;
            }
 
            ParameterSymbol? parameter1 = extension1.ExtensionParameter;
            ParameterSymbol? parameter2 = extension2.ExtensionParameter;
            if (parameter1 is null || parameter2 is null)
            {
                return parameter1 is null && parameter2 is null;
            }
 
            if (!MemberSignatureComparer.HaveSameParameterType(parameter1, typeMap1, parameter2, typeMap2,
                refKindCompareMode: MemberSignatureComparer.RefKindCompareMode.IgnoreRefKind,
                considerDefaultValues: false, TypeCompareKind.AllIgnoreOptions))
            {
                return false;
            }
 
            return true;
        }
 
        internal static bool HaveSameCSharpSignature(SourceNamedTypeSymbol extension1, SourceNamedTypeSymbol extension2)
        {
            Debug.Assert(extension1.IsExtension);
            Debug.Assert(extension2.IsExtension);
 
            int arity1 = extension1.Arity;
            if (arity1 != extension2.Arity)
            {
                return false;
            }
 
            TypeMap? typeMap1 = MemberSignatureComparer.GetTypeMap(extension1);
            TypeMap? typeMap2 = MemberSignatureComparer.GetTypeMap(extension2);
            if (arity1 > 0)
            {
                ImmutableArray<TypeParameterSymbol> typeParams1 = extension1.TypeParameters;
                ImmutableArray<TypeParameterSymbol> typeParams2 = extension2.TypeParameters;
 
                if (!typeParams1.SequenceEqual(typeParams2, (p1, p2) => p1.Name == p2.Name))
                {
                    return false;
                }
 
                if (!typeParams1.SequenceEqual(typeParams2, (p1, p2) => hasSameAttributes(p1.GetAttributes(), p2.GetAttributes())))
                {
                    return false;
                }
 
                for (int i = 0; i < arity1; i++)
                {
                    if (!haveSameConstraints(typeParams1[i], typeMap1, typeParams2[i], typeMap2))
                    {
                        return false;
                    }
                }
            }
 
            ParameterSymbol? parameter1 = extension1.ExtensionParameter;
            ParameterSymbol? parameter2 = extension2.ExtensionParameter;
            if (parameter1 is null)
            {
                return parameter2 is null;
            }
            else if (parameter2 is null)
            {
                return parameter1 is null;
            }
 
            if (parameter1.DeclaredScope != parameter2.DeclaredScope)
            {
                return false;
            }
 
            if (parameter1.Name != parameter2.Name)
            {
                return false;
            }
 
            if (!MemberSignatureComparer.HaveSameParameterType(parameter1, typeMap1, parameter2, typeMap2,
                refKindCompareMode: MemberSignatureComparer.RefKindCompareMode.ConsiderDifferences,
                considerDefaultValues: false, TypeCompareKind.ConsiderEverything))
            {
                return false;
            }
 
            if (!hasSameAttributes(parameter1.GetAttributes(), parameter2.GetAttributes()))
            {
                return false;
            }
 
            return true;
 
            static bool hasSameAttributes(ImmutableArray<CSharpAttributeData> attributes1, ImmutableArray<CSharpAttributeData> attributes2)
            {
                if (attributes1.IsEmpty && attributes2.IsEmpty)
                {
                    return true;
                }
 
                // Tracked by https://github.com/dotnet/roslyn/issues/78827 : optimization, consider using a pool
                var comparer = CommonAttributeDataComparer.InstanceIgnoringNamedArgumentOrder;
                var counts = new Dictionary<CSharpAttributeData, int>(comparer);
 
                foreach (var attribute in attributes1)
                {
                    if (attribute.IsConditionallyOmitted)
                    {
                        continue;
                    }
 
                    counts[attribute] = counts.TryGetValue(attribute, out var foundCount) ? foundCount + 1 : 1;
                }
 
                foreach (var attribute in attributes2)
                {
                    if (attribute.IsConditionallyOmitted)
                    {
                        continue;
                    }
 
                    if (!counts.TryGetValue(attribute, out var foundCount) || foundCount == 0)
                    {
                        return false;
                    }
 
                    counts[attribute] = foundCount - 1;
                }
 
                return counts.Values.All(c => c == 0);
            }
 
            static bool haveSameConstraints(TypeParameterSymbol typeParameter1, TypeMap? typeMap1, TypeParameterSymbol typeParameter2, TypeMap? typeMap2)
            {
                if ((typeParameter1.HasConstructorConstraint != typeParameter2.HasConstructorConstraint) ||
                    (typeParameter1.HasReferenceTypeConstraint != typeParameter2.HasReferenceTypeConstraint) ||
                    (typeParameter1.HasValueTypeConstraint != typeParameter2.HasValueTypeConstraint) ||
                    (typeParameter1.AllowsRefLikeType != typeParameter2.AllowsRefLikeType) ||
                    (typeParameter1.HasUnmanagedTypeConstraint != typeParameter2.HasUnmanagedTypeConstraint) ||
                    (typeParameter1.Variance != typeParameter2.Variance) ||
                    (typeParameter1.HasNotNullConstraint != typeParameter2.HasNotNullConstraint))
                {
                    return false;
                }
 
                return haveSameTypeConstraints(typeParameter1, typeMap1, typeParameter2, typeMap2);
            }
 
            static bool haveSameTypeConstraints(TypeParameterSymbol typeParameter1, TypeMap? typeMap1, TypeParameterSymbol typeParameter2, TypeMap? typeMap2)
            {
                // Since the purpose is to ensure that we can safely round-trip metadata
                // and since top-level nullability is encoded per type constraint
                // we need to check nullability (including top-level nullability) per type constraint.
 
                ImmutableArray<TypeWithAnnotations> constraintTypes1 = typeParameter1.ConstraintTypesNoUseSiteDiagnostics;
                ImmutableArray<TypeWithAnnotations> constraintTypes2 = typeParameter2.ConstraintTypesNoUseSiteDiagnostics;
 
                if (constraintTypes1.IsEmpty && constraintTypes2.IsEmpty)
                {
                    return true;
                }
 
                var comparer = TypeWithAnnotations.EqualsComparer.ConsiderEverythingComparer;
                var substitutedTypes1 = new HashSet<TypeWithAnnotations>(comparer);
                var substitutedTypes2 = new HashSet<TypeWithAnnotations>(comparer);
 
                substituteConstraintTypes(constraintTypes1, typeMap1, substitutedTypes1);
                substituteConstraintTypes(constraintTypes2, typeMap2, substitutedTypes2);
 
                return areConstraintTypesSubset(substitutedTypes1, substitutedTypes2, typeParameter2) &&
                    areConstraintTypesSubset(substitutedTypes2, substitutedTypes1, typeParameter1);
            }
 
            static bool areConstraintTypesSubset(HashSet<TypeWithAnnotations> constraintTypes1, HashSet<TypeWithAnnotations> constraintTypes2, TypeParameterSymbol typeParameter2)
            {
                foreach (TypeWithAnnotations constraintType in constraintTypes1)
                {
                    if (!constraintTypes2.Contains(constraintType))
                    {
                        return false;
                    }
                }
 
                return true;
            }
 
            static void substituteConstraintTypes(ImmutableArray<TypeWithAnnotations> types, TypeMap? typeMap, HashSet<TypeWithAnnotations> result)
            {
                foreach (TypeWithAnnotations type in types)
                {
                    result.Add(MemberSignatureComparer.SubstituteType(typeMap, type));
                }
            }
        }
 
        /// <summary>
        /// Reports diagnostic when:
        /// two extension blocks grouped into a single grouping type have different IL-level signatures, or
        /// two extension blocks grouped into a single marker type have different C#-level signatures.
        /// </summary>
        internal void CheckSignatureCollisions(BindingDiagnosticBag diagnostics)
        {
            PooledHashSet<SourceNamedTypeSymbol>? alreadyReportedExtensions = null;
 
            foreach (ExtensionGroupingType groupingType in _groupingTypes)
            {
                checkCollisions(enumerateExtensionsInGrouping(groupingType), HaveSameILSignature, ref alreadyReportedExtensions, diagnostics);
            }
 
            foreach (ImmutableArray<SourceNamedTypeSymbol> mergedBlocks in EnumerateMergedExtensionBlocks())
            {
                checkCollisions(mergedBlocks, HaveSameCSharpSignature, ref alreadyReportedExtensions, diagnostics);
            }
 
            alreadyReportedExtensions?.Free();
            return;
 
            static IEnumerable<SourceNamedTypeSymbol> enumerateExtensionsInGrouping(ExtensionGroupingType groupingType)
            {
                foreach (var marker in groupingType.ExtensionMarkerTypes)
                {
                    foreach (var extension in marker.UnderlyingExtensions)
                    {
                        yield return extension;
                    }
                }
            }
 
            static void checkCollisions(IEnumerable<SourceNamedTypeSymbol> extensions, Func<SourceNamedTypeSymbol, SourceNamedTypeSymbol, bool> compare,
                ref PooledHashSet<SourceNamedTypeSymbol>? alreadyReportedExtensions, BindingDiagnosticBag diagnostics)
            {
                SourceNamedTypeSymbol? first = null;
 
                foreach (SourceNamedTypeSymbol extension in extensions)
                {
                    Debug.Assert(extension.IsExtension);
                    Debug.Assert(extension.IsDefinition);
 
                    if (first is null)
                    {
                        first = extension;
                        continue;
                    }
 
                    if (!compare(first, extension))
                    {
                        alreadyReportedExtensions ??= PooledHashSet<SourceNamedTypeSymbol>.GetInstance();
                        if (alreadyReportedExtensions.Add(extension))
                        {
                            diagnostics.Add(ErrorCode.ERR_ExtensionBlockCollision, extension.Locations[0]);
                        }
                    }
                }
            }
        }
 
        private abstract class ExtensionGroupingOrMarkerType : Cci.INestedTypeDefinition
        {
            ushort ITypeDefinition.Alignment => 0;
 
            IEnumerable<IGenericTypeParameter> ITypeDefinition.GenericParameters => GenericParameters;
 
            protected abstract IEnumerable<IGenericTypeParameter> GenericParameters { get; }
 
            ushort ITypeDefinition.GenericParameterCount => GenericParameterCount;
 
            ushort INamedTypeReference.GenericParameterCount => GenericParameterCount;
 
            protected abstract ushort GenericParameterCount { get; }
 
            bool ITypeDefinition.HasDeclarativeSecurity => false;
 
            bool ITypeDefinition.IsAbstract => IsAbstract;
 
            protected abstract bool IsAbstract { get; }
 
            bool ITypeDefinition.IsBeforeFieldInit => false;
 
            bool ITypeDefinition.IsComObject => false;
 
            bool ITypeDefinition.IsGeneric => GenericParameterCount != 0;
 
            bool ITypeDefinition.IsInterface => false;
 
            bool ITypeDefinition.IsDelegate => false;
 
            bool ITypeDefinition.IsRuntimeSpecial => false;
 
            bool ITypeDefinition.IsSerializable => false;
 
            bool ITypeDefinition.IsSpecialName => true;
 
            bool ITypeDefinition.IsWindowsRuntimeImport => false;
 
            bool ITypeDefinition.IsSealed => IsSealed;
 
            protected abstract bool IsSealed { get; }
 
            LayoutKind ITypeDefinition.Layout => LayoutKind.Auto;
 
            IEnumerable<SecurityAttribute> ITypeDefinition.SecurityAttributes => SpecializedCollections.EmptyEnumerable<SecurityAttribute>();
 
            uint ITypeDefinition.SizeOf => 0;
 
            CharSet ITypeDefinition.StringFormat => CharSet.Ansi;
 
            ITypeDefinition ITypeDefinitionMember.ContainingTypeDefinition => ContainingTypeDefinition;
 
            protected abstract ITypeDefinition ContainingTypeDefinition { get; }
 
            TypeMemberVisibility ITypeDefinitionMember.Visibility => TypeMemberVisibility.Public;
 
            bool IDefinition.IsEncDeleted => false;
 
            bool INamedTypeReference.MangleName => false;
 
            string? INamedTypeReference.AssociatedFileIdentifier => null;
 
            bool ITypeReference.IsEnum => false;
 
            bool ITypeReference.IsValueType => false;
 
            Cci.PrimitiveTypeCode ITypeReference.TypeCode => Cci.PrimitiveTypeCode.NotPrimitive;
 
            TypeDefinitionHandle ITypeReference.TypeDef => default;
 
            IGenericMethodParameterReference? ITypeReference.AsGenericMethodParameterReference => null;
 
            IGenericTypeInstanceReference? ITypeReference.AsGenericTypeInstanceReference => null;
 
            IGenericTypeParameterReference? ITypeReference.AsGenericTypeParameterReference => null;
 
            INamespaceTypeReference? ITypeReference.AsNamespaceTypeReference => null;
 
            INestedTypeReference? ITypeReference.AsNestedTypeReference => this;
 
            ISpecializedNestedTypeReference? ITypeReference.AsSpecializedNestedTypeReference => null;
 
            string? INamedEntity.Name => Name;
 
            public abstract string Name { get; }
 
            IDefinition? IReference.AsDefinition(EmitContext context)
            {
                return this;
            }
 
            INamespaceTypeDefinition? ITypeReference.AsNamespaceTypeDefinition(EmitContext context)
            {
                return null;
            }
 
            INestedTypeDefinition? ITypeReference.AsNestedTypeDefinition(EmitContext context)
            {
                return this;
            }
 
            bool Cci.INestedTypeReference.InheritsEnclosingTypeTypeParameters => false;
 
            ITypeDefinition? ITypeReference.AsTypeDefinition(EmitContext context)
            {
                return this;
            }
 
            void IReference.Dispatch(MetadataVisitor visitor)
            {
                visitor.Visit((INamedTypeDefinition)this);
            }
 
            IEnumerable<ICustomAttribute> IReference.GetAttributes(EmitContext context)
            {
                return GetAttributes(context);
            }
 
            protected abstract IEnumerable<ICustomAttribute> GetAttributes(EmitContext context);
 
            ITypeReference? ITypeDefinition.GetBaseClass(EmitContext context)
            {
                return ObjectType;
            }
 
            protected abstract ITypeReference? ObjectType { get; }
 
            ITypeReference ITypeMemberReference.GetContainingType(EmitContext context)
            {
                return ContainingTypeDefinition;
            }
 
            IEnumerable<IEventDefinition> ITypeDefinition.GetEvents(EmitContext context)
            {
                return SpecializedCollections.EmptyEnumerable<IEventDefinition>();
            }
 
            IEnumerable<Cci.MethodImplementation> ITypeDefinition.GetExplicitImplementationOverrides(EmitContext context)
            {
                return SpecializedCollections.EmptyEnumerable<Cci.MethodImplementation>();
            }
 
            IEnumerable<IFieldDefinition> ITypeDefinition.GetFields(EmitContext context)
            {
                return SpecializedCollections.EmptyEnumerable<IFieldDefinition>();
            }
 
            ISymbolInternal? IReference.GetInternalSymbol()
            {
                return null;
            }
 
            IEnumerable<IMethodDefinition> ITypeDefinition.GetMethods(EmitContext context)
            {
                return GetMethods(context);
            }
 
            protected abstract IEnumerable<IMethodDefinition> GetMethods(EmitContext context);
 
            IEnumerable<INestedTypeDefinition> ITypeDefinition.GetNestedTypes(EmitContext context)
            {
                return NestedTypes;
            }
 
            protected abstract IEnumerable<INestedTypeDefinition> NestedTypes { get; }
 
            IEnumerable<IPropertyDefinition> ITypeDefinition.GetProperties(EmitContext context)
            {
                return GetProperties(context);
            }
 
            protected abstract IEnumerable<IPropertyDefinition> GetProperties(EmitContext context);
 
            ITypeDefinition? ITypeReference.GetResolvedType(EmitContext context)
            {
                return this;
            }
 
            IEnumerable<TypeReferenceWithAttributes> ITypeDefinition.Interfaces(EmitContext context)
            {
                return SpecializedCollections.EmptyEnumerable<TypeReferenceWithAttributes>();
            }
 
            public sealed override bool Equals(object? obj)
            {
                // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used.
                throw ExceptionUtilities.Unreachable();
            }
 
            public sealed override int GetHashCode()
            {
                // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used.
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        private sealed class ExtensionGroupingType : ExtensionGroupingOrMarkerType, IComparable<ExtensionGroupingType>
        {
            private readonly string _name;
            public readonly ImmutableArray<ExtensionMarkerType> ExtensionMarkerTypes;
            private ImmutableArray<ExtensionGroupingTypeTypeParameter> _lazyTypeParameters;
 
            public ExtensionGroupingType(string name, MultiDictionary<string, SourceNamedTypeSymbol> extensionMarkerTypes)
            {
                _name = name;
 
                var builder = ArrayBuilder<ExtensionMarkerType>.GetInstance(extensionMarkerTypes.Count);
 
                foreach (var pair in extensionMarkerTypes)
                {
                    builder.Add(new ExtensionMarkerType(this, name: pair.Key, extensions: pair.Value));
                }
 
                builder.Sort();
                ExtensionMarkerTypes = builder.ToImmutableAndFree();
            }
 
            int IComparable<ExtensionGroupingType>.CompareTo(ExtensionGroupingType? other)
            {
                Debug.Assert(other is { });
                return ExtensionMarkerTypes[0].CompareTo(other.ExtensionMarkerTypes[0]);
            }
 
            protected override IEnumerable<IGenericTypeParameter> GenericParameters
            {
                get
                {
                    if (_lazyTypeParameters.IsDefault)
                    {
                        var typeParameters = ExtensionMarkerTypes[0].UnderlyingExtensions[0].Arity != 0 ?
                            ((INestedTypeDefinition)ExtensionMarkerTypes[0].UnderlyingExtensions[0].GetCciAdapter()).GenericParameters.SelectAsArray(static (p, @this) => new ExtensionGroupingTypeTypeParameter(@this, p), this) :
                            [];
                        ImmutableInterlocked.InterlockedInitialize(ref _lazyTypeParameters, typeParameters);
                    }
 
                    return _lazyTypeParameters;
                }
            }
 
            protected override ushort GenericParameterCount => (ushort)ExtensionMarkerTypes[0].UnderlyingExtensions[0].Arity;
 
            protected override bool IsAbstract => false;
 
            protected override bool IsSealed => true;
 
            protected override ITypeDefinition ContainingTypeDefinition
            {
                get
                {
                    NamedTypeSymbol? containingType = ExtensionMarkerTypes[0].UnderlyingExtensions[0].ContainingType;
                    Debug.Assert(containingType is not null);
                    return containingType.GetCciAdapter();
                }
            }
 
            public override string Name => _name;
 
            protected override ITypeReference? ObjectType => ExtensionMarkerTypes[0].UnderlyingExtensions[0].ContainingAssembly.GetSpecialType(SpecialType.System_Object).GetCciAdapter();
 
            protected override IEnumerable<IMethodDefinition> GetMethods(EmitContext context)
            {
                foreach (var marker in ExtensionMarkerTypes)
                {
                    foreach (var type in marker.UnderlyingExtensions)
                    {
                        foreach (var method in type.GetMethodsToEmit())
                        {
                            Debug.Assert((object)method != null);
 
                            if (method.GetCciAdapter().ShouldInclude(context))
                            {
                                yield return method.GetCciAdapter();
                            }
                        }
                    }
                }
            }
 
            protected override IEnumerable<INestedTypeDefinition> NestedTypes => ExtensionMarkerTypes;
 
            protected override IEnumerable<IPropertyDefinition> GetProperties(EmitContext context)
            {
                foreach (var marker in ExtensionMarkerTypes)
                {
                    foreach (var type in marker.UnderlyingExtensions)
                    {
                        foreach (PropertySymbol property in type.GetPropertiesToEmit())
                        {
                            Debug.Assert((object)property != null);
                            IPropertyDefinition definition = property.GetCciAdapter();
                            // If any accessor should be included, then the property should be included too
                            if (definition.ShouldInclude(context) || !definition.GetAccessors(context).IsEmpty())
                            {
                                yield return definition;
                            }
                        }
                    }
                }
            }
 
            protected override IEnumerable<ICustomAttribute> GetAttributes(EmitContext context)
            {
                SynthesizedAttributeData? extensionAttribute = ExtensionMarkerTypes[0].UnderlyingExtensions[0].DeclaringCompilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_ExtensionAttribute__ctor);
 
                if (extensionAttribute is { })
                {
                    yield return extensionAttribute;
                }
            }
        }
 
        private sealed class ExtensionGroupingTypeTypeParameter : InheritedTypeParameter
        {
            internal ExtensionGroupingTypeTypeParameter(ExtensionGroupingType inheritingType, IGenericTypeParameter parentParameter) :
                base(parentParameter.Index, inheritingType, parentParameter)
            {
            }
 
            public override string? Name => "$T" + Index;
 
            public override IEnumerable<TypeReferenceWithAttributes> GetConstraints(EmitContext context)
            {
                // Drop all attributes from constraints, they are about C# specific semantics.
                foreach (var constraint in base.GetConstraints(context))
                {
                    yield return new TypeReferenceWithAttributes(constraint.TypeRef);
                }
            }
 
            public override IEnumerable<ICustomAttribute> GetAttributes(EmitContext context)
            {
                // Preserve only the synthesized IsUnmanagedAttribute.
                if (MustBeValueType)
                {
                    var unmanagedCtor = ((PEModuleBuilder)context.Module).TryGetSynthesizedIsUnmanagedAttribute()?.Constructors[0] ??
                        ((ExtensionGroupingType)DefiningType).ExtensionMarkerTypes[0].UnderlyingExtensions[0].DeclaringCompilation.
                            GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_IsUnmanagedAttribute__ctor);
 
                    if (unmanagedCtor is { })
                    {
                        foreach (var attribute in base.GetAttributes(context))
                        {
                            if (attribute is SynthesizedAttributeData synthesized &&
                                synthesized.AttributeConstructor == unmanagedCtor)
                            {
                                return [synthesized];
                            }
                        }
                    }
                }
 
                return SpecializedCollections.EmptyEnumerable<ICustomAttribute>();
            }
        }
 
        private sealed class ExtensionMarkerType : ExtensionGroupingOrMarkerType, IComparable<ExtensionMarkerType>
        {
            public readonly ExtensionGroupingType GroupingType;
            private readonly string _name;
            public readonly ImmutableArray<SourceNamedTypeSymbol> UnderlyingExtensions;
            private ImmutableArray<InheritedTypeParameter> _lazyTypeParameters;
 
            public ExtensionMarkerType(ExtensionGroupingType groupingType, string name, MultiDictionary<string, SourceNamedTypeSymbol>.ValueSet extensions)
            {
                GroupingType = groupingType;
                _name = name;
 
                var builder = ArrayBuilder<SourceNamedTypeSymbol>.GetInstance(extensions.Count);
                builder.AddRange(extensions);
                builder.Sort(LexicalOrderSymbolComparer.Instance);
                UnderlyingExtensions = builder.ToImmutableAndFree();
            }
 
            public int CompareTo(ExtensionMarkerType? other)
            {
                Debug.Assert(other is { });
                return LexicalOrderSymbolComparer.Instance.Compare(UnderlyingExtensions[0], other.UnderlyingExtensions[0]);
            }
 
            protected override IEnumerable<IGenericTypeParameter> GenericParameters
            {
                get
                {
                    if (_lazyTypeParameters.IsDefault)
                    {
                        var typeParameters = UnderlyingExtensions[0].Arity != 0 ?
                            ((INestedTypeDefinition)UnderlyingExtensions[0].GetCciAdapter()).GenericParameters.SelectAsArray(static (p, @this) => new InheritedTypeParameter(p.Index, @this, p), this) :
                            [];
                        ImmutableInterlocked.InterlockedInitialize(ref _lazyTypeParameters, typeParameters);
                    }
 
                    return _lazyTypeParameters;
                }
            }
 
            protected override ushort GenericParameterCount => (ushort)UnderlyingExtensions[0].Arity;
 
            protected override bool IsAbstract => true;
 
            protected override bool IsSealed => true;
 
            protected override ITypeDefinition ContainingTypeDefinition => GroupingType;
 
            public override string Name => _name;
 
            protected override ITypeReference? ObjectType => UnderlyingExtensions[0].ContainingAssembly.GetSpecialType(SpecialType.System_Object).GetCciAdapter();
 
            protected override IEnumerable<IMethodDefinition> GetMethods(EmitContext context)
            {
                var marker = UnderlyingExtensions[0].TryGetOrCreateExtensionMarker();
 
                if (marker is { })
                {
                    yield return marker.GetCciAdapter();
                }
            }
 
            protected override IEnumerable<INestedTypeDefinition> NestedTypes => SpecializedCollections.EmptyEnumerable<INestedTypeDefinition>();
 
            protected override IEnumerable<IPropertyDefinition> GetProperties(EmitContext context) => SpecializedCollections.EmptyEnumerable<IPropertyDefinition>();
 
            protected override IEnumerable<ICustomAttribute> GetAttributes(EmitContext context)
            {
                return SpecializedCollections.EmptyEnumerable<ICustomAttribute>();
            }
        }
    }
}