File: EntityRegistry.cs
Web Access
Project: src\src\runtime\src\tools\ilasm\src\ILAssembler\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.Diagnostics;
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<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 void WriteContentTo(MetadataBuilder builder, BlobBuilder ilStream, IReadOnlyDictionary<string, int> mappedFieldDataNames)
        {
            // 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));
            }

            // 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: Only record param entries for parameters that have names
                        // or other rows that would refer to it.
                        if (param.Name is not null
                            || param.MarshallingDescriptor.Count != 0
                            || param.HasCustomAttributes
                            || param.HasConstant)
                        {
                            RecordEntityInTable(TableIndex.Param, param);
                        }
                    }
                    // Record generic parameters for methods
                    foreach (var genericParam in method.GenericParameters)
                    {
                        RecordEntityInTable(TableIndex.GenericParam, genericParam);
                    }
                    foreach (var constraint in method.GenericParameterConstraints)
                    {
                        RecordEntityInTable(TableIndex.GenericParamConstraint, constraint);
                    }
                }
                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);
                }

                foreach (var genericParam in type.GenericParameters)
                {
                    RecordEntityInTable(TableIndex.GenericParam, genericParam);
                }

                // COMPAT: Record the generic parameter constraints based on the order saved in the TypeDef
                foreach (var constraint in type.GenericParameterConstraints)
                {
                    RecordEntityInTable(TableIndex.GenericParamConstraint, constraint);
                }
            }

            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.
            builder.AddModule(0, Module.Name is null ? default : builder.GetOrAddString(Module.Name), builder.GetOrAddGuid(Guid.NewGuid()), default, default);

            foreach (TypeReferenceEntity type in GetSeenEntities(TableIndex.TypeRef))
            {
                EntityBase resolutionScope = type.ResolutionScope;
                builder.AddTypeReference(
                    resolutionScope is FakeTypeEntity fakeScope ? fakeScope.ResolutionScopeColumnHandle : resolutionScope.Handle,
                    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));

                builder.AddEventMap(
                    (TypeDefinitionHandle)type.Handle,
                    GetEventHandleForList(type.Events, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Events, i));
                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)
                {
                    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))
            {
                builder.AddFieldDefinition(
                    fieldDef.Attributes,
                    builder.GetOrAddString(fieldDef.Name),
                    fieldDef.Signature!.Count == 0 ? default : builder.GetOrAddBlob(fieldDef.Signature));

                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 not null)
                {
                    builder.AddMarshallingDescriptor(fieldDef.Handle, builder.GetOrAddBlob(fieldDef.MarshallingDescriptor));
                }

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

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

                int rva = 0;
                if (methodDef.MethodBody.CodeBuilder.Count != 0)
                {
                    rva = ilStream.Count;
                    methodDef.MethodBody.CodeBuilder.WriteContentTo(ilStream);
                }

                builder.AddMethodDefinition(
                    methodDef.MethodAttributes,
                    methodDef.ImplementationAttributes,
                    builder.GetOrAddString(methodDef.Name),
                    builder.GetOrAddBlob(methodDef.MethodSignature!),
                    rva,
                    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))
            {
                builder.AddParameter(
                    param.Attributes,
                    param.Name is null ? default : builder.GetOrAddString(param.Name),
                    param.Sequence);

                if (param.MarshallingDescriptor is not null)
                {
                    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 (MemberReferenceEntity memberRef in _memberReferences)
            {
                builder.AddMemberReference(
                    memberRef.Parent.Handle,
                    builder.GetOrAddString(memberRef.Name),
                    builder.GetOrAddBlob(memberRef.Signature));
            }

            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(standaloneSig.Signature));
            }

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

                foreach (var accessor in evt.Accessors)
                {
                    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(prop.Type));

                foreach (var accessor in prop.Accessors)
                {
                    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(),
                    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(typeSpec.Signature));
            }

            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(),
                    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(methodSpec.Signature));
            }

            foreach (GenericParameterEntity genericParam in GetSeenEntities(TableIndex.GenericParam))
            {
                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);
            }

            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(4, 0), culture: null, publicKeyOrToken: null, 0, ProcessorArchitecture.None);
        }

        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 (entity.Count < rowNumber - 1)
                {
                    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)
        {
            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();
                scope = GetOrCreateEntity((scope, typeName.Namespace, typeName.Name), TableIndex.TypeRef, _seenTypeRefs, value => new TypeReferenceEntity(scope, value.Namespace, value.Name), typeRef =>
                {
                    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();
                });
            }
            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>
        {
            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 encoder = new ArrayShapeEncoder(elementType);
                encoder.Shape(shape.Rank, shape.Sizes, shape.LowerBounds);
                return encoder.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);
                }
                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());
                paramEncoder.Type().PrimitiveType(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)
            {
                return new BlobOrHandle(handle, rawTypeKind == (byte)SignatureTypeKind.ValueType);
            }

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

            public struct EmptyGenericContext { }

        }

        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
            // TODO-COMPAT: The following scenarios are required for compat with the existing ILASM, but are not required to produce valid metadata:
            // 2. The MemberReference refers to a local FieldDefinition

            var signature = memberRef.Signature.ToArray();
            SignatureHeader header = new(signature[0]);
            if (header.Kind == SignatureKind.Method)
            {
                if (header.CallingConvention == SignatureCallingConvention.VarArgs)
                {
                    UpdateMemberRefForVarargSignatures(memberRef, signature);
                }
                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];
                            // Look on this type for methods with the same name and signature
                            foreach (var method in typeDef.Methods)
                            {
                                if (method.Name == memberRef.Name
                                    && method.MethodSignature!.ContentEquals(memberRef.Signature))
                                {
                                    ((IHasHandle)memberRef).SetHandle(method.Handle);
                                    return;
                                }
                            }
                        }
                        break;
                }
            }
            RecordEntityInTable(TableIndex.MemberRef, memberRef);
        }

        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.
            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);
                        }
                    }
                }
            }

            // 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);
                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 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;
        }

        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; }

            public InstructionEncoder MethodBody { get; } = new(new BlobBuilder(), new ControlFlowBuilder());

            public MethodBodyAttributes BodyAttributes { get; set; }

            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();
        }
    }
}