File: EntityRegistry.cs
Web Access
Project: ILAssembler.csproj (ILAssembler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Text;

namespace ILAssembler
{
    internal sealed class EntityRegistry
    {
        private readonly Dictionary<TableIndex, List<EntityBase>> _seenEntities = new();
        private readonly Dictionary<(TypeDefinitionEntity? ContainingType, string Namespace, string Name), TypeDefinitionEntity> _seenTypeDefs = new();
        private readonly Dictionary<(EntityBase ResolutionScope, string Namespace, string Name), TypeReferenceEntity> _seenTypeRefs = new();
        private readonly Dictionary<string, AssemblyReferenceEntity> _seenAssemblyRefs = new(StringComparer.OrdinalIgnoreCase);
        private readonly Dictionary<string, ModuleReferenceEntity> _seenModuleRefs = new();
        private readonly Dictionary<BlobBuilder, TypeSpecificationEntity> _seenTypeSpecs = new(new BlobBuilderContentEqualityComparer());
        private readonly Dictionary<BlobBuilder, StandaloneSignatureEntity> _seenStandaloneSignatures = new(new BlobBuilderContentEqualityComparer());
        private readonly Dictionary<string, FileEntity> _seenFiles = new();
        private readonly List<ManifestResourceEntity> _manifestResourceEntities = new();
        private readonly Dictionary<(ExportedTypeEntity? ContainingType, string Namespace, string Name), ExportedTypeEntity> _seenExportedTypes = new();
        private readonly List<TypeReferenceEntity> _typeReferences = new();
        private readonly List<MemberReferenceEntity> _memberReferences = new();
        private readonly Dictionary<(EntityBase, BlobBuilder), MethodSpecificationEntity> _seenMethodSpecs = new(new MethodSpecEqualityComparer());

        private sealed class BlobBuilderContentEqualityComparer : IEqualityComparer<BlobBuilder>
        {
            public bool Equals(BlobBuilder? x, BlobBuilder? y)
            {
                if (x is null && y is null)
                {
                    return true;
                }

                if (x is null || y is null)
                {
                    return false;
                }

                return x.ContentEquals(y!);
            }

            public int GetHashCode(BlobBuilder obj)
            {
                HashCode hash = default;
                foreach (var b in obj.GetBlobs())
                {
                    hash.AddBytes(b.GetBytes());
                }
                return hash.ToHashCode();
            }
        }

        private sealed class MethodSpecEqualityComparer : IEqualityComparer<(EntityBase, BlobBuilder)>
        {
            public bool Equals((EntityBase, BlobBuilder) x, (EntityBase, BlobBuilder) y)
            {
                return x.Item1 == y.Item1 && x.Item2.ContentEquals(y.Item2);
            }

            public int GetHashCode((EntityBase, BlobBuilder) obj)
            {
                return (obj.Item1.GetHashCode(), obj.Item2.Count).GetHashCode();
            }
        }

        public enum WellKnownBaseType
        {
            System_Object,
            System_ValueType,
            System_Enum
        }

        public EntityRegistry()
        {
            ModuleType = GetOrCreateTypeDefinition(null, "", "<Module>", moduleType =>
            {
                moduleType.BaseType = null;
            });
        }

        public IReadOnlyList<EntityBase> GetSeenEntities(TableIndex table)
        {
            if (_seenEntities.TryGetValue(table, out var entities))
            {
                return entities;
            }
            return Array.Empty<EntityBase>();
        }

        public Blob WriteContentTo(MetadataBuilder builder, BlobBuilder ilStream, IReadOnlyDictionary<string, int> mappedFieldDataNames, bool deterministic)
        {
            // Set the assembly handle early since DeclarativeSecurityAttribute needs it
            // The assembly definition handle is always row 1 (there's only ever one assembly per module)
            // Assembly table token = 0x20000001
            if (Assembly is not null)
            {
                ((IHasHandle)Assembly).SetHandle(MetadataTokens.EntityHandle(0x20000001));
            }

            List<GenericParameterEntity> allGenericParams = [];
            List<GenericParameterConstraintEntity> allGenericConstraints = [];

            // Now that we've seen all of the entities, we can write them out in the correct order.
            // Record the entities in the correct order so they are assigned handles.
            // After this, we'll write out the content of the entities in the correct order.
            foreach (TypeDefinitionEntity type in GetSeenEntities(TableIndex.TypeDef))
            {
                // Record entries for members defined in list columns
                foreach (var method in type.Methods)
                {
                    RecordEntityInTable(TableIndex.MethodDef, method);
                    foreach (var param in method.Parameters)
                    {
                        // COMPAT: Always emit Param rows for explicit parameters (sequence > 0)
                        // to match native ilasm behavior. For the return type parameter (sequence 0),
                        // only emit if it has attributes, a name, or associated metadata.
                        if (param.Sequence > 0
                            || param.Name is not null
                            || param.Attributes != ParameterAttributes.None
                            || param.MarshallingDescriptor.Count != 0
                            || param.HasCustomAttributes
                            || param.HasConstant)
                        {
                            RecordEntityInTable(TableIndex.Param, param);
                        }
                    }

                    // Don't record generic parameters or constraints for methods.
                    // Entries need to be sorted by the value of the TypeOrMethodDef coded index,
                    // which can intermix TypeDef and MethodDef generic parameters based on the order of the TypeDef and MethodDef entries.
                    // We'll record these after processing all TypeDefs and MethodDefs.
                    allGenericParams.AddRange(method.GenericParameters);
                    allGenericConstraints.AddRange(method.GenericParameterConstraints);
                }
                foreach (var field in type.Fields)
                {
                    RecordEntityInTable(TableIndex.Field, field);
                }
                foreach (var property in type.Properties)
                {
                    RecordEntityInTable(TableIndex.Property, property);
                }
                foreach (var @event in type.Events)
                {
                    RecordEntityInTable(TableIndex.Event, @event);
                }

                // Record entries in tables that are sorted based on their containing/associated class
                foreach (var impl in type.InterfaceImplementations)
                {
                    RecordEntityInTable(TableIndex.InterfaceImpl, impl);
                }

                foreach (var impl in type.MethodImplementations)
                {
                    RecordEntityInTable(TableIndex.MethodImpl, impl);
                }

                // Don't record generic parameters or constraints for methods.
                // Entries need to be sorted by the value of the TypeOrMethodDef coded index,
                // which can intermix TypeDef and MethodDef generic parameters based on the order of the TypeDef and MethodDef entries.
                // We'll record these after processing all TypeDefs and MethodDefs.
                allGenericParams.AddRange(type.GenericParameters);
                allGenericConstraints.AddRange(type.GenericParameterConstraints);
            }

            // Now that we've processed all TypeDefs and their corresponding GenericParam and GenericParamConstraint entries,
            // we can process the GenericParam and GenericParamConstrain entries
            // and maintain ordering requirements for TypeOrMethodDef coded index values.
            allGenericParams.Sort((gp1, gp2) =>
            {
                var owner1 = gp1.Owner!.Handle;
                var owner2 = gp2.Owner!.Handle;
                int row1 = MetadataTokens.GetRowNumber(owner1);
                int row2 = MetadataTokens.GetRowNumber(owner2);
                int tag1 = owner1.Kind == HandleKind.TypeDefinition ? 0 : 1;
                int tag2 = owner2.Kind == HandleKind.TypeDefinition ? 0 : 1;
                int compare = (row1 << 1 | tag1).CompareTo(row2 << 1 | tag2);
                if (compare != 0)
                {
                    return compare;
                }
                return gp1.Index.CompareTo(gp2.Index);
            });

            foreach (GenericParameterEntity genericParam in allGenericParams)
            {
                // GenericParam index is stored as a 2-byte value; skip params beyond the limit
                if (genericParam.Index > ushort.MaxValue)
                    continue;

                RecordEntityInTable(TableIndex.GenericParam, genericParam);
            }

            allGenericConstraints.Sort((c1, c2) =>
            {
                var owner1 = c1.Owner!.Handle;
                var owner2 = c2.Owner!.Handle;
                int row1 = MetadataTokens.GetRowNumber(owner1);
                int row2 = MetadataTokens.GetRowNumber(owner2);
                return row1.CompareTo(row2);
            });

            foreach (GenericParameterConstraintEntity constraint in allGenericConstraints)
            {
                RecordEntityInTable(TableIndex.GenericParamConstraint, constraint);
            }

            // Resolve TypeRef entities to local TypeDef entities when possible.
            // This must happen before MemberRef resolution so that MemberRef parents
            // that point to local types already have TypeDef handles.
            ResolveTypeReferences();

            // Create a signature rewriter that remaps PseudoHandle-based TypeRef coded indices
            // in blobs to the resolved real handles via list index lookup.
            SignatureRewriter signatureRewriter = new(_typeReferences);

            foreach (MemberReferenceEntity memberReferenceEntity in _memberReferences)
            {
                ResolveAndRecordMemberReference(memberReferenceEntity);
            }

            // Now that we've recorded all of the entities that wouldn't have had handles before,
            // we can start writing out the content of the entities.
            Blob mvidFixup = default;
            GuidHandle mvid;
            if (deterministic)
            {
                ReservedBlob<GuidHandle> reservedMvid = builder.ReserveGuid();
                mvid = reservedMvid.Handle;
                mvidFixup = reservedMvid.Content;
            }
            else
            {
                mvid = builder.GetOrAddGuid(Guid.NewGuid());
            }

            builder.AddModule(0, Module.Name is null ? default : builder.GetOrAddString(Module.Name), mvid, default, default);

            // Emit every recorded TypeRef row. All TypeRefs are recorded in ResolveTypeReferences
            // (in PseudoHandle order), even when they resolved to a local TypeDef, matching native
            // ilasm which preserves all TypeRef rows. Emitting all of them keeps the emitted TypeRef
            // row numbers aligned with the PseudoHandle values used during parsing/signature encoding.
            foreach (TypeReferenceEntity type in GetSeenEntities(TableIndex.TypeRef))
            {
                EntityHandle scopeHandle = type.ResolutionScope switch
                {
                    FakeTypeEntity fakeScope => fakeScope.ResolutionScopeColumnHandle,
                    // Nested TypeRef: reference the enclosing TypeRef's own TypeRef row (PseudoHandle),
                    // never the TypeDef it may have resolved to. A TypeDefinition handle is not a valid
                    // ResolutionScope coded index (see CodedIndex.ToResolutionScopeTag).
                    TypeReferenceEntity enclosing => enclosing.PseudoHandle,
                    EntityBase scope => scope.Handle
                };
                builder.AddTypeReference(
                    scopeHandle,
                    builder.GetOrAddString(type.Namespace),
                    builder.GetOrAddString(type.Name));
            }

            for (int i = 0; i < GetSeenEntities(TableIndex.TypeDef).Count; i++)
            {
                TypeDefinitionEntity type = (TypeDefinitionEntity)GetSeenEntities(TableIndex.TypeDef)[i];
                builder.AddTypeDefinition(
                    type.Attributes,
                    builder.GetOrAddString(type.Namespace),
                    builder.GetOrAddString(type.Name),
                    type.BaseType is null ? default : type.BaseType.Handle,
                    GetFieldHandleForList(type.Fields, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Fields, i),
                    GetMethodHandleForList(type.Methods, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Methods, i));

                if (type.Events.Count > 0)
                {
                    builder.AddEventMap(
                        (TypeDefinitionHandle)type.Handle,
                        GetEventHandleForList(type.Events, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Events, i));
                }
                if (type.Properties.Count > 0)
                {
                    builder.AddPropertyMap(
                        (TypeDefinitionHandle)type.Handle,
                        GetPropertyHandleForList(type.Properties, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Properties, i));
                }

                if (type.PackingSize is not null || type.ClassSize is not null
                    || (type.Attributes & TypeAttributes.LayoutMask) is TypeAttributes.ExplicitLayout)
                {
                    builder.AddTypeLayout(
                        (TypeDefinitionHandle)type.Handle,
                        (ushort)(type.PackingSize ?? 0),
                        (uint)(type.ClassSize ?? 0));
                }

                if (type.ContainingType is not null)
                {
                    builder.AddNestedType((TypeDefinitionHandle)type.Handle, (TypeDefinitionHandle)type.ContainingType.Handle);
                }
            }

            foreach (FieldDefinitionEntity fieldDef in GetSeenEntities(TableIndex.Field))
            {
                var fieldAttributes = fieldDef.Attributes;
                if (fieldDef.HasConstant)
                {
                    fieldAttributes |= FieldAttributes.HasDefault;
                }
                if (fieldDef.MarshallingDescriptor is { Count: > 0 })
                {
                    fieldAttributes |= FieldAttributes.HasFieldMarshal;
                }
                if (fieldDef.DataDeclarationName is not null && mappedFieldDataNames.ContainsKey(fieldDef.DataDeclarationName))
                {
                    fieldAttributes |= FieldAttributes.HasFieldRVA;
                }
                builder.AddFieldDefinition(
                    fieldAttributes,
                    builder.GetOrAddString(fieldDef.Name),
                    fieldDef.Signature!.Count == 0 ? default : builder.GetOrAddBlob(RewriteSignatureBlob(fieldDef.Signature, signatureRewriter)));

                if (fieldDef.Offset is not null)
                {
                    builder.AddFieldLayout((FieldDefinitionHandle)fieldDef.Handle, fieldDef.Offset.Value);
                }

                if (fieldDef.DataDeclarationName is not null && mappedFieldDataNames.TryGetValue(fieldDef.DataDeclarationName, out int dataOffset))
                {
                    builder.AddFieldRelativeVirtualAddress((FieldDefinitionHandle)fieldDef.Handle, dataOffset);
                }

                if (fieldDef.MarshallingDescriptor is { Count: > 0 })
                {
                    builder.AddMarshallingDescriptor(fieldDef.Handle, builder.GetOrAddBlob(fieldDef.MarshallingDescriptor));
                }

                if (fieldDef.HasConstant)
                {
                    builder.AddConstant(fieldDef.Handle, fieldDef.ConstantValue);
                }
            }

            var bodyStreamEncoder = new MethodBodyStreamEncoder(ilStream);

            for (int i = 0; i < GetSeenEntities(TableIndex.MethodDef).Count; i++)
            {
                MethodDefinitionEntity methodDef = (MethodDefinitionEntity)GetSeenEntities(TableIndex.MethodDef)[i];

                int bodyOffset = -1;
                if (methodDef.MethodBody.CodeBuilder.Count != 0)
                {
                    // Add deferred exception regions now that TypeRef-to-TypeDef resolution is complete.
                    // Catch clause type handles are read here, after resolution has set the real handle.
                    foreach (var region in methodDef.ExceptionRegions)
                    {
                        switch (region)
                        {
                            case ExceptionRegion.CatchRegion catchRegion:
                                methodDef.MethodBody.ControlFlowBuilder!.AddCatchRegion(catchRegion.TryStart, catchRegion.TryEnd, catchRegion.HandlerStart, catchRegion.HandlerEnd, catchRegion.CatchType.Handle);
                                break;
                            case ExceptionRegion.FinallyRegion finallyRegion:
                                methodDef.MethodBody.ControlFlowBuilder!.AddFinallyRegion(finallyRegion.TryStart, finallyRegion.TryEnd, finallyRegion.HandlerStart, finallyRegion.HandlerEnd);
                                break;
                            case ExceptionRegion.FaultRegion faultRegion:
                                methodDef.MethodBody.ControlFlowBuilder!.AddFaultRegion(faultRegion.TryStart, faultRegion.TryEnd, faultRegion.HandlerStart, faultRegion.HandlerEnd);
                                break;
                            case ExceptionRegion.FilterRegion filterRegion:
                                methodDef.MethodBody.ControlFlowBuilder!.AddFilterRegion(filterRegion.TryStart, filterRegion.TryEnd, filterRegion.HandlerStart, filterRegion.HandlerEnd, filterRegion.FilterStart);
                                break;
                        }
                    }

                    StandaloneSignatureHandle localsSigHandle = methodDef.LocalsSignature is not null
                        ? (StandaloneSignatureHandle)methodDef.LocalsSignature.Handle
                        : default;
                    try
                    {
                        bodyOffset = bodyStreamEncoder.AddMethodBody(
                            methodDef.MethodBody,
                            methodDef.MaxStack,
                            localsSigHandle,
                            methodDef.BodyAttributes);
                    }
                    catch (InvalidOperationException)
                    {
                        // Method has unresolved labels or other body errors.
                        // Emit a minimal valid method body containing the raw IL bytes so
                        // the PE can still be emitted (error diagnostics are already recorded).
                        var fallbackBody = bodyStreamEncoder.AddMethodBody(
                            methodDef.MethodBody.CodeBuilder.Count,
                            methodDef.MaxStack,
                            exceptionRegionCount: 0,
                            hasSmallExceptionRegions: true,
                            localsSigHandle,
                            methodDef.BodyAttributes);
                        bodyOffset = fallbackBody.Offset;
                        var writer1 = new BlobWriter(fallbackBody.Instructions);
                        methodDef.MethodBody.CodeBuilder.WriteContentTo(ref writer1);
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        // Exception handler regions have invalid ranges (e.g., from parse
                        // errors that produced malformed control flow). Emit the IL in a
                        // minimal valid method body and omit exception regions in fallback.
                        var fallbackBody = bodyStreamEncoder.AddMethodBody(
                            methodDef.MethodBody.CodeBuilder.Count,
                            methodDef.MaxStack,
                            exceptionRegionCount: 0,
                            hasSmallExceptionRegions: true,
                            localsSigHandle,
                            methodDef.BodyAttributes);
                        bodyOffset = fallbackBody.Offset;
                        var writer2 = new BlobWriter(fallbackBody.Instructions);
                        methodDef.MethodBody.CodeBuilder.WriteContentTo(ref writer2);
                    }
                }

                var methodAttributes = methodDef.MethodAttributes;
                if (methodDef.MethodImportInformation is not null)
                {
                    methodAttributes |= MethodAttributes.PinvokeImpl;
                }
                builder.AddMethodDefinition(
                    methodAttributes,
                    methodDef.ImplementationAttributes,
                    builder.GetOrAddString(methodDef.Name),
                    builder.GetOrAddBlob(RewriteSignatureBlob(methodDef.MethodSignature!, signatureRewriter)),
                    bodyOffset,
                    GetParameterHandleForList(methodDef.Parameters, GetSeenEntities(TableIndex.MethodDef), method => ((MethodDefinitionEntity)method).Parameters, i));

                if (methodDef.MethodImportInformation is not null)
                {
                    builder.AddMethodImport(
                        (MethodDefinitionHandle)methodDef.Handle,
                        methodDef.MethodImportInformation.Value.Attributes,
                        methodDef.MethodImportInformation.Value.EntryPointName is null ? default : builder.GetOrAddString(methodDef.MethodImportInformation.Value.EntryPointName),
                        (ModuleReferenceHandle)methodDef.MethodImportInformation.Value.ModuleName.Handle);
                }
            }

            foreach (ParameterEntity param in GetSeenEntities(TableIndex.Param))
            {
                var paramAttributes = param.Attributes;
                if (param.HasConstant)
                {
                    paramAttributes |= ParameterAttributes.HasDefault;
                }
                if (param.MarshallingDescriptor.Count != 0)
                {
                    paramAttributes |= ParameterAttributes.HasFieldMarshal;
                }
                builder.AddParameter(
                    paramAttributes,
                    param.Name is null ? default : builder.GetOrAddString(param.Name),
                    param.Sequence);

                if (param.MarshallingDescriptor.Count != 0)
                {
                    builder.AddMarshallingDescriptor(param.Handle, builder.GetOrAddBlob(param.MarshallingDescriptor));
                }

                if (param.HasConstant)
                {
                    builder.AddConstant(param.Handle, param.ConstantValue);
                }
            }

            foreach (InterfaceImplementationEntity impl in GetSeenEntities(TableIndex.InterfaceImpl))
            {
                builder.AddInterfaceImplementation(
                    (TypeDefinitionHandle)impl.Type.Handle,
                    impl.InterfaceType is FakeTypeEntity fakeType ? fakeType.TypeColumnHandle : impl.InterfaceType.Handle);
            }

            foreach (MethodImplementationEntity impl in GetSeenEntities(TableIndex.MethodImpl))
            {
                builder.AddMethodImplementation(
                    (TypeDefinitionHandle)impl.MethodBody.ContainingType.Handle,
                    impl.MethodBody.Handle,
                    impl.MethodDeclaration.Handle);
            }

            foreach (MemberReferenceEntity memberRef in _memberReferences)
            {
                // Skip member references that were resolved to local MethodDef or FieldDef tokens.
                if (memberRef.Handle.Kind is HandleKind.MethodDefinition or HandleKind.FieldDefinition)
                {
                    continue;
                }
                builder.AddMemberReference(
                    memberRef.Parent.Handle,
                    builder.GetOrAddString(memberRef.Name),
                    builder.GetOrAddBlob(RewriteSignatureBlob(memberRef.Signature, signatureRewriter)));
            }

            foreach (DeclarativeSecurityAttributeEntity declSecurity in GetSeenEntities(TableIndex.DeclSecurity))
            {
                builder.AddDeclarativeSecurityAttribute(
                    declSecurity.Parent?.Handle ?? default,
                    declSecurity.Action,
                    builder.GetOrAddBlob(declSecurity.PermissionSet));
            }

            foreach (CustomAttributeEntity customAttr in GetSeenEntities(TableIndex.CustomAttribute))
            {
                EntityHandle parent = customAttr.Owner switch
                {
                    AssemblyEntity => EntityHandle.AssemblyDefinition,
                    ModuleEntity => EntityHandle.ModuleDefinition,
                    { Handle: var h } => h,
                    _ => default
                };
                builder.AddCustomAttribute(
                    parent,
                    customAttr.Constructor.Handle,
                    builder.GetOrAddBlob(customAttr.Value));
            }

            foreach (StandaloneSignatureEntity standaloneSig in GetSeenEntities(TableIndex.StandAloneSig))
            {
                builder.AddStandaloneSignature(
                    builder.GetOrAddBlob(RewriteSignatureBlob(standaloneSig.Signature, signatureRewriter)));
            }

            foreach (EventEntity evt in GetSeenEntities(TableIndex.Event))
            {
                builder.AddEvent(
                    evt.Attributes,
                    builder.GetOrAddString(evt.Name),
                    evt.Type.Handle);

                foreach (var accessor in evt.Accessors)
                {
                    if (accessor.Method.Handle.Kind == HandleKind.MethodDefinition)
                    {
                        builder.AddMethodSemantics(evt.Handle, accessor.Semantic, (MethodDefinitionHandle)accessor.Method.Handle);
                    }
                }
            }

            foreach (PropertyEntity prop in GetSeenEntities(TableIndex.Property))
            {
                builder.AddProperty(
                    prop.Attributes,
                    builder.GetOrAddString(prop.Name),
                    builder.GetOrAddBlob(RewriteSignatureBlob(prop.Type, signatureRewriter)));

                foreach (var accessor in prop.Accessors)
                {
                    if (accessor.Method.Handle.Kind == HandleKind.MethodDefinition)
                    {
                        builder.AddMethodSemantics(prop.Handle, accessor.Semantic, (MethodDefinitionHandle)accessor.Method.Handle);
                    }
                }

                if (prop.HasConstant)
                {
                    builder.AddConstant(prop.Handle, prop.ConstantValue);
                }
            }

            foreach (ModuleReferenceEntity moduleRef in GetSeenEntities(TableIndex.ModuleRef))
            {
                builder.AddModuleReference(builder.GetOrAddString(moduleRef.Name));
            }

            foreach (AssemblyReferenceEntity asmRef in GetSeenEntities(TableIndex.AssemblyRef))
            {
                builder.AddAssemblyReference(
                    builder.GetOrAddString(asmRef.Name),
                    asmRef.Version ?? new Version(0, 0, 0, 0),
                    asmRef.Culture is null ? default : builder.GetOrAddString(asmRef.Culture),
                    asmRef.PublicKeyOrToken is null ? default : builder.GetOrAddBlob(asmRef.PublicKeyOrToken),
                    asmRef.Flags,
                    asmRef.Hash is null ? default : builder.GetOrAddBlob(asmRef.Hash));
            }

            foreach (TypeSpecificationEntity typeSpec in GetSeenEntities(TableIndex.TypeSpec))
            {
                builder.AddTypeSpecification(builder.GetOrAddBlob(RewriteTypeSpecBlob(typeSpec.Signature, signatureRewriter)));
            }

            if (Assembly is not null)
            {
                // Combine the base flags with the architecture bits
                var assemblyFlags = Assembly.Flags | (AssemblyFlags)((int)Assembly.ProcessorArchitecture << 4);
                builder.AddAssembly(
                    builder.GetOrAddString(Assembly.Name),
                    Assembly.Version ?? new Version(0, 0, 0, 0),
                    Assembly.Culture is null ? default : builder.GetOrAddString(Assembly.Culture),
                    Assembly.PublicKeyOrToken is null ? default : builder.GetOrAddBlob(Assembly.PublicKeyOrToken),
                    assemblyFlags,
                    Assembly.HashAlgorithm);
            }

            foreach (FileEntity file in GetSeenEntities(TableIndex.File))
            {
                builder.AddAssemblyFile(
                    builder.GetOrAddString(file.Name),
                    file.Hash is not null ? builder.GetOrAddBlob(file.Hash) : default,
                    file.HasMetadata);
            }

            foreach (ExportedTypeEntity exportedType in GetSeenEntities(TableIndex.ExportedType))
            {
                // Implementation must be a valid handle type: AssemblyFileHandle, AssemblyReferenceHandle, or ExportedTypeHandle
                // COMPAT: If implementation is null, skip emitting this exported type
                if (exportedType.Implementation is null)
                {
                    continue;
                }
                builder.AddExportedType(
                    exportedType.Attributes,
                    builder.GetOrAddString(exportedType.Namespace),
                    builder.GetOrAddString(exportedType.Name),
                    exportedType.Implementation.Handle,
                    exportedType.TypeDefinitionId);
            }

            foreach (ManifestResourceEntity resource in GetSeenEntities(TableIndex.ManifestResource))
            {
                builder.AddManifestResource(
                    resource.Attributes,
                    builder.GetOrAddString(resource.Name),
                    resource.Implementation?.Handle ?? default,
                    resource.Offset);
            }

            foreach (MethodSpecificationEntity methodSpec in GetSeenEntities(TableIndex.MethodSpec))
            {
                builder.AddMethodSpecification(methodSpec.Parent.Handle, builder.GetOrAddBlob(RewriteMethodSpecBlob(methodSpec.Signature, signatureRewriter)));
            }

            foreach (GenericParameterEntity genericParam in GetSeenEntities(TableIndex.GenericParam))
            {
                // GenericParam index is stored as a 2-byte value; skip params beyond the limit
                if (genericParam.Index > ushort.MaxValue)
                    continue;
                builder.AddGenericParameter(
                    genericParam.Owner!.Handle,
                    genericParam.Attributes,
                    builder.GetOrAddString(genericParam.Name),
                    genericParam.Index);
            }

            foreach (GenericParameterConstraintEntity constraint in GetSeenEntities(TableIndex.GenericParamConstraint))
            {
                builder.AddGenericParameterConstraint(
                    (GenericParameterHandle)constraint.Owner!.Handle,
                    constraint.BaseType.Handle);
            }

            return mvidFixup;

            static FieldDefinitionHandle GetFieldHandleForList(IReadOnlyList<EntityBase> list, IReadOnlyList<EntityBase> listOwner, Func<EntityBase, IReadOnlyList<EntityBase>> getList, int ownerIndex)
                => (FieldDefinitionHandle)GetHandleForList(list, listOwner, getList, ownerIndex, TableIndex.Field);

            static MethodDefinitionHandle GetMethodHandleForList(IReadOnlyList<EntityBase> list, IReadOnlyList<EntityBase> listOwner, Func<EntityBase, IReadOnlyList<EntityBase>> getList, int ownerIndex)
                => (MethodDefinitionHandle)GetHandleForList(list, listOwner, getList, ownerIndex, TableIndex.MethodDef);

            static PropertyDefinitionHandle GetPropertyHandleForList(IReadOnlyList<EntityBase> list, IReadOnlyList<EntityBase> listOwner, Func<EntityBase, IReadOnlyList<EntityBase>> getList, int ownerIndex)
                => (PropertyDefinitionHandle)GetHandleForList(list, listOwner, getList, ownerIndex, TableIndex.Property);

            static EventDefinitionHandle GetEventHandleForList(IReadOnlyList<EntityBase> list, IReadOnlyList<EntityBase> listOwner, Func<EntityBase, IReadOnlyList<EntityBase>> getList, int ownerIndex)
                => (EventDefinitionHandle)GetHandleForList(list, listOwner, getList, ownerIndex, TableIndex.Event);

            static ParameterHandle GetParameterHandleForList(IReadOnlyList<EntityBase> list, IReadOnlyList<EntityBase> listOwner, Func<EntityBase, IReadOnlyList<EntityBase>> getList, int ownerIndex)
                => (ParameterHandle)GetHandleForList(list, listOwner, getList, ownerIndex, TableIndex.Param);

            static EntityHandle GetHandleForList(IReadOnlyList<EntityBase> list, IReadOnlyList<EntityBase> listOwner, Func<EntityBase, IReadOnlyList<EntityBase>> getList, int ownerIndex, TableIndex tokenType)
            {
                // Return the first entry in the list that has a handle.
                // If no item has a handle, return the start of the next list.
                // If there is no next list, return one past the end of the previous list.
                foreach (var item in list)
                {
                    if (!item.Handle.IsNil)
                    {
                        return item.Handle;
                    }
                }

                for (int i = ownerIndex + 1; i < listOwner.Count; i++)
                {
                    var otherList = getList(listOwner[i]);
                    foreach (var item in otherList)
                    {
                        if (!item.Handle.IsNil)
                        {
                            return item.Handle;
                        }
                    }
                }

                for (int i = ownerIndex - 1; i >= 0; i--)
                {
                    var otherList = getList(listOwner[i]);
                    if (otherList.Count != 0 && !otherList[otherList.Count - 1].Handle.IsNil)
                    {
                        return MetadataTokens.EntityHandle(tokenType, MetadataTokens.GetRowNumber(otherList[otherList.Count - 1].Handle) + 1);
                    }
                }
                // If all lists are empty, return row 1 (first potential entry).
                // ECMA-335 metadata rows are 1-indexed, so row 0 is invalid.
                return MetadataTokens.EntityHandle(tokenType, 1);
            }
        }

        public TypeEntity? ResolveImplicitBaseType(WellKnownBaseType? type)
        {
            if (type is null)
            {
                return null;
            }
            return type switch
            {
                WellKnownBaseType.System_Object => SystemObjectType,
                WellKnownBaseType.System_ValueType => SystemValueTypeType,
                WellKnownBaseType.System_Enum => SystemEnumType,
                _ => throw new ArgumentOutOfRangeException(nameof(type))
            };
        }

        private TypeEntity? _systemObject;
        public TypeEntity SystemObjectType
        {
            get
            {
                return _systemObject ??= ResolveFromCoreAssembly("System.Object");
            }
        }

        private TypeEntity? _systemValueType;
        public TypeEntity SystemValueTypeType
        {
            get
            {
                return _systemValueType ??= ResolveFromCoreAssembly("System.ValueType");
            }
        }

        private TypeEntity? _systemEnum;
        public TypeEntity SystemEnumType
        {
            get
            {
                return _systemEnum ??= ResolveFromCoreAssembly("System.Enum");
            }
        }

        public TypeDefinitionEntity ModuleType { get; }

        public ModuleEntity Module { get; } = new ModuleEntity();

        public AssemblyEntity? Assembly { get; set; }

        private TypeReferenceEntity ResolveFromCoreAssembly(string typeName)
        {
            // Check for assembly refs in order of preference then fall back to creating mscorlib if none found
            AssemblyReferenceEntity coreAsmRef = GetCoreLibAssemblyReference();
            return GetOrCreateTypeReference(coreAsmRef, new TypeName(null, typeName));
        }

        public AssemblyReferenceEntity GetCoreLibAssemblyReference()
        {
            return FindAssemblyReference("System.Private.CoreLib")
                ?? FindAssemblyReference("System.Runtime")
                ?? FindAssemblyReference("mscorlib")
                ?? FindAssemblyReference("netstandard")
                ?? GetOrCreateAssemblyReference("mscorlib", new Version(0, 0, 0, 0), culture: null, publicKeyOrToken: null, 0, ProcessorArchitecture.None);
        }

        private static bool IsCoreLibAssemblyName(string name)
        {
            return name is "mscorlib" or "System.Runtime" or "System.Private.CoreLib" or "netstandard";
        }

        public interface IHasHandle
        {
            EntityHandle Handle { get; }
            void SetHandle(EntityHandle token);
        }

        public void RecordEntityInTable(TableIndex table, EntityBase entity)
        {
            if (!_seenEntities.TryGetValue(table, out List<EntityBase>? entities))
            {
                _seenEntities[table] = entities = new List<EntityBase>();
            }
            entities.Add(entity);
            ((IHasHandle)entity).SetHandle(MetadataTokens.EntityHandle(table, entities.Count));
        }

        private TEntity GetOrCreateEntity<TKey, TEntity>(TKey key, TableIndex table, Dictionary<TKey, TEntity> cache, Func<TKey, TEntity> constructor, Action<TEntity> onCreate)
            where TKey : notnull
            where TEntity : EntityBase
        {
            if (cache.TryGetValue(key, out TEntity? entity))
            {
                return entity;
            }
            entity = constructor(key);
            RecordEntityInTable(table, entity);
            cache.Add(key, entity);
            onCreate(entity);
            return entity;
        }

        private TEntity CreateEntity<TEntity>(TableIndex table, List<TEntity> cache, Func<TEntity> constructor)
            where TEntity : EntityBase
        {
            TEntity entity = constructor();
            RecordEntityInTable(table, entity);
            cache.Add(entity);
            return entity;
        }

        public TypeDefinitionEntity GetOrCreateTypeDefinition(TypeDefinitionEntity? containingType, string @namespace, string name, Action<TypeDefinitionEntity> onCreateType)
        {
            return GetOrCreateEntity((containingType, @namespace, name), TableIndex.TypeDef, _seenTypeDefs, (key) => new(key.Item1, key.Item2, key.Item3), onCreateType);
        }

        public TypeDefinitionEntity? FindTypeDefinition(TypeDefinitionEntity? containingType, string @namespace, string @name)
        {
            if (_seenTypeDefs.TryGetValue((containingType, @namespace, name), out var typeDef))
            {
                return typeDef;
            }
            return null;
        }

        public AssemblyReferenceEntity GetOrCreateAssemblyReference(string name, Action<AssemblyReferenceEntity> onCreateAssemblyReference)
        {
            return GetOrCreateEntity(name, TableIndex.AssemblyRef, _seenAssemblyRefs, _ => new(name), onCreateAssemblyReference);
        }

        public ModuleReferenceEntity GetOrCreateModuleReference(string name, Action<ModuleReferenceEntity> onCreateModuleReference)
        {
            return GetOrCreateEntity(name, TableIndex.ModuleRef, _seenModuleRefs, name => new(name), onCreateModuleReference);
        }

        public ModuleReferenceEntity? FindModuleReference(string name)
        {
            if (_seenModuleRefs.TryGetValue(name, out var moduleRef))
            {
                return moduleRef;
            }
            return null;
        }

        public static GenericParameterEntity CreateGenericParameter(GenericParameterAttributes attributes, string name)
        {
            GenericParameterEntity param = new(attributes, name);
            return param;
        }

        public static GenericParameterConstraintEntity CreateGenericConstraint(TypeEntity baseType)
        {
            GenericParameterConstraintEntity constraint = new(baseType);
            return constraint;
        }

        public TypeSpecificationEntity GetOrCreateTypeSpec(BlobBuilder signature)
        {
            return GetOrCreateEntity(signature, TableIndex.TypeSpec, _seenTypeSpecs, signature => new(signature), _ => { });
        }

        public EntityBase ResolveHandleToEntity(EntityHandle entityHandle)
        {
            _ = MetadataTokens.TryGetTableIndex(entityHandle.Kind, out var tableIndex);
            if (_seenEntities.TryGetValue(tableIndex, out var entity))
            {
                int rowNumber = MetadataTokens.GetRowNumber(entityHandle);
                if (rowNumber >= 1 && rowNumber <= entity.Count)
                {
                    return entity[rowNumber - 1];
                }
            }
            // Row entry does not exist. Use our FakeTypeEntity type to record the invalid handle.
            return new FakeTypeEntity(entityHandle);
        }

        public TypeReferenceEntity GetOrCreateTypeReference(EntityBase resolutionContext, TypeName name)
        {
            // COMPAT: When the resolution scope is a corelib assembly ref (mscorlib, System.Runtime, etc.),
            // redirect to the preferred corelib assembly ref to match native ilasm behavior.
            // Native ilasm always uses the preferred corelib for well-known types.
            if (resolutionContext is AssemblyReferenceEntity asmRefScope && IsCoreLibAssemblyName(asmRefScope.Name))
            {
                var preferredCoreLib = GetCoreLibAssemblyReference();
                if (preferredCoreLib != asmRefScope)
                {
                    resolutionContext = preferredCoreLib;
                }
            }

            Stack<(string Namespace, string Name)> allTypeNames = new();
            // Record all of the containing type names
            for (TypeName? containingType = name; containingType is not null; containingType = containingType.ContainingTypeName)
            {
                allTypeNames.Push(NameHelpers.SplitDottedNameToNamespaceAndName(containingType.DottedName));
            }

            EntityBase scope = resolutionContext;
            while (scope is TypeReferenceEntity typeRef)
            {
                allTypeNames.Push((typeRef.Namespace, typeRef.Name));
                scope = typeRef.ResolutionScope;
            }
            while (allTypeNames.Count > 0)
            {
                var typeName = allTypeNames.Pop();
                var key = (scope, typeName.Namespace, typeName.Name);
                if (!_seenTypeRefs.TryGetValue(key, out TypeReferenceEntity? typeRef))
                {
                    typeRef = new TypeReferenceEntity(scope, typeName.Namespace, typeName.Name);
                    _seenTypeRefs.Add(key, typeRef);
                    _typeReferences.Add(typeRef);
                    typeRef.PseudoHandle = MetadataTokens.TypeReferenceHandle(_typeReferences.Count);

                    StringBuilder builder = new(typeRef.Namespace.Length + typeRef.Name.Length + 1);
                    builder.AppendFormat("{0}.{1}", typeRef.Namespace, typeRef.Name);
                    if (resolutionContext is AssemblyReferenceEntity asmRef)
                    {
                        var assemblyNameInfo = new AssemblyNameInfo(
                            asmRef.Name,
                            asmRef.Version,
                            string.IsNullOrEmpty(asmRef.Culture) ? null : asmRef.Culture,
                            asmRef.PublicKeyOrToken is null ? AssemblyNameFlags.None : AssemblyNameFlags.PublicKey,
                            asmRef.PublicKeyOrToken?.ToImmutableArray() ?? []);
                        builder.Append(", ");
                        builder.Append(assemblyNameInfo.FullName);
                    }
                    typeRef.ReflectionNotation = builder.ToString();
                }
                scope = typeRef;
            }
            return (TypeReferenceEntity)scope;
        }

        public static MethodDefinitionEntity CreateUnrecordedMethodDefinition(TypeDefinitionEntity containingType, string name)
        {
            return new MethodDefinitionEntity(containingType, name);
        }

        public static bool TryAddMethodDefinitionToContainingType(MethodDefinitionEntity methodDef)
        {
            if (methodDef.MethodSignature is null)
            {
                throw new ArgumentException("The method signature must be defined before recording the method definition, to enable detecting duplicate methods.");
            }
            bool allowDuplicate = (methodDef.MethodAttributes & MethodAttributes.MemberAccessMask) == MethodAttributes.PrivateScope;
            if (!allowDuplicate)
            {
                foreach (var method in methodDef.ContainingType.Methods)
                {
                    if (methodDef.Name == method.Name
                        && methodDef.MethodSignature.ContentEquals(method.MethodSignature!)
                        && (method.MethodAttributes & MethodAttributes.MemberAccessMask) != MethodAttributes.PrivateScope)
                    {
                        return false;
                    }
                }
            }
            methodDef.ContainingType.Methods.Add(methodDef);
            return true;
        }

        public static FieldDefinitionEntity? CreateUnrecordedFieldDefinition(FieldAttributes attributes, TypeDefinitionEntity containingType, string name, BlobBuilder signature)
        {
            var field = new FieldDefinitionEntity(attributes, containingType, name, signature);
            bool allowDuplicate = (field.Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.PrivateScope;
            if (!allowDuplicate)
            {
                foreach (var fieldDef in field.ContainingType.Fields)
                {
                    if (fieldDef.Name == field.Name
                        && fieldDef.Signature.ContentEquals(field.Signature!)
                        && (fieldDef.Attributes & FieldAttributes.FieldAccessMask) != FieldAttributes.PrivateScope)
                    {
                        return null;
                    }
                }
            }
            field.ContainingType.Fields.Add(field);
            return field;
        }

        public static InterfaceImplementationEntity CreateUnrecordedInterfaceImplementation(TypeDefinitionEntity implementingType, TypeEntity interfaceType)
        {
            return new InterfaceImplementationEntity(implementingType, interfaceType);
        }

        public static ParameterEntity CreateParameter(ParameterAttributes attributes, string? name, BlobBuilder marshallingDescriptor, int sequence)
        {
            return new ParameterEntity(attributes, name, marshallingDescriptor, sequence);
        }

        public MemberReferenceEntity CreateLazilyRecordedMemberReference(TypeEntity containingType, string name, BlobBuilder signature)
        {
            var entity = new MemberReferenceEntity(containingType, name, signature);
            _memberReferences.Add(entity);
            return entity;
        }

        private sealed class SignatureRewriter : ISignatureTypeProvider<SignatureRewriter.BlobOrHandle, SignatureRewriter.EmptyGenericContext>
        {
            private readonly List<TypeReferenceEntity>? _typeReferences;

            public SignatureRewriter() { }

            public SignatureRewriter(List<TypeReferenceEntity> typeReferences)
            {
                _typeReferences = typeReferences;
            }
            public readonly struct BlobOrHandle
            {
                public BlobOrHandle(BlobBuilder? blob)
                {
                    Blob = blob;
                    Handle = default;
                    HandleIsValueType = false;
                }

                public BlobOrHandle(EntityHandle handle, bool handleIsValueType)
                {
                    Blob = default;
                    Handle = handle;
                    HandleIsValueType = handleIsValueType;
                }

                private BlobBuilder? Blob { get; }
                public EntityHandle Handle { get; }
                public bool HandleIsValueType { get; }

                public static implicit operator BlobOrHandle(BlobBuilder blob) => new(blob);
                public static implicit operator BlobBuilder(BlobOrHandle blobOrHandle)
                {
                    if (blobOrHandle.Blob is not null)
                    {
                        return blobOrHandle.Blob;
                    }
                    var signatureTypeEncoder = new SignatureTypeEncoder(new BlobBuilder());
                    signatureTypeEncoder.Type(blobOrHandle.Handle, blobOrHandle.HandleIsValueType);
                    return signatureTypeEncoder.Builder;
                }

                public void WriteBlobTo(BlobBuilder builder)
                {
                    ((BlobBuilder)this).WriteContentTo(builder);
                }
            }

            public BlobOrHandle GetArrayType(BlobOrHandle elementType, ArrayShape shape)
            {
                var builder = new BlobBuilder();
                builder.WriteByte((byte)SignatureTypeCode.Array);
                elementType.WriteBlobTo(builder);
                var encoder = new ArrayShapeEncoder(builder);
                encoder.Shape(shape.Rank, shape.Sizes, shape.LowerBounds);
                return builder;
            }

            public BlobOrHandle GetByReferenceType(BlobOrHandle elementType)
            {
                var paramEncoder = new ParameterTypeEncoder(new BlobBuilder());
                paramEncoder.Type(isByRef: true);
                elementType.WriteBlobTo(paramEncoder.Builder);
                return paramEncoder.Builder;
            }

            public BlobOrHandle GetFunctionPointerType(MethodSignature<BlobOrHandle> signature)
            {
                var sig = new SignatureTypeEncoder(new BlobBuilder());
                sig.FunctionPointer(signature.Header.CallingConvention, (FunctionPointerAttributes)signature.Header.Attributes, signature.GenericParameterCount)
                    .Parameters(signature.ParameterTypes.Length, out var retTypeBuilder, out var parametersEncoder);
                signature.ReturnType.WriteBlobTo(retTypeBuilder.Builder);
                for (int i = 0; i < signature.ParameterTypes.Length; i++)
                {
                    if (i == signature.RequiredParameterCount)
                    {
                        parametersEncoder.StartVarArgs();
                    }
                    BlobBuilder paramType = signature.ParameterTypes[i];
                    paramType.WriteContentTo(parametersEncoder.AddParameter().Builder);
                }
                return sig.Builder;
            }

            public BlobOrHandle GetGenericInstantiation(BlobOrHandle genericType, ImmutableArray<BlobOrHandle> typeArguments)
            {
                var encoder = new SignatureTypeEncoder(new BlobBuilder());
                var parameterEncoder = encoder.GenericInstantiation(genericType.Handle, typeArguments.Length, genericType.HandleIsValueType);
                foreach (var typeArg in typeArguments)
                {
                    typeArg.WriteBlobTo(parameterEncoder.AddArgument().Builder);
                }
                return encoder.Builder;
            }

            public BlobOrHandle GetGenericMethodParameter(EmptyGenericContext genericContext, int index)
            {
                var encoder = new SignatureTypeEncoder(new BlobBuilder());
                encoder.GenericMethodTypeParameter(index);
                return encoder.Builder;
            }
            public BlobOrHandle GetGenericTypeParameter(EmptyGenericContext genericContext, int index)
            {
                var encoder = new SignatureTypeEncoder(new BlobBuilder());
                encoder.GenericTypeParameter(index);
                return encoder.Builder;
            }
            public BlobOrHandle GetModifiedType(BlobOrHandle modifier, BlobOrHandle unmodifiedType, bool isRequired)
            {
                var builder = new BlobBuilder();
                if (isRequired)
                {
                    builder.WriteByte((byte)SignatureTypeCode.RequiredModifier);
                }
                else
                {
                    builder.WriteByte((byte)SignatureTypeCode.OptionalModifier);
                }
                // The modifier is a TypeDefOrRefOrSpec coded index (no CLASS/VALUETYPE prefix).
                builder.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(modifier.Handle));
                unmodifiedType.WriteBlobTo(builder);
                return builder;
            }
            public BlobOrHandle GetPinnedType(BlobOrHandle elementType)
            {
                var builder = new BlobBuilder();
                builder.WriteByte((byte)SignatureTypeCode.Pinned);
                elementType.WriteBlobTo(builder);
                return builder;
            }
            public BlobOrHandle GetPointerType(BlobOrHandle elementType)
            {
                var paramEncoder = new ParameterTypeEncoder(new BlobBuilder());
                paramEncoder.Type().Pointer();
                elementType.WriteBlobTo(paramEncoder.Builder);
                return paramEncoder.Builder;
            }
            public BlobOrHandle GetPrimitiveType(PrimitiveTypeCode typeCode)
            {
                var paramEncoder = new ParameterTypeEncoder(new BlobBuilder());
                if ((int)typeCode >= 2 && (int)typeCode <= 14)
                {
                    paramEncoder.Type().PrimitiveType(typeCode);
                }
                else
                {
                    // Invalid type code from malformed signature - write raw byte
                    paramEncoder.Builder.WriteByte((byte)typeCode);
                }
                return paramEncoder.Builder;
            }
            public BlobOrHandle GetSZArrayType(BlobOrHandle elementType)
            {
                var paramEncoder = new ParameterTypeEncoder(new BlobBuilder());
                paramEncoder.Type().SZArray();
                elementType.WriteBlobTo(paramEncoder.Builder);
                return paramEncoder.Builder;
            }
            public BlobOrHandle GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
            {
                return new BlobOrHandle(handle, rawTypeKind == (byte)SignatureTypeKind.ValueType);
            }
            public BlobOrHandle GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
            {
                bool isValueType = rawTypeKind == (byte)SignatureTypeKind.ValueType;
                if (_typeReferences is not null)
                {
                    int row = MetadataTokens.GetRowNumber(handle);
                    if (row >= 1 && row <= _typeReferences.Count)
                    {
                        return new BlobOrHandle(_typeReferences[row - 1].Handle, isValueType);
                    }
                }
                return new BlobOrHandle(handle, isValueType);
            }

            public BlobOrHandle GetTypeFromSpecification(MetadataReader reader, EmptyGenericContext genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
            {
                return new BlobOrHandle(handle, rawTypeKind == (byte)SignatureTypeKind.ValueType);
            }

            public struct EmptyGenericContext { }

        }

        private void ResolveTypeReferences()
        {
            // Record every TypeRef as a row in creation (PseudoHandle) order, even when it refers to
            // a locally-defined type, matching native ilasm which preserves all TypeRef rows. Because
            // PseudoHandle is the gapless 1-based index into _typeReferences, the sequential row handle
            // RecordEntityInTable assigns equals the PseudoHandle, keeping emitted rows aligned with the
            // handles used during parsing/signature encoding.
            // After recording, attempt to resolve each TypeRef that refers to a local type to its
            // TypeDef handle; that overwrites the entity's Handle and backpatches any IL tokens. Process
            // in creation order so a nested TypeRef can observe whether its enclosing type resolved first.
            foreach (TypeReferenceEntity typeRef in _typeReferences)
            {
                RecordEntityInTable(TableIndex.TypeRef, typeRef);
                TryResolveTypeReferenceToDefinition(typeRef);
            }
        }

        private bool TryResolveTypeReferenceToDefinition(TypeReferenceEntity typeRef)
        {
            EntityBase resolutionScope = typeRef.ResolutionScope;

            // Nested TypeRef: resolution scope is another TypeRef.
            // If the outer type was resolved to a TypeDef, look up the nested type.
            if (resolutionScope is TypeReferenceEntity outerTypeRef)
            {
                if (outerTypeRef.Handle.Kind == HandleKind.TypeDefinition)
                {
                    var outerTypeDef = (TypeDefinitionEntity)GetSeenEntities(TableIndex.TypeDef)[MetadataTokens.GetRowNumber(outerTypeRef.Handle) - 1];
                    var nestedTypeDef = FindTypeDefinition(outerTypeDef, typeRef.Namespace, typeRef.Name);
                    if (nestedTypeDef is not null)
                    {
                        ((IHasHandle)typeRef).SetHandle(nestedTypeDef.Handle);
                        return true;
                    }
                }
                return false;
            }

            // Top-level TypeRef: check if the resolution scope is a self-referencing assembly.
            if (resolutionScope is AssemblyReferenceEntity asmRef)
            {
                if (Assembly is not null && string.Equals(asmRef.Name, Assembly.Name, StringComparison.OrdinalIgnoreCase))
                {
                    var typeDef = FindTypeDefinition(null, typeRef.Namespace, typeRef.Name);
                    if (typeDef is not null)
                    {
                        ((IHasHandle)typeRef).SetHandle(typeDef.Handle);
                        return true;
                    }
                }
                return false;
            }

            // Resolution scope is module-level (ModuleEntity/ModuleReferenceEntity) for a local type.
            if (resolutionScope is ModuleEntity or ModuleReferenceEntity)
            {
                var typeDef = FindTypeDefinition(null, typeRef.Namespace, typeRef.Name);
                if (typeDef is not null)
                {
                    ((IHasHandle)typeRef).SetHandle(typeDef.Handle);
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Rewrites a signature blob, replacing PseudoHandle-based TypeRef coded indices
        /// with resolved Handle-based coded indices. Returns the original blob if no mapping
        /// is needed or if the signature cannot be decoded.
        /// </summary>
        private static BlobBuilder RewriteSignatureBlob(BlobBuilder original, SignatureRewriter rewriter)
        {
            var bytes = original.ToArray();
            if (bytes.Length == 0)
            {
                return original;
            }

            try
            {
                var header = new SignatureHeader(bytes[0]);
                return header.Kind switch
                {
                    SignatureKind.Method => RewriteMethodSignatureBlob(bytes, rewriter),
                    SignatureKind.Field => RewriteFieldSignatureBlob(bytes, rewriter),
                    SignatureKind.LocalVariables => RewriteLocalSignatureBlob(bytes, rewriter),
                    SignatureKind.Property => RewritePropertySignatureBlob(bytes, rewriter),
                    _ => original
                };
            }
            catch
            {
                return original;
            }
        }

        /// <summary>
        /// Rewrites a TypeSpec signature blob (which is just a type, not a full signature with header).
        /// </summary>
        private BlobBuilder RewriteTypeSpecBlob(BlobBuilder original, SignatureRewriter rewriter)
        {
            var bytes = original.ToArray();
            if (bytes.Length == 0)
            {
                return original;
            }

            try
            {
                var decoder = new SignatureDecoder<SignatureRewriter.BlobOrHandle, SignatureRewriter.EmptyGenericContext>(rewriter, null!, default);
                unsafe
                {
                    fixed (byte* ptr = bytes)
                    {
                        var reader = new BlobReader(ptr, bytes.Length);
                        var decoded = decoder.DecodeType(ref reader);
                        BlobBuilder result = decoded;
                        return result;
                    }
                }
            }
            catch
            {
                return original;
            }
        }

        /// <summary>
        /// Rewrites a MethodSpec instantiation blob (generic type arguments).
        /// </summary>
        private BlobBuilder RewriteMethodSpecBlob(BlobBuilder original, SignatureRewriter rewriter)
        {
            var bytes = original.ToArray();
            if (bytes.Length == 0)
            {
                return original;
            }

            try
            {
                var decoder = new SignatureDecoder<SignatureRewriter.BlobOrHandle, SignatureRewriter.EmptyGenericContext>(rewriter, null!, default);
                unsafe
                {
                    fixed (byte* ptr = bytes)
                    {
                        var reader = new BlobReader(ptr, bytes.Length);
                        var typeArgs = decoder.DecodeMethodSpecificationSignature(ref reader);
                        var newBlob = new BlobBuilder();
                        const byte methodSpecSignatureHeader = 0x0A;
                        newBlob.WriteByte(methodSpecSignatureHeader);
                        newBlob.WriteCompressedInteger(typeArgs.Length);
                        foreach (var typeArg in typeArgs)
                        {
                            typeArg.WriteBlobTo(newBlob);
                        }
                        return newBlob;
                    }
                }
            }
            catch
            {
                return original;
            }
        }

        private static BlobBuilder RewriteMethodSignatureBlob(byte[] bytes, SignatureRewriter rewriter)
        {
            var decoder = new SignatureDecoder<SignatureRewriter.BlobOrHandle, SignatureRewriter.EmptyGenericContext>(rewriter, null!, default);
            unsafe
            {
                fixed (byte* ptr = bytes)
                {
                    var reader = new BlobReader(ptr, bytes.Length);
                    var sig = decoder.DecodeMethodSignature(ref reader);

                    var newBlob = new BlobBuilder();
                    var encoder = new BlobEncoder(newBlob);
                    encoder.MethodSignature(sig.Header.CallingConvention, sig.GenericParameterCount, sig.Header.Attributes.HasFlag(SignatureAttributes.Instance))
                        .Parameters(sig.ParameterTypes.Length, out var retBuilder, out var paramsBuilder);
                    sig.ReturnType.WriteBlobTo(retBuilder.Builder);
                    for (int i = 0; i < sig.ParameterTypes.Length; i++)
                    {
                        if (sig.RequiredParameterCount != sig.ParameterTypes.Length && i == sig.RequiredParameterCount)
                        {
                            paramsBuilder.StartVarArgs();
                        }
                        sig.ParameterTypes[i].WriteBlobTo(paramsBuilder.AddParameter().Builder);
                    }
                    return newBlob;
                }
            }
        }

        private static BlobBuilder RewriteFieldSignatureBlob(byte[] bytes, SignatureRewriter rewriter)
        {
            var decoder = new SignatureDecoder<SignatureRewriter.BlobOrHandle, SignatureRewriter.EmptyGenericContext>(rewriter, null!, default);
            unsafe
            {
                fixed (byte* ptr = bytes)
                {
                    var reader = new BlobReader(ptr, bytes.Length);
                    var fieldType = decoder.DecodeFieldSignature(ref reader);

                    var newBlob = new BlobBuilder();
                    newBlob.WriteByte((byte)SignatureKind.Field); // 0x06
                    fieldType.WriteBlobTo(newBlob);
                    return newBlob;
                }
            }
        }

        private static BlobBuilder RewriteLocalSignatureBlob(byte[] bytes, SignatureRewriter rewriter)
        {
            var decoder = new SignatureDecoder<SignatureRewriter.BlobOrHandle, SignatureRewriter.EmptyGenericContext>(rewriter, null!, default);
            unsafe
            {
                fixed (byte* ptr = bytes)
                {
                    var reader = new BlobReader(ptr, bytes.Length);
                    var localTypes = decoder.DecodeLocalSignature(ref reader);

                    var newBlob = new BlobBuilder();
                    var encoder = new BlobEncoder(newBlob);
                    var localsEncoder = encoder.LocalVariableSignature(localTypes.Length);
                    foreach (var localType in localTypes)
                    {
                        localType.WriteBlobTo(localsEncoder.AddVariable().Builder);
                    }
                    return newBlob;
                }
            }
        }

        private static BlobBuilder RewritePropertySignatureBlob(byte[] bytes, SignatureRewriter rewriter)
        {
            var decoder = new SignatureDecoder<SignatureRewriter.BlobOrHandle, SignatureRewriter.EmptyGenericContext>(rewriter, null!, default);
            unsafe
            {
                fixed (byte* ptr = bytes)
                {
                    var reader = new BlobReader(ptr, bytes.Length);
                    var sig = decoder.DecodeMethodSignature(ref reader);

                    var newBlob = new BlobBuilder();
                    var encoder = new BlobEncoder(newBlob);
                    encoder.PropertySignature(sig.Header.Attributes.HasFlag(SignatureAttributes.Instance))
                        .Parameters(sig.ParameterTypes.Length, out var retBuilder, out var paramsBuilder);
                    sig.ReturnType.WriteBlobTo(retBuilder.Builder);
                    for (int i = 0; i < sig.ParameterTypes.Length; i++)
                    {
                        sig.ParameterTypes[i].WriteBlobTo(paramsBuilder.AddParameter().Builder);
                    }
                    return newBlob;
                }
            }
        }

        private void ResolveAndRecordMemberReference(MemberReferenceEntity memberRef)
        {
            // We need to resolve a MemberReference in a few scenarios:
            // 1. The MemberReference references a local MethodDefinition
            //   - This case may occur when a method is referenced by a property or event, which can only reference MethodDefinition entities
            //   - This also produces compat with the existing ILASM, which always resolves local method references to MethodDef tokens
            // 2. The MemberReference refers to a local FieldDefinition
            //   - This produces compat with the existing ILASM, which always resolves local field references to FieldDef tokens

            var signature = memberRef.Signature.ToArray();
            SignatureHeader header = new(signature[0]);
            if (header.Kind == SignatureKind.Method)
            {
                if (header.CallingConvention == SignatureCallingConvention.VarArgs)
                {
                    UpdateMemberRefForVarargSignatures(memberRef, signature);
                }
                if (TryResolveMethodReference(memberRef))
                {
                    return;
                }
            }
            else if (header.Kind == SignatureKind.Field)
            {
                if (TryResolveFieldReference(memberRef))
                {
                    return;
                }
            }
            RecordEntityInTable(TableIndex.MemberRef, memberRef);
        }

        private bool TryResolveMethodReference(MemberReferenceEntity memberRef)
        {
            switch (memberRef.Parent)
            {
                // Use this weird construction to look up TypeDefs as we may change TypeRef resolution to use a similar model to MemberReference
                // where we always return a TypeReference type, but it might just point to a TypeDef handle.
                case TypeEntity { Handle.Kind: HandleKind.TypeDefinition } type:
                    {
                        var typeDef = (TypeDefinitionEntity)GetSeenEntities(TableIndex.TypeDef)[MetadataTokens.GetRowNumber(type.Handle) - 1];
                        foreach (var method in typeDef.Methods)
                        {
                            if (method.Name == memberRef.Name
                                && method.MethodSignature!.ContentEquals(memberRef.Signature))
                            {
                                ((IHasHandle)memberRef).SetHandle(method.Handle);
                                return true;
                            }
                        }
                    }
                    break;
            }
            return false;
        }

        private bool TryResolveFieldReference(MemberReferenceEntity memberRef)
        {
            switch (memberRef.Parent)
            {
                case TypeEntity { Handle.Kind: HandleKind.TypeDefinition } type:
                    {
                        var typeDef = (TypeDefinitionEntity)GetSeenEntities(TableIndex.TypeDef)[MetadataTokens.GetRowNumber(type.Handle) - 1];
                        foreach (var field in typeDef.Fields)
                        {
                            if (field.Name == memberRef.Name
                                && field.Signature.ContentEquals(memberRef.Signature))
                            {
                                ((IHasHandle)memberRef).SetHandle(field.Handle);
                                return true;
                            }
                        }
                    }
                    break;
            }
            return false;
        }

        private void UpdateMemberRefForVarargSignatures(MemberReferenceEntity memberRef, byte[] signature)
        {
            var decoder = new SignatureDecoder<SignatureRewriter.BlobOrHandle, SignatureRewriter.EmptyGenericContext>(new SignatureRewriter(), null!, default);
            BlobEncoder methodDefSig = new(new BlobBuilder());
            bool hasVarargParameters = false;
            // TODO-SRM: Propose a public API to construct a blob reader over a byte array or ReadOnlyMemory<byte>
            // to avoid the unsafe block.
            // Alternatively, propose an API to get the corresponding MethodDefSig for a MethodRefSig and move all of this logic into SRM.
            try
            {
                unsafe
                {
                    fixed (byte* ptr = &signature[0])
                    {
                        var reader = new BlobReader(ptr, signature.Length);
                        var methodSignature = decoder.DecodeMethodSignature(ref reader);

                        if (methodSignature.RequiredParameterCount != methodSignature.ParameterTypes.Length)
                        {
                            hasVarargParameters = true;

                            methodDefSig.MethodSignature(methodSignature.Header.CallingConvention, methodSignature.GenericParameterCount, methodSignature.Header.Attributes.HasFlag(SignatureAttributes.Instance))
                                .Parameters(methodSignature.RequiredParameterCount, out var retTypeBuilder, out var parametersEncoder);
                            methodSignature.ReturnType.WriteBlobTo(retTypeBuilder.Builder);
                            for (int i = 0; i < methodSignature.RequiredParameterCount; i++)
                            {
                                methodSignature.ParameterTypes[i].WriteBlobTo(parametersEncoder.AddParameter().Builder);
                            }
                        }
                    }
                }
            }
            catch (BadImageFormatException)
            {
                // Signature contains constructs (e.g., sentinel markers) that the
                // SignatureDecoder cannot parse. Skip vararg processing and emit
                // the MemberRef with its original signature.
                return;
            }

            // If the method has vararg parameters, then this needs to be a MemberRef whose parent is a reference to the method with the signature without any vararg parameters.
            if (hasVarargParameters)
            {
                var methodRef = new MemberReferenceEntity(memberRef.Parent, memberRef.Name, methodDefSig.Builder);
                ResolveAndRecordMemberReference(methodRef);
                // Only reparent the call-site MemberRef if the base method resolved to a MethodDef.
                // MemberRef is not a valid MemberRefParent in the coded index, so we can only
                // reparent when the inner ref resolved to MethodDef.
                if (methodRef.Handle.Kind == HandleKind.MethodDefinition)
                {
                    memberRef.SetMemberRefParent(methodRef);
                }
            }
        }

        public MethodSpecificationEntity GetOrCreateMethodSpecification(EntityBase method, BlobBuilder signature)
        {
            return GetOrCreateEntity(
                (method, signature),
                TableIndex.MethodSpec,
                _seenMethodSpecs,
                ((EntityBase method, BlobBuilder signature) value) => new(method, signature),
                _ => { });
        }

        public StandaloneSignatureEntity GetOrCreateStandaloneSignature(BlobBuilder signature)
        {
            return GetOrCreateEntity(signature, TableIndex.StandAloneSig, _seenStandaloneSignatures, (sig) => new(sig), _ => { });
        }

        public DeclarativeSecurityAttributeEntity CreateDeclarativeSecurityAttribute(DeclarativeSecurityAction action, BlobBuilder permissionSet)
        {
            var entity = new DeclarativeSecurityAttributeEntity(action, permissionSet);
            RecordEntityInTable(TableIndex.DeclSecurity, entity);
            return entity;
        }

        public CustomAttributeEntity CreateCustomAttribute(EntityBase constructor, BlobBuilder value)
        {
            var entity = new CustomAttributeEntity(constructor, value);
            RecordEntityInTable(TableIndex.CustomAttribute, entity);
            return entity;
        }

        public static MethodImplementationEntity CreateUnrecordedMethodImplementation(MethodDefinitionEntity methodBody, MemberReferenceEntity methodDeclaration)
        {
            return new MethodImplementationEntity(methodBody, methodDeclaration);
        }

        public FileEntity GetOrCreateFile(string name, bool hasMetadata, BlobBuilder? hash)
        {
            return GetOrCreateEntity(name, TableIndex.File, _seenFiles, (name) => new FileEntity(name), entity =>
            {
                entity.HasMetadata = hasMetadata;
                entity.Hash = hash;
            });
        }

        public FileEntity? FindFile(string name)
        {
            if (_seenFiles.TryGetValue(name, out var file))
            {
                return file;
            }
            return null;
        }

        public AssemblyReferenceEntity? FindAssemblyReference(string name)
        {
            if (_seenAssemblyRefs.TryGetValue(name, out var asmRef))
            {
                return asmRef;
            }
            return null;
        }

        public AssemblyReferenceEntity GetOrCreateAssemblyReference(string name, Version version, string? culture, BlobBuilder? publicKeyOrToken, AssemblyFlags flags, ProcessorArchitecture architecture)
        {
            return GetOrCreateEntity(name, TableIndex.AssemblyRef, _seenAssemblyRefs, _ => new AssemblyReferenceEntity(name), entity =>
            {
                entity.Version = version;
                entity.Culture = culture;
                entity.PublicKeyOrToken = publicKeyOrToken;
                entity.Flags = flags;
                entity.ProcessorArchitecture = architecture;
            });
        }

        public ManifestResourceEntity CreateManifestResource(string name, uint offset)
        {
            return CreateEntity(TableIndex.ManifestResource, _manifestResourceEntities, () => new ManifestResourceEntity(name, offset));
        }

        public ExportedTypeEntity GetOrCreateExportedType(EntityBase? implementation, string @namespace, string name, Action<ExportedTypeEntity> onCreateType)
        {
            // We only key on the implementation if the type is nested (ExportedTypeEntity).
            // For forwarders, implementation is AssemblyReferenceEntity which is not used in the key.
            // However, we need to pass the actual implementation to the entity constructor.
            return GetOrCreateEntity((implementation as ExportedTypeEntity, @namespace, name), TableIndex.ExportedType, _seenExportedTypes, (key) => new(key.Item3, key.Item2, implementation), onCreateType);
        }

        public ExportedTypeEntity? FindExportedType(ExportedTypeEntity? containingType, string @namespace, string @name)
        {
            if (_seenExportedTypes.TryGetValue((containingType, @namespace, name), out var typeDef))
            {
                return typeDef;
            }
            return null;
        }

        public IHasHandle? EntryPoint { get; set; }

        public abstract class EntityBase : IHasHandle
        {
            public virtual EntityHandle Handle { get; private set; }

            protected virtual void SetHandle(EntityHandle token)
            {
                Handle = token;
            }

            void IHasHandle.SetHandle(EntityHandle token) => SetHandle(token);
        }

        public abstract class TypeEntity : EntityBase
        {
        }

        public interface IHasReflectionNotation
        {
            string ReflectionNotation { get; }
        }

        // COMPAT: The ilasm grammar allows ModuleRefs and AssemblyRefs to be returned in addition to types in typeSpec rules and arbitrary tokens to be returned
        // by the mdtoken grammar, which can replace a type amongst other things.
        // We'll record the actual handle as callers might need to retrieve it,
        // but for emit we'll use the specialized properties depending on the case where the invalid handle is used.
        public sealed class FakeTypeEntity : TypeEntity
        {
            public FakeTypeEntity(EntityHandle realEntity)
            {
                ((IHasHandle)this).SetHandle(realEntity);
                TypeColumnHandle = default(TypeDefinitionHandle);
                ResolutionScopeColumnHandle = default(ModuleDefinitionHandle);
                TypeSignatureHandle = MetadataTokens.TypeDefinitionHandle(MetadataTokens.GetRowNumber(realEntity));
                ImplementationHandle = default(AssemblyReferenceHandle);
            }

            /// <summary>
            /// For cases where an arbitrary (non-TypeDefOrRefOrSpec) token is referenced by a column in a metadata table that uses coded indices, the encoding fails,
            /// ilasm asserts, and a nil token for 0 option (TypeDef) of the coded index is emitted.
            /// </summary>
            public EntityHandle TypeColumnHandle { get; }

            /// <summary>
            /// For cases where an arbitrary (non-ResolutionScope) token is referenced by a column in a metadata table that uses coded indices, the encoding fails,
            /// ilasm asserts, and a nil token for 0 option (ModuleDefinition) of the coded index is emitted.
            /// </summary>
            public EntityHandle ResolutionScopeColumnHandle { get; }

            /// <summary>
            /// In cases where an arbitrary (non-TypeDefOrRefOrSpec) token is referenced in a metadata blob (like a signature blob),
            /// the token is emitted as a compressed integer of the row entry, shifted to account for encoding the table type.
            /// </summary>
            public EntityHandle TypeSignatureHandle { get; }

            /// <summary>
            /// In cases where an arbitrary (non-Implementation) token is referenced in a column in a metadata table,
            /// the the token is emitted as the nil token.
            /// </summary>
            public EntityHandle ImplementationHandle { get; }
        }

        public sealed class TypeDefinitionEntity : TypeEntity, IHasReflectionNotation
        {
            public TypeDefinitionEntity(TypeDefinitionEntity? containingType, string @namespace, string name)
            {
                ContainingType = containingType;
                Namespace = @namespace;
                Name = name;

                ReflectionNotation = CreateReflectionNotation(this);

                static string CreateReflectionNotation(TypeDefinitionEntity typeDefinition)
                {
                    StringBuilder builder = new();
                    Stack<TypeDefinitionEntity> containingTypes = new();
                    for (TypeDefinitionEntity? containingType = typeDefinition; containingType is not null; containingType = containingType.ContainingType)
                    {
                        containingTypes.Push(containingType);
                    }
                    while (containingTypes.Count != 0)
                    {
                        TypeDefinitionEntity containingType = containingTypes.Pop();
                        builder.Append(containingType.Namespace);
                        builder.Append('.');
                        builder.Append(containingType.Name);
                        if (containingTypes.Count > 0)
                        {
                            builder.Append('+');
                        }
                    }
                    return builder.ToString();
                }
            }
            public TypeDefinitionEntity? ContainingType { get; }
            public string Namespace { get; }
            public string Name { get; }
            public TypeAttributes Attributes { get; set; }
            public TypeEntity? BaseType { get; set; }

            public NamedElementList<GenericParameterEntity> GenericParameters { get; } = new();

            // COMPAT: Save the list of generic parameter constraints here to ensure we can match ILASM's emit order for generic parameter constraints exactly.
            public List<GenericParameterConstraintEntity> GenericParameterConstraints { get; } = new();

            public List<MethodDefinitionEntity> Methods { get; } = new();

            public List<MethodImplementationEntity> MethodImplementations { get; } = new();

            public List<FieldDefinitionEntity> Fields { get; } = new();

            public List<PropertyEntity> Properties { get; } = new();

            public List<EventEntity> Events { get; } = new();

            public List<InterfaceImplementationEntity> InterfaceImplementations { get; } = new();

            public string ReflectionNotation { get; }

            // ClassLayout table fields
            public int? PackingSize { get; set; }
            public int? ClassSize { get; set; }
        }

        public sealed class TypeReferenceEntity(EntityBase resolutionScope, string @namespace, string name) : TypeEntity, IHasReflectionNotation
        {
            public EntityBase ResolutionScope { get; } = resolutionScope;

            public string Namespace { get; } = @namespace;

            public string Name { get; } = name;

            public string ReflectionNotation { get; set; } = string.Empty;

            /// <summary>
            /// The TypeRef table row handle for this entity, assigned at creation time.
            /// Because every TypeRef is emitted as a row in this same order (see
            /// <see cref="EntityRegistry.WriteContentTo"/>), this value equals the final emitted
            /// TypeRef row handle. It is used for signature blob encoding during parsing and as the
            /// ResolutionScope of nested TypeRefs, even when this TypeRef also resolves to a local
            /// TypeDef (in which case <see cref="Handle"/> returns the resolved TypeDefinition handle).
            /// </summary>
            public TypeReferenceHandle PseudoHandle { get; set; }

            /// <summary>
            /// Returns the real handle if set (during emission), otherwise the PseudoHandle
            /// (during parsing). This allows code that reads Handle during parsing
            /// (e.g., catch clauses, base type references) to get a valid TypeRef handle.
            /// </summary>
            public override EntityHandle Handle => base.Handle.IsNil ? PseudoHandle : base.Handle;

            private readonly List<Blob> _placesToWriteResolvedToken = new();

            /// <summary>
            /// Records a 4-byte blob location (e.g., in an IL instruction stream) that
            /// contains the PseudoHandle token and needs to be backpatched with the real
            /// handle once TypeRef resolution is complete.
            /// </summary>
            public void RecordBlobToWriteResolvedToken(Blob blob)
            {
                _placesToWriteResolvedToken.Add(blob);
            }

            protected override void SetHandle(EntityHandle token)
            {
                base.SetHandle(token);
                foreach (var blob in _placesToWriteResolvedToken)
                {
                    var writer = new BlobWriter(blob);
                    writer.WriteInt32(MetadataTokens.GetToken(token));
                }
            }
        }

        public sealed class TypeSpecificationEntity(BlobBuilder signature) : TypeEntity
        {
            public BlobBuilder Signature { get; } = signature;
        }

        public sealed class GenericParameterEntity(GenericParameterAttributes attributes, string name) : EntityBase, INamed
        {
            public GenericParameterAttributes Attributes { get; } = attributes;

            public EntityBase? Owner { get; set; }

            public int Index { get; set; }

            public List<GenericParameterConstraintEntity> Constraints { get; } = new();

            public string Name { get; } = name;
        }

        public sealed class GenericParameterConstraintEntity(TypeEntity baseType) : EntityBase
        {
            public GenericParameterEntity? Owner { get; set; }

            public TypeEntity BaseType { get; } = baseType;
        }

        public sealed class ModuleReferenceEntity(string name) : EntityBase
        {
            public string Name { get; } = name;
        }

        public sealed class MethodDefinitionEntity(TypeDefinitionEntity containingType, string name) : EntityBase, IHasHandle
        {
            public TypeDefinitionEntity ContainingType { get; } = containingType;
            public string Name { get; } = name;

            public MethodAttributes MethodAttributes { get; set; }

            public List<ParameterEntity> Parameters { get; } = new();

            public NamedElementList<GenericParameterEntity> GenericParameters { get; } = new();

            // COMPAT: Save the list of generic parameter constraints here to ensure we can match ILASM's emit order for generic parameter constraints exactly.
            public List<GenericParameterConstraintEntity> GenericParameterConstraints { get; } = new();

            public SignatureHeader SignatureHeader { get; set; }

            public BlobBuilder? MethodSignature { get; set; }

            public StandaloneSignatureEntity? LocalsSignature { get; set; }

            // TODO: https://github.com/dotnet/runtime/issues/127261
            // InstructionEncoder produces corrupted IL when mixing OpCode() with
            // direct CodeBuilder.WriteByte() across BlobBuilder chunk boundaries.
            // Using a larger initial capacity avoids multi-chunk operation.
            public InstructionEncoder MethodBody { get; } = new(new BlobBuilder(4096), new ControlFlowBuilder());

            public MethodBodyAttributes BodyAttributes { get; set; }

            /// <summary>
            /// Deferred exception regions. Registered during parsing but added to
            /// <see cref="InstructionEncoder.ControlFlowBuilder"/> during emission
            /// so that TypeRef-to-TypeDef resolution has completed before catch type
            /// handles are read.
            /// </summary>
            public List<ExceptionRegion> ExceptionRegions { get; } = new();

            public int MaxStack { get; set; }

            public (ModuleReferenceEntity ModuleName, string? EntryPointName, MethodImportAttributes Attributes)? MethodImportInformation { get; set; }
            public MethodImplAttributes ImplementationAttributes { get; set; }

            /// <summary>
            /// Debug information for this method (sequence points, document).
            /// </summary>
            public MethodDebugInfo DebugInfo { get; } = new();

            /// <summary>
            /// Export ordinal for this method (from .export directive). -1 means not exported.
            /// </summary>
            public int ExportOrdinal { get; set; } = -1;

            /// <summary>
            /// Export alias name (from .export [n] as alias). Null means use method name.
            /// </summary>
            public string? ExportAlias { get; set; }

            /// <summary>
            /// 1-based VTable entry index (from .vtentry directive). 0 means not in vtable.
            /// </summary>
            public int VTableEntry { get; set; }

            /// <summary>
            /// 1-based slot within the VTable entry (from .vtentry directive). 0 means not in vtable.
            /// </summary>
            public int VTableSlot { get; set; }
        }

        public sealed class ParameterEntity(ParameterAttributes attributes, string? name, BlobBuilder marshallingDescriptor, int sequence) : EntityBase
        {
            public ParameterAttributes Attributes { get; } = attributes;
            public string? Name { get; } = name;
            public BlobBuilder MarshallingDescriptor { get; set; } = marshallingDescriptor;
            public bool HasCustomAttributes { get; set; }
            public int Sequence { get; } = sequence;
            public bool HasConstant { get; set; }
            public object? ConstantValue { get; set; }
        }

        public sealed class MemberReferenceEntity(EntityBase parent, string name, BlobBuilder signature) : EntityBase
        {
            // In the case of a MemberRef to a specific instantiation of a vararg method, we need to update the owner at emit time.
            public EntityBase Parent { get; private set; } = parent;
            public string Name { get; } = name;
            public BlobBuilder Signature { get; } = signature;

            private readonly List<Blob> _placesToWriteResolvedHandle = new();

            public void RecordBlobToWriteResolvedHandle(Blob blob)
            {
                _placesToWriteResolvedHandle.Add(blob);
            }

            protected override void SetHandle(EntityHandle token)
            {
                base.SetHandle(token);
                // Now that we've set the handle, backpatch all the blobs that need to be updated.
                // This way we can determine the right token to use for the member reference
                // after we've processed all of the source code.
                foreach (var blob in _placesToWriteResolvedHandle)
                {
                    var writer = new BlobWriter(blob);
                    writer.WriteInt32(MetadataTokens.GetToken(token));
                }
            }

            internal void SetMemberRefParent(MemberReferenceEntity parent)
            {
                Parent = parent;
            }
        }

        public sealed class MethodSpecificationEntity(EntityBase parent, BlobBuilder signature) : EntityBase
        {
            public EntityBase Parent { get; } = parent;
            public BlobBuilder Signature { get; } = signature;
        }

        public sealed class StandaloneSignatureEntity(BlobBuilder signature) : EntityBase
        {
            public BlobBuilder Signature { get; } = signature;
        }

        public sealed class DeclarativeSecurityAttributeEntity(DeclarativeSecurityAction action, BlobBuilder permissionSet) : EntityBase
        {
            public EntityBase? Parent { get; set; }
            public DeclarativeSecurityAction Action { get; } = action;
            public BlobBuilder PermissionSet { get; } = permissionSet;
        }

        public sealed class CustomAttributeEntity(EntityBase constructor, BlobBuilder value) : EntityBase
        {
            public EntityBase? Owner { get; set; }
            public EntityBase Constructor { get; } = constructor;
            public BlobBuilder Value { get; } = value;
        }

        public sealed class MethodImplementationEntity(MethodDefinitionEntity methodBody, MemberReferenceEntity methodDeclaration) : EntityBase
        {
            public MethodDefinitionEntity MethodBody { get; } = methodBody;
            public MemberReferenceEntity MethodDeclaration { get; } = methodDeclaration;
        }

        public sealed class FieldDefinitionEntity(FieldAttributes attributes, TypeDefinitionEntity type, string name, BlobBuilder signature) : EntityBase
        {
            public FieldAttributes Attributes { get; } = attributes;
            public TypeDefinitionEntity ContainingType { get; } = type;
            public string Name { get; } = name;
            public BlobBuilder Signature { get; } = signature;

            public BlobBuilder? MarshallingDescriptor { get; set; }
            public string? DataDeclarationName { get; set; }

            // FieldLayout table field (explicit field offset)
            public int? Offset { get; set; }

            // Constant table entry
            public bool HasConstant { get; set; }
            public object? ConstantValue { get; set; }
        }

        public sealed class InterfaceImplementationEntity(TypeDefinitionEntity type, TypeEntity interfaceType) : EntityBase
        {
            public TypeDefinitionEntity Type { get; } = type;
            public TypeEntity InterfaceType { get; } = interfaceType;
        }

        public sealed class EventEntity(EventAttributes attributes, TypeEntity type, string name) : EntityBase
        {
            public EventAttributes Attributes { get; } = attributes;
            public TypeEntity Type { get; } = type;
            public string Name { get; } = name;

            public List<(MethodSemanticsAttributes Semantic, EntityBase Method)> Accessors { get; } = new();
        }

        public sealed class PropertyEntity(PropertyAttributes attributes, BlobBuilder type, string name) : EntityBase
        {
            public PropertyAttributes Attributes { get; set; } = attributes;
            public BlobBuilder Type { get; } = type;
            public string Name { get; } = name;
            public bool HasConstant { get; set; }
            public object? ConstantValue { get; set; }

            public List<(MethodSemanticsAttributes Semantic, EntityBase Method)> Accessors { get; } = new();
        }

        public sealed class FileEntity(string name) : EntityBase
        {
            public string Name { get; } = name;
            public bool HasMetadata { get; set; }
            public BlobBuilder? Hash { get; set; }
        }

        public sealed class ModuleEntity : EntityBase
        {
            public string? Name { get; set; }
        }

        public abstract class AssemblyOrRefEntity(string name) : EntityBase
        {
            public string Name { get; set; } = name;
            public Version? Version { get; set; }
            public string? Culture { get; set; }
            public BlobBuilder? PublicKeyOrToken { get; set; }
            public AssemblyFlags Flags { get; set; }
            public ProcessorArchitecture ProcessorArchitecture { get; set; }
        }

        public sealed class AssemblyEntity(string name) : AssemblyOrRefEntity(name)
        {
            public AssemblyHashAlgorithm HashAlgorithm { get; set; }
        }

        public sealed class AssemblyReferenceEntity(string name) : AssemblyOrRefEntity(name)
        {
            public BlobBuilder? Hash { get; set; }
        }

        public sealed class ManifestResourceEntity(string name, uint offset) : EntityBase
        {
            public string Name { get; } = name;
            public uint Offset { get; } = offset;
            public ManifestResourceAttributes Attributes { get; set; }
            public EntityBase? Implementation { get; set; }
        }

        public sealed class ExportedTypeEntity : EntityBase
        {
            public ExportedTypeEntity(string name, string @namespace, EntityBase? implementation)
            {
                Name = name;
                Namespace = @namespace;
                Implementation = implementation;
            }

            public string Name { get; }
            public string Namespace { get; }
            public TypeAttributes Attributes { get; set; }
            public EntityBase? Implementation { get; }

            public int TypeDefinitionId { get; set; }
        }

        /// <summary>
        /// Represents a sequence point mapping IL offset to source location.
        /// </summary>
        public readonly struct SequencePoint
        {
            public SequencePoint(int ilOffset, int startLine, int startColumn, int endLine, int endColumn)
            {
                ILOffset = ilOffset;
                StartLine = startLine;
                StartColumn = startColumn;
                EndLine = endLine;
                EndColumn = endColumn;
            }

            public int ILOffset { get; }
            public int StartLine { get; }
            public int StartColumn { get; }
            public int EndLine { get; }
            public int EndColumn { get; }

            /// <summary>
            /// Creates a hidden sequence point (used for compiler-generated code).
            /// </summary>
            public static SequencePoint Hidden(int ilOffset) => new(ilOffset, 0xFEEFEE, 0, 0xFEEFEE, 0);

            public bool IsHidden => StartLine == 0xFEEFEE;
        }

        /// <summary>
        /// Debug information for a method, including sequence points and local scopes.
        /// </summary>
        public sealed class MethodDebugInfo
        {
            public string? DocumentPath { get; set; }
            public List<SequencePoint> SequencePoints { get; } = new();
        }

        /// <summary>
        /// A deferred exception region entry. Stored during parsing and applied to the
        /// <see cref="ControlFlowBuilder"/> during emission, after TypeRef-to-TypeDef resolution.
        /// </summary>
        internal abstract record ExceptionRegion(LabelHandle TryStart, LabelHandle TryEnd, LabelHandle HandlerStart, LabelHandle HandlerEnd)
        {
            internal sealed record CatchRegion(LabelHandle TryStart, LabelHandle TryEnd, LabelHandle HandlerStart, LabelHandle HandlerEnd, TypeEntity CatchType) : ExceptionRegion(TryStart, TryEnd, HandlerStart, HandlerEnd);
            internal sealed record FinallyRegion(LabelHandle TryStart, LabelHandle TryEnd, LabelHandle HandlerStart, LabelHandle HandlerEnd) : ExceptionRegion(TryStart, TryEnd, HandlerStart, HandlerEnd);
            internal sealed record FaultRegion(LabelHandle TryStart, LabelHandle TryEnd, LabelHandle HandlerStart, LabelHandle HandlerEnd) : ExceptionRegion(TryStart, TryEnd, HandlerStart, HandlerEnd);
            internal sealed record FilterRegion(LabelHandle TryStart, LabelHandle TryEnd, LabelHandle HandlerStart, LabelHandle HandlerEnd, LabelHandle FilterStart) : ExceptionRegion(TryStart, TryEnd, HandlerStart, HandlerEnd);
        }
    }
}