File: GrammarVisitor.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.Buffers.Binary;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using Antlr4.Runtime;
using Antlr4.Runtime.Misc;
using Antlr4.Runtime.Tree;

namespace ILAssembler
{
    internal abstract record GrammarResult
    {
        protected GrammarResult() { }

        public sealed record String(string Value) : GrammarResult;

        public sealed record Literal<T>(T Value) : GrammarResult;

        public sealed record Sequence<T>(ImmutableArray<T> Value) : GrammarResult;

        /// <summary>
        /// A formatted blob of bytes.
        /// </summary>
        /// <param name="Value">The bytes of the blob.</param>
        public sealed record FormattedBlob(BlobBuilder Value) : GrammarResult;

        public sealed record SentinelValue
        {
            public static SentinelValue Instance { get; } = new();

            public static Literal<SentinelValue> Result { get; } = new(Instance);
        }

        public sealed record Flag<T>(T Value, bool ShouldAppend = true) : GrammarResult
            where T : struct, Enum
        {
            private readonly T _groupMask;
            public Flag(T value, bool shouldAppend, T groupMask)
                : this(value, shouldAppend)
            {
                _groupMask = groupMask;
            }
            public Flag(T value, T groupMask)
                : this(value)
            {
                _groupMask = groupMask;
            }

            public static T operator |(T lhs, Flag<T> rhs)
            {
                if (!rhs.ShouldAppend)
                {
                    return rhs.Value;
                }
                return (T)(object)(((int)(object)lhs & (~(int)(object)rhs._groupMask)) | (int)(object)rhs.Value);
            }
        }
    }

#pragma warning disable CA1822 // Mark members as static
    internal sealed class GrammarVisitor : ICILVisitor<GrammarResult>
    {
        private const string NodeShouldNeverBeDirectlyVisited = "This node should never be directly visited. It should be directly processed by its parent node.";
        private readonly ImmutableArray<Diagnostic>.Builder _diagnostics = ImmutableArray.CreateBuilder<Diagnostic>();
        private readonly EntityRegistry _entityRegistry = new();
        private readonly IReadOnlyDictionary<string, SourceText> _documents;
        private readonly Options _options;
        private readonly MetadataBuilder _metadataBuilder = new();
        private readonly Func<string, byte[]> _resourceLocator;

        // Record the mapped field data directly into the blob to ensure we preserve ordering
        private readonly BlobBuilder _mappedFieldData = new();
        private readonly Dictionary<string, int> _mappedFieldDataNames = new();
        private readonly Dictionary<string, List<Blob>> _mappedFieldDataReferenceFixups = new();
        private readonly BlobBuilder _manifestResources = new();

        // Typedef aliases - maps alias name to the resolved entity
        private readonly Dictionary<string, TypedefEntry> _typedefs = new();

        // Debug info tracking
        private Guid _currentLanguageGuid = Guid.Empty;
        private Guid _currentLanguageVendorGuid = Guid.Empty;
        private Guid _currentDocumentTypeGuid = Guid.Empty;
        private string? _currentDocumentPath;
        private readonly Dictionary<string, DocumentHandle> _documentHandles = new();
        private readonly MetadataBuilder _pdbBuilder = new();

        // VTable fixup tracking - uses types from VTableFixupSupport
        private readonly List<VTableFixupSupport.VTableFixupEntry> _vtableFixups = new();

        public GrammarVisitor(IReadOnlyDictionary<string, SourceText> documents, Options options, Func<string, byte[]> resourceLocator)
        {
            _documents = documents;
            _options = options;
            _resourceLocator = resourceLocator;
        }
        /// <summary>
        /// Represents a typedef alias entry.
        /// </summary>
        private abstract record TypedefEntry
        {
            public sealed record Type(EntityRegistry.TypeEntity Entity) : TypedefEntry;
            public sealed record TypeBlob(BlobBuilder Blob) : TypedefEntry;
            public sealed record Member(EntityRegistry.EntityBase Entity) : TypedefEntry;
            public sealed record CustomAttribute(EntityRegistry.EntityBase Constructor, BlobBuilder Value) : TypedefEntry;
        }

        private void ReportDiagnostic(DiagnosticSeverity severity, string id, string message, Antlr4.Runtime.ParserRuleContext context)
        {
            var location = Location.From(context.Start, _documents);
            _diagnostics.Add(new Diagnostic(id, severity, message, location));
        }

        private void ReportError(string id, string message, Antlr4.Runtime.ParserRuleContext context)
            => ReportDiagnostic(DiagnosticSeverity.Error, id, message, context);

        private void ReportWarning(string id, string message, Antlr4.Runtime.ParserRuleContext context)
            => ReportDiagnostic(DiagnosticSeverity.Warning, id, message, context);

        public (ImmutableArray<Diagnostic> Diagnostics, PEBuilder? Image) BuildImage()
        {
            // Return early if there are structural errors that prevent building valid metadata.
            // However, allow errors in method bodies (ILA0016-0019) to pass through so we can
            // emit the assembly with the errors reported.
            // In error-tolerant mode, continue despite errors.
            var structuralErrors = _diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error && !IsRecoverableError(d.Id));
            if (structuralErrors.Any() && !_options.ErrorTolerant)
            {
                return (_diagnostics.ToImmutable(), null);
            }

            // Check for vtable fixups and exports - collect export info
            var exports = ImmutableArray.CreateBuilder<VTableExportPEBuilder.ExportInfo>();
            foreach (var entity in _entityRegistry.GetSeenEntities(TableIndex.MethodDef))
            {
                if (entity is EntityRegistry.MethodDefinitionEntity method && method.ExportOrdinal >= 0)
                {
                    exports.Add(new VTableExportPEBuilder.ExportInfo(
                        method.ExportOrdinal,
                        method.ExportAlias ?? method.Name,
                        MetadataTokens.GetToken(method.Handle),
                        method.VTableEntry,
                        method.VTableSlot));
                }
            }

            BlobBuilder ilStream = new();
            _entityRegistry.WriteContentTo(_metadataBuilder, ilStream, _mappedFieldDataNames);
            MetadataRootBuilder rootBuilder = new(_metadataBuilder, _options.MetadataVersion);

            // Compute metadata size from the MetadataSizes
            // We need this for data label fixup RVA calculations
            var sizes = rootBuilder.Sizes;
            int metadataSize = ComputeMetadataSize(sizes);

            // Apply command-line overrides
            Subsystem subsystem = _options.Subsystem ?? _subsystem;
            int fileAlignment = _options.FileAlignment ?? _alignment;
            long imageBase = _options.ImageBase ?? _imageBase;
            ushort majorSubsystemVersion = _options.SubsystemVersion?.Major ?? 4;
            ushort minorSubsystemVersion = _options.SubsystemVersion?.Minor ?? 0;
            Machine machine = _options.Machine ?? Machine.I386;

            // Build DllCharacteristics from options
            DllCharacteristics dllCharacteristics = DllCharacteristics.DynamicBase | DllCharacteristics.NxCompatible | DllCharacteristics.NoSeh | DllCharacteristics.TerminalServerAware;
            if (_options.AppContainer)
            {
                dllCharacteristics |= DllCharacteristics.AppContainer;
            }
            if (_options.HighEntropyVA)
            {
                dllCharacteristics |= DllCharacteristics.HighEntropyVirtualAddressSpace;
            }
            if (_options.StripReloc)
            {
                dllCharacteristics &= ~DllCharacteristics.DynamicBase;
            }

            PEHeaderBuilder header = new(
                machine: machine,
                fileAlignment: fileAlignment,
                imageBase: (ulong)imageBase,
                subsystem: subsystem,
                majorSubsystemVersion: majorSubsystemVersion,
                minorSubsystemVersion: minorSubsystemVersion,
                dllCharacteristics: dllCharacteristics);

            MethodDefinitionHandle entryPoint = default;
            if (_entityRegistry.EntryPoint is not null)
            {
                entryPoint = (MethodDefinitionHandle)_entityRegistry.EntryPoint.Handle;
            }

            // Build debug directory if we have any debug info
            DebugDirectoryBuilder? debugDirectoryBuilder = BuildDebugDirectory(entryPoint, out int debugDataSize);

            // Use custom PE builder if we have vtable fixups, exports, or data label reference fixups
            if (_vtableFixups.Count > 0 || exports.Count > 0 || _mappedFieldDataReferenceFixups.Count > 0)
            {
                var vtableFixupInfos = BuildVTableFixupInfos();

                // Apply CorFlags from options or directive
                CorFlags corFlags = _options.CorFlags ?? _corflags;
                if (_options.Prefer32Bit)
                {
                    corFlags |= CorFlags.Prefers32Bit;
                }

                VTableExportPEBuilder peBuilder = new(
                    header,
                    rootBuilder,
                    ilStream,
                    _mappedFieldData,
                    _manifestResources,
                    debugDirectoryBuilder: debugDirectoryBuilder,
                    entryPoint: entryPoint,
                    flags: corFlags,
                    vtableFixups: vtableFixupInfos,
                    exports: exports.ToImmutable(),
                    mappedFieldDataOffsets: _mappedFieldDataNames,
                    dataLabelFixups: _mappedFieldDataReferenceFixups,
                    metadataSize: metadataSize,
                    debugDataSize: debugDataSize);

                return (_diagnostics.ToImmutable(), peBuilder);
            }

            // Apply CorFlags from options or directive
            CorFlags standardCorFlags = _options.CorFlags ?? _corflags;
            if (_options.Prefer32Bit)
            {
                standardCorFlags |= CorFlags.Prefers32Bit;
            }

            // Deterministic ID provider for reproducible builds
            Func<IEnumerable<Blob>, BlobContentId>? deterministicIdProvider = _options.Deterministic
                ? content =>
                {
                    using var hash = IncrementalHash.CreateHash(System.Security.Cryptography.HashAlgorithmName.SHA256);
                    foreach (var blob in content)
                    {
                        hash.AppendData(blob.GetBytes());
                    }
                    return BlobContentId.FromHash(hash.GetHashAndReset());
                }
                : null;

            ManagedPEBuilder standardBuilder = new(
                header,
                rootBuilder,
                ilStream,
                _mappedFieldData,
                _manifestResources,
                flags: standardCorFlags,
                entryPoint: entryPoint,
                debugDirectoryBuilder: debugDirectoryBuilder,
                deterministicIdProvider: deterministicIdProvider);

            return (_diagnostics.ToImmutable(), standardBuilder);
        }

        private ImmutableArray<VTableExportPEBuilder.VTableFixupInfo> BuildVTableFixupInfos()
        {
            if (_vtableFixups.Count == 0)
                return ImmutableArray<VTableExportPEBuilder.VTableFixupInfo>.Empty;

            var builder = ImmutableArray.CreateBuilder<VTableExportPEBuilder.VTableFixupInfo>(_vtableFixups.Count);

            for (int entryIndex = 0; entryIndex < _vtableFixups.Count; entryIndex++)
            {
                var vtf = _vtableFixups[entryIndex];
                var methodTokens = ImmutableArray.CreateBuilder<int>(vtf.SlotCount);

                // Initialize with zeros
                for (int i = 0; i < vtf.SlotCount; i++)
                {
                    methodTokens.Add(0);
                }

                // Find methods that reference this vtable entry
                foreach (var entity in _entityRegistry.GetSeenEntities(TableIndex.MethodDef))
                {
                    if (entity is EntityRegistry.MethodDefinitionEntity method &&
                        method.VTableEntry == entryIndex + 1 && // 1-based
                        method.VTableSlot > 0 &&
                        method.VTableSlot <= vtf.SlotCount)
                    {
                        methodTokens[method.VTableSlot - 1] = MetadataTokens.GetToken(method.Handle);
                    }
                }

                builder.Add(new VTableExportPEBuilder.VTableFixupInfo(
                    vtf.DataLabel,
                    vtf.SlotCount,
                    vtf.Flags,
                    methodTokens.ToImmutable()));
            }

            return builder.ToImmutable();
        }

        private DebugDirectoryBuilder? BuildDebugDirectory(MethodDefinitionHandle entryPoint, out int debugDataSize)
        {
            debugDataSize = 0;

            // Check if we have any methods with debug info
            bool hasDebugInfo = false;
            foreach (var entity in _entityRegistry.GetSeenEntities(TableIndex.MethodDef))
            {
                if (entity is EntityRegistry.MethodDefinitionEntity method &&
                    method.DebugInfo.SequencePoints.Count > 0)
                {
                    hasDebugInfo = true;
                    break;
                }
            }

            // Generate PDB if we have debug info OR if --debug/--pdb options are set
            bool generatePdb = hasDebugInfo || _options.Debug || _options.Pdb;
            if (!generatePdb)
            {
                return null;
            }

            // Build PDB metadata
            BuildPdbMetadata();

            // Get row counts from main metadata for the portable PDB
            var typeSystemRowCounts = _metadataBuilder.GetRowCounts();

            // Create the portable PDB
            var pdbBuilder = new PortablePdbBuilder(
                _pdbBuilder,
                typeSystemRowCounts,
                entryPoint,
                idProvider: content => new BlobContentId(Guid.NewGuid(), 0x04030201));

            var pdbBlob = new BlobBuilder();
            var pdbContentId = pdbBuilder.Serialize(pdbBlob);

            // Create debug directory with embedded PDB
            var debugDirectoryBuilder = new DebugDirectoryBuilder();
            debugDirectoryBuilder.AddCodeViewEntry(
                $"assembly.pdb",
                pdbContentId,
                pdbBuilder.FormatVersion);
            debugDirectoryBuilder.AddEmbeddedPortablePdbEntry(pdbBlob, pdbBuilder.FormatVersion);

            // Calculate debug data size:
            // 2 debug directory entries (28 bytes each) + CodeView data (~24 bytes) + Embedded PDB data (compressed pdbBlob + 8 header)
            // CodeView entry: signature (4) + guid (16) + age (4) + path (variable, ~12 for "assembly.pdb\0")
            const int debugDirEntrySize = 28;
            int codeViewDataSize = 4 + 16 + 4 + "assembly.pdb".Length + 1; // signature + guid + age + path + null
            int embeddedPdbHeaderSize = 8; // MPDB signature (4) + uncompressed size (4)
            // The embedded PDB is compressed, estimate conservatively as same size
            int embeddedPdbDataSize = embeddedPdbHeaderSize + pdbBlob.Count;

            debugDataSize = (2 * debugDirEntrySize) + codeViewDataSize + embeddedPdbDataSize;

            return debugDirectoryBuilder;
        }

        private void BuildPdbMetadata()
        {
            // Add documents and sequence points to the PDB metadata builder
            foreach (var entity in _entityRegistry.GetSeenEntities(TableIndex.MethodDef))
            {
                if (entity is not EntityRegistry.MethodDefinitionEntity method)
                {
                    continue;
                }

                var debugInfo = method.DebugInfo;
                if (debugInfo.SequencePoints.Count == 0)
                {
                    // Add empty debug info entry for methods without sequence points
                    _pdbBuilder.AddMethodDebugInformation(default, default);
                    continue;
                }

                // Get or create document handle
                DocumentHandle documentHandle = default;
                if (debugInfo.DocumentPath is not null)
                {
                    if (!_documentHandles.TryGetValue(debugInfo.DocumentPath, out documentHandle))
                    {
                        var nameHandle = _pdbBuilder.GetOrAddDocumentName(debugInfo.DocumentPath);
                        var languageGuidHandle = _currentLanguageGuid != Guid.Empty
                            ? _pdbBuilder.GetOrAddGuid(_currentLanguageGuid)
                            : default;
                        documentHandle = _pdbBuilder.AddDocument(
                            nameHandle,
                            default, // hash algorithm
                            default, // hash
                            languageGuidHandle);
                        _documentHandles[debugInfo.DocumentPath] = documentHandle;
                    }
                }

                // Encode sequence points
                var sequencePointsBlob = EncodeSequencePoints(debugInfo.SequencePoints);
                var sequencePointsBlobHandle = _pdbBuilder.GetOrAddBlob(sequencePointsBlob);

                _pdbBuilder.AddMethodDebugInformation(documentHandle, sequencePointsBlobHandle);
            }
        }

        private static BlobBuilder EncodeSequencePoints(List<EntityRegistry.SequencePoint> sequencePoints)
        {
            var builder = new BlobBuilder();

            if (sequencePoints.Count == 0)
            {
                return builder;
            }

            // LocalSignature (not used here, write 0)
            builder.WriteCompressedInteger(0);

            int previousOffset = 0;
            int previousStartLine = 0;
            int previousStartColumn = 0;

            foreach (var sp in sequencePoints)
            {
                // IL offset delta
                int offsetDelta = sp.ILOffset - previousOffset;
                builder.WriteCompressedInteger(offsetDelta);
                previousOffset = sp.ILOffset;

                if (sp.IsHidden)
                {
                    // Hidden sequence point: delta lines = 0, delta columns = 0
                    builder.WriteCompressedInteger(0);
                    builder.WriteCompressedInteger(0);
                }
                else
                {
                    // Delta lines
                    int deltaLines = sp.EndLine - sp.StartLine;
                    builder.WriteCompressedInteger(deltaLines);

                    // Delta columns
                    int deltaColumns = sp.EndColumn - sp.StartColumn;
                    if (deltaLines == 0)
                    {
                        builder.WriteCompressedInteger(deltaColumns);
                    }
                    else
                    {
                        builder.WriteCompressedSignedInteger(deltaColumns);
                    }

                    // Start line delta (signed)
                    if (previousStartLine == 0)
                    {
                        builder.WriteCompressedInteger(sp.StartLine);
                    }
                    else
                    {
                        builder.WriteCompressedSignedInteger(sp.StartLine - previousStartLine);
                    }

                    // Start column delta (signed)
                    if (previousStartColumn == 0)
                    {
                        builder.WriteCompressedInteger(sp.StartColumn);
                    }
                    else
                    {
                        builder.WriteCompressedSignedInteger(sp.StartColumn - previousStartColumn);
                    }

                    previousStartLine = sp.StartLine;
                    previousStartColumn = sp.StartColumn;
                }
            }

            return builder;
        }

        private static bool IsRecoverableError(string diagnosticId)
        {
            // Method body and signature diagnostics are recoverable - we emit the assembly but report the error.
            // This matches native ilasm behavior where errors during method/field emission don't prevent
            // the assembly from being written when the /ERR (OnErrGo) flag is set.
            return diagnosticId is DiagnosticIds.ByteArrayTooShort
                or DiagnosticIds.ArgumentNotFound
                or DiagnosticIds.LocalNotFound
                or DiagnosticIds.LabelNotFound
                or DiagnosticIds.GenericParameterIndexOutOfRange
                or DiagnosticIds.ParameterIndexOutOfRange
                or DiagnosticIds.GenericParameterNotFound
                or DiagnosticIds.UnknownGenericParameter
                or DiagnosticIds.MissingInstanceCallConv;
        }

        public GrammarResult Visit(IParseTree tree) => tree.Accept(this);

        GrammarResult ICILVisitor<GrammarResult>.VisitAlignment(CILParser.AlignmentContext context) => VisitAlignment(context);
        public GrammarResult.Literal<int> VisitAlignment(CILParser.AlignmentContext context)
        {
            return VisitInt32(context.int32());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitAsmAttr(CILParser.AsmAttrContext context) => VisitAsmAttr(context);
        public GrammarResult.Literal<AssemblyFlags> VisitAsmAttr(CILParser.AsmAttrContext context)
            => new(context.asmAttrAny().Select(VisitAsmAttrAny).Aggregate((AssemblyFlags)0, (lhs, rhs) => lhs | rhs));
        GrammarResult ICILVisitor<GrammarResult>.VisitAsmAttrAny(CILParser.AsmAttrAnyContext context) => VisitAsmAttrAny(context);
        public GrammarResult.Flag<AssemblyFlags> VisitAsmAttrAny(CILParser.AsmAttrAnyContext context)
        {
            return context.GetText() switch
            {
                "retargetable" => new(AssemblyFlags.Retargetable),
                "windowsruntime" => new(AssemblyFlags.WindowsRuntime),
                "noplatform" => new(AssemblyFlags.NoPlatform),
                "legacy library" => new(0),
                "cil" => new(GetFlagForArch(ProcessorArchitecture.MSIL), AssemblyFlags.ArchitectureMask),
                "x86" => new(GetFlagForArch(ProcessorArchitecture.X86), AssemblyFlags.ArchitectureMask),
                "amd64" => new(GetFlagForArch(ProcessorArchitecture.Amd64), AssemblyFlags.ArchitectureMask),
                "arm" => new(GetFlagForArch(ProcessorArchitecture.Arm), AssemblyFlags.ArchitectureMask),
                "arm64" => new(GetFlagForArch((ProcessorArchitecture)6), AssemblyFlags.ArchitectureMask),
                _ => throw new UnreachableException()
            };
        }

        private static AssemblyFlags GetFlagForArch(ProcessorArchitecture arch)
        {
            return (AssemblyFlags)((int)arch << 4);
        }

        private static (ProcessorArchitecture, AssemblyFlags) GetArchAndFlags(AssemblyFlags flags)
        {
            var arch = (ProcessorArchitecture)(((int)flags & 0xF0) >> 4);
            var newFlags = flags & ~((AssemblyFlags)((int)arch << 4));
            return (arch, newFlags);
        }

        private EntityRegistry.AssemblyOrRefEntity? _currentAssemblyOrRef;
        public GrammarResult VisitAsmOrRefDecl(CILParser.AsmOrRefDeclContext context)
        {
            Debug.Assert(_currentAssemblyOrRef is not null);

            if (context.customAttrDecl() is { } attr)
            {
                var customAttr = VisitCustomAttrDecl(attr).Value;
                customAttr?.Owner = _currentAssemblyOrRef;
                return GrammarResult.SentinelValue.Result;
            }

            string decl = context.GetChild(0).GetText();
            if (decl == ".publicKey")
            {
                BlobBuilder blob = new();
                blob.WriteBytes(VisitBytes(context.bytes()).Value);
                _currentAssemblyOrRef!.PublicKeyOrToken = blob;
            }
            else if (decl == ".ver")
            {
                var versionComponents = context.intOrWildcard();
                _currentAssemblyOrRef!.Version = new Version(
                    VisitIntOrWildcard(versionComponents[0]).Value ?? 0,
                    VisitIntOrWildcard(versionComponents[1]).Value ?? 0,
                    VisitIntOrWildcard(versionComponents[2]).Value ?? 0,
                    VisitIntOrWildcard(versionComponents[3]).Value ?? 0);
            }
            else if (decl == ".locale")
            {
                _currentAssemblyOrRef!.Culture = context.compQstring() is { } compQstring
                    ? VisitCompQstring(compQstring).Value
                    : Encoding.Unicode.GetString([.. VisitBytes(context.bytes()).Value]);
            }
            return GrammarResult.SentinelValue.Result;
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitAssemblyBlock(CILParser.AssemblyBlockContext context) => VisitAssemblyBlock(context);
        public GrammarResult VisitAssemblyBlock(CILParser.AssemblyBlockContext context)
        {
            // Use command-line override if specified, otherwise use the name from the .assembly directive
            string assemblyName = _options.AssemblyName ?? VisitDottedName(context.dottedName()).Value;
            _entityRegistry.Assembly ??= new EntityRegistry.AssemblyEntity(assemblyName);
            var attr = VisitAsmAttr(context.asmAttr()).Value;
            (_entityRegistry.Assembly.ProcessorArchitecture, _entityRegistry.Assembly.Flags) = GetArchAndFlags(attr);
            foreach (var decl in context.assemblyDecls().assemblyDecl())
            {
                VisitAssemblyDecl(decl);
            }

            // Apply command-line key file override (overrides .publickey directive)
            if (_options.KeyFile is not null)
            {
                ApplyKeyFile(_options.KeyFile);
            }

            // Apply DebuggableAttribute for --debug option
            if (_options.Debug || _options.DebugMode is not null)
            {
                ApplyDebuggableAttribute();
            }

            return GrammarResult.SentinelValue.Result;
        }

        /// <summary>
        /// Add DebuggableAttribute to the assembly based on debug options.
        /// - /DEBUG: 0x101 = Default | DisableOptimizations
        /// - /DEBUG=OPT: 0x03 = Default | IgnoreSymbolStoreSequencePoints
        /// - /DEBUG=IMPL: 0x103 = Default | DisableOptimizations | EnableEditAndContinue
        /// </summary>
        private void ApplyDebuggableAttribute()
        {
            if (_entityRegistry.Assembly is null)
            {
                return;
            }

            // DebuggingModes enum values from System.Diagnostics.DebuggableAttribute:
            // None = 0x00, Default = 0x01, IgnoreSymbolStoreSequencePoints = 0x02,
            // EnableEditAndContinue = 0x04, DisableOptimizations = 0x100
            const int DebuggingModesDefault = 0x101;  // Default | DisableOptimizations
            const int DebuggingModesOpt = 0x03;       // Default | IgnoreSymbolStoreSequencePoints
            const int DebuggingModesImpl = 0x103;     // Default | DisableOptimizations | EnableEditAndContinue

            int debuggingModes = _options.DebugMode switch
            {
                DebugMode.Opt => DebuggingModesOpt,
                DebugMode.Impl => DebuggingModesImpl,
                _ => DebuggingModesDefault
            };

            // Get reference to core library
            var coreAsmRef = _entityRegistry.GetCoreLibAssemblyReference();

            // Create reference to System.Diagnostics.DebuggableAttribute
            var debuggableAttrType = _entityRegistry.GetOrCreateTypeReference(
                coreAsmRef,
                new TypeName(null, "System.Diagnostics.DebuggableAttribute"));

            // Create reference to nested type DebuggingModes
            var debuggingModesType = _entityRegistry.GetOrCreateTypeReference(
                debuggableAttrType,
                new TypeName(null, "DebuggingModes"));

            // Create constructor signature: .ctor(DebuggingModes)
            BlobBuilder ctorSig = new();
            var sigEncoder = new BlobEncoder(ctorSig);
            sigEncoder.MethodSignature(SignatureCallingConvention.Default, 0, isInstanceMethod: true)
                .Parameters(1,
                    returnType => returnType.Void(),
                    parameters => parameters.AddParameter().Type().Type(debuggingModesType.Handle, isValueType: true));

            var ctor = _entityRegistry.CreateLazilyRecordedMemberReference(debuggableAttrType, ".ctor", ctorSig);

            // Create custom attribute blob: prolog (0x0001) + int32 value + named args count (0x0000)
            BlobBuilder attrValue = new();
            attrValue.WriteUInt16(0x0001); // Prolog
            attrValue.WriteInt32(debuggingModes); // DebuggingModes value
            attrValue.WriteUInt16(0x0000); // No named arguments

            // Create and attach the custom attribute
            var customAttr = _entityRegistry.CreateCustomAttribute(ctor, attrValue);
            customAttr.Owner = _entityRegistry.Assembly;
        }

        private void ApplyKeyFile(string keyFilePath)
        {
            if (_entityRegistry.Assembly is null)
            {
                return;
            }

            try
            {
                byte[] keyBytes = File.ReadAllBytes(keyFilePath);
                BlobBuilder blob = new();
                blob.WriteBytes(keyBytes);
                _entityRegistry.Assembly.PublicKeyOrToken = blob;
                _entityRegistry.Assembly.Flags |= AssemblyFlags.PublicKey;
            }
            catch (Exception ex)
            {
                // Create a location pointing to the first document (if available)
                var firstDoc = _documents.Values.FirstOrDefault();
                var location = firstDoc is not null
                    ? new Location(new SourceSpan(0, 0), firstDoc)
                    : new Location(new SourceSpan(0, 0), new SourceText(string.Empty, keyFilePath));
                _diagnostics.Add(new Diagnostic(DiagnosticIds.KeyFileError, DiagnosticSeverity.Error, $"Failed to read key file '{keyFilePath}': {ex.Message}", location));
            }
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitAssemblyDecl(CILParser.AssemblyDeclContext context) => VisitAssemblyDecl(context);
        public GrammarResult VisitAssemblyDecl(CILParser.AssemblyDeclContext context)
        {
            if (context.secDecl() is { } secDecl)
            {
                var declarativeSecurity = VisitSecDecl(secDecl);
                if (declarativeSecurity.Value is { } sec)
                {
                    sec.Parent = _entityRegistry.Assembly;
                }
            }
            else if (context.int32() is { } hashAlg)
            {
                _entityRegistry.Assembly!.HashAlgorithm = (AssemblyHashAlgorithm)VisitInt32(hashAlg).Value;
            }
            else if (context.asmOrRefDecl() is { } asmOrRef)
            {
                _currentAssemblyOrRef = _entityRegistry.Assembly;
                VisitAsmOrRefDecl(asmOrRef);
                _currentAssemblyOrRef = null;
            }
            return GrammarResult.SentinelValue.Result;
        }
        public GrammarResult VisitAssemblyDecls(CILParser.AssemblyDeclsContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        public GrammarResult VisitAssemblyRefDecl(CILParser.AssemblyRefDeclContext context)
        {
            if (context.asmOrRefDecl() is { } asmOrRef)
            {
                VisitAsmOrRefDecl(asmOrRef);
            }
            string decl = context.GetChild(0).GetText();
            if (decl == ".hash")
            {
                var blob = new BlobBuilder();
                blob.WriteBytes(VisitBytes(context.bytes()).Value);
                ((EntityRegistry.AssemblyReferenceEntity)_currentAssemblyOrRef!).Hash = blob;
            }
            if (decl == ".publickeytoken")
            {
                var blob = new BlobBuilder();
                blob.WriteBytes(VisitBytes(context.bytes()).Value);
                _currentAssemblyOrRef!.PublicKeyOrToken = blob;
            }
            return GrammarResult.SentinelValue.Result;
        }
        public GrammarResult VisitAssemblyRefDecls(CILParser.AssemblyRefDeclsContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        GrammarResult ICILVisitor<GrammarResult>.VisitAssemblyRefHead(CILParser.AssemblyRefHeadContext context) => VisitAssemblyRefHead(context);
        public GrammarResult.Literal<EntityRegistry.AssemblyReferenceEntity> VisitAssemblyRefHead(CILParser.AssemblyRefHeadContext context)
        {
            var (arch, flags) = GetArchAndFlags(VisitAsmAttr(context.asmAttr()).Value);
            var dottedNames = context.dottedName();
            string name = VisitDottedName(dottedNames[0]).Value;
            string alias = name;
            if (dottedNames.Length > 1)
            {
                alias = VisitDottedName(dottedNames[1]).Value;
            }
            return new(_entityRegistry.GetOrCreateAssemblyReference(alias, asmref =>
            {
                asmref.Name = name;
                asmref.Flags = flags;
                asmref.ProcessorArchitecture = arch;
            }));
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitAtOpt(CILParser.AtOptContext context) => VisitAtOpt(context);
        public static GrammarResult.Literal<string?> VisitAtOpt(CILParser.AtOptContext context) => context.id() is {} id ? new(VisitId(id).Value) : new(null);

        GrammarResult ICILVisitor<GrammarResult>.VisitBoolSeq(CILParser.BoolSeqContext context) => VisitBoolSeq(context);
        public static GrammarResult.FormattedBlob VisitBoolSeq(CILParser.BoolSeqContext context)
        {
            var builder = ImmutableArray.CreateBuilder<bool>();

            foreach (var item in context.truefalse())
            {
                builder.AddRange(VisitTruefalse(item).Value);
            }

            return new(builder.ToImmutable().SerializeSequence());
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitBound(CILParser.BoundContext context) => VisitBound(context);
        public GrammarResult.Literal<(int? Lower, int? Upper)> VisitBound(CILParser.BoundContext context)
        {
            bool hasEllipsis = context.ELLIPSIS() is not null;
            if (context.ChildCount == 0 || (context.ChildCount == 1 && hasEllipsis))
            {
                return new((null, null));
            }

            var indicies = context.int32();

            int firstValue = VisitInt32(indicies[0]).Value;

            return (indicies.Length, hasEllipsis) switch
            {
                (1, false) => new((0, firstValue)),
                (1, true) => new((firstValue, null)),
                (2, false) => new((firstValue, VisitInt32(indicies[1]).Value - firstValue + 1)),
                _ => throw new UnreachableException()
            };
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitBounds(CILParser.BoundsContext context) => VisitBounds(context);
        public GrammarResult.Sequence<(int? Lower, int? Upper)> VisitBounds(CILParser.BoundsContext context)
        {
            return new(context.bound().Select(bound => VisitBound(bound).Value).ToImmutableArray());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitBytes(CILParser.BytesContext context) => VisitBytes(context);
        public static GrammarResult.Sequence<byte> VisitBytes(CILParser.BytesContext context)
        {
            var builder = ImmutableArray.CreateBuilder<byte>();

            foreach (var item in context.hexbytes())
            {
                builder.AddRange(VisitHexbytes(item).Value);
            }

            return new(builder.ToImmutable());
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitCallConv(CILParser.CallConvContext context) => VisitCallConv(context);
        public GrammarResult.Literal<byte> VisitCallConv(CILParser.CallConvContext context)
        {
            if (context.callKind() is CILParser.CallKindContext callKind)
            {
                return new((byte)VisitCallKind(callKind).Value);
            }
            else if (context.int32() is CILParser.Int32Context int32)
            {
                return new((byte)VisitInt32(int32).Value);
            }
            else if (context.INSTANCE() is not null)
            {
                return new((byte)(VisitCallConv(context.callConv()).Value | (byte)SignatureAttributes.Instance));
            }
            else if (context.EXPLICIT() is not null)
            {
                return new((byte)(VisitCallConv(context.callConv()).Value | (byte)SignatureAttributes.ExplicitThis));
            }
            return new(0);
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitCallKind(CILParser.CallKindContext context) => VisitCallKind(context);
        public GrammarResult.Literal<SignatureCallingConvention> VisitCallKind(CILParser.CallKindContext context)
        {
            // callKind can be empty (/* EMPTY */) - return Default in that case
            if (context.ChildCount == 0)
            {
                return new(SignatureCallingConvention.Default);
            }
            int childType = context.GetChild<ITerminalNode>(context.ChildCount - 1).Symbol.Type;
            return new(childType switch
            {
                CILParser.DEFAULT => SignatureCallingConvention.Default,
                CILParser.VARARG => SignatureCallingConvention.VarArgs,
                CILParser.CDECL => SignatureCallingConvention.CDecl,
                CILParser.STDCALL => SignatureCallingConvention.StdCall,
                CILParser.THISCALL => SignatureCallingConvention.ThisCall,
                CILParser.FASTCALL => SignatureCallingConvention.FastCall,
                CILParser.UNMANAGED => SignatureCallingConvention.Unmanaged,
                _ => throw new UnreachableException()
            });
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitCatchClause(CILParser.CatchClauseContext context) => VisitCatchClause(context);
        public GrammarResult.Literal<EntityRegistry.TypeEntity> VisitCatchClause(CILParser.CatchClauseContext context) => VisitTypeSpec(context.typeSpec());

        GrammarResult ICILVisitor<GrammarResult>.VisitCaValue(CILParser.CaValueContext context) => VisitCaValue(context);
        public GrammarResult.FormattedBlob VisitCaValue(CILParser.CaValueContext context)
        {
            BlobBuilder blob = new();
            if (context.truefalse() is CILParser.TruefalseContext truefalse)
            {
                blob.WriteByte((byte)SerializationTypeCode.Boolean);
                blob.WriteBoolean(VisitTruefalse(truefalse).Value);
            }
            else if (context.compQstring() is CILParser.CompQstringContext str)
            {
                blob.WriteUTF8(VisitCompQstring(str).Value);
                blob.WriteByte(0);
            }
            else if (context.className() is CILParser.ClassNameContext className)
            {
                var name = VisitClassName(className).Value;
                blob.WriteByte((byte)SerializationTypeCode.Enum);
                blob.WriteUTF8((name as EntityRegistry.IHasReflectionNotation)?.ReflectionNotation ?? "");
                blob.WriteByte(0);
                byte size = 4;
                if (context.INT8() is not null)
                {
                    size = 1;
                }
                else if (context.INT16() is not null)
                {
                    size = 2;
                }
                blob.WriteByte(size);
                blob.WriteInt32(VisitInt32(context.int32()).Value);
            }
            else
            {
                blob.WriteByte((byte)SerializationTypeCode.Int32);
                blob.WriteInt32(VisitInt32(context.int32()).Value);
            }
            return new(blob);
        }

        public GrammarResult VisitChildren(IRuleNode node)
        {
            for (int i = 0; i < node.ChildCount; i++)
            {
                node.GetChild(i).Accept(this);
            }
            return GrammarResult.SentinelValue.Result;
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitClassAttr(CILParser.ClassAttrContext context) => VisitClassAttr(context);

        public GrammarResult.Literal<(GrammarResult.Flag<TypeAttributes> Attribute, EntityRegistry.WellKnownBaseType? FallbackBase, bool RequireSealed)> VisitClassAttr(CILParser.ClassAttrContext context)
        {
            if (context.int32() is CILParser.Int32Context int32)
            {
                int value = VisitInt32(int32).Value;
                // COMPAT: The VALUE and ENUM keywords use sentinel values to pass through the fallback base type
                // in ILASM. These sentinel values can be provided through the "pass the value of the flag" feature,
                // so we detect those old flags here and provide the correct fallback type.
                bool requireSealed = false;
                EntityRegistry.WellKnownBaseType? fallbackBase = null;
                if ((value & 0x80000000) != 0)
                {
                    requireSealed = true;
                    fallbackBase = EntityRegistry.WellKnownBaseType.System_ValueType;
                }
                if ((value & 0x40000000) != 0)
                {
                    fallbackBase = EntityRegistry.WellKnownBaseType.System_Enum;
                }
                // Mask off the sentinel bits
                value &= unchecked((int)~0xC0000000);
                // COMPAT: When explicit flags are provided they always supercede previously set flags
                // (other than the sentinel values)
                return new((new((TypeAttributes)value, ShouldAppend: false), fallbackBase, requireSealed));
            }

            if (context.ENUM() is not null)
            {
                // COMPAT: ilasm implies the Sealed flag when using the 'value' keyword in a type declaration
                // even when the 'enum' keyword is used.
                return new((new(context.VALUE() is not null ? TypeAttributes.Sealed : 0), EntityRegistry.WellKnownBaseType.System_Enum, false));
            }
            else if (context.VALUE() is not null)
            {
                // COMPAT: ilasm implies the Sealed flag when using the 'value' keyword in a type declaration
                return new((new(TypeAttributes.Sealed), EntityRegistry.WellKnownBaseType.System_ValueType, true));
            }
            else if (context.EXPLICIT() is not null)
            {
                return new((new(TypeAttributes.ExplicitLayout), null, false));
            }
            else if (context.INTERFACE() is not null)
            {
                // COMPAT: interface implies abstract
                return new((new(TypeAttributes.Interface | TypeAttributes.Abstract), null, false));
            }

            switch (context.GetText())
            {
                case "private":
                    return new((new(TypeAttributes.NotPublic), null, false));
                case "ansi":
                    return new((new(TypeAttributes.AnsiClass), null, false));
                case "autochar":
                    return new((new(TypeAttributes.AutoClass), null, false));
                case "auto":
                    return new((new(TypeAttributes.AutoLayout), null, false));
                case "sequential":
                    return new((new(TypeAttributes.SequentialLayout), null, false));
                case "extended":
                    return new((new(TypeAttributes.ExtendedLayout), null, false));
                default:
                    return new((new((TypeAttributes)Enum.Parse(typeof(TypeAttributes), context.GetText(), true)), null, false));
            }
        }

        private sealed class CurrentMethodContext
        {
            public CurrentMethodContext(EntityRegistry.MethodDefinitionEntity definition)
            {
                Definition = definition;
            }

            public EntityRegistry.MethodDefinitionEntity Definition { get; }

            public Dictionary<string, LabelHandle> Labels { get; } = new();

            public HashSet<string> DeclaredLabels { get; } = new();

            public Dictionary<string, ParserRuleContext> UndefinedLabelReferences { get; } = new();

            public Dictionary<string, int> ArgumentNames { get; } = new();

            public List<Dictionary<string, int>> LocalsScopes { get; } = new();

            public List<SignatureArg> AllLocals { get; } = new();
        }

        private CurrentMethodContext? _currentMethod;

        public GrammarResult VisitClassDecl(CILParser.ClassDeclContext context)
        {
            if (context.classHead() is CILParser.ClassHeadContext classHead)
            {
                _currentTypeDefinition.Push(VisitClassHead(classHead).Value);
                VisitClassDecls(context.classDecls());
                _currentTypeDefinition.Pop();
            }
            else if (context.methodHead() is CILParser.MethodHeadContext methodHead)
            {
                _currentMethod = new(VisitMethodHead(methodHead).Value);
                VisitMethodDecls(context.methodDecls());
                // Validate that all referenced labels were declared
                ValidateLabelReferences();
                _currentMethod = null;
            }
            else if (context.secDecl() is {} secDecl)
            {
                var declarativeSecurity = VisitSecDecl(secDecl).Value;
                declarativeSecurity?.Parent = _currentTypeDefinition.PeekOrDefault();
            }
            else if (context.fieldDecl() is {} fieldDecl)
            {
                _ = VisitFieldDecl(fieldDecl);
            }
            else if (context.int32() is {} int32)
            {
                // .pack or .size
                string keyword = context.GetChild(0).GetText();
                int value = VisitInt32(int32).Value;
                var currentType = _currentTypeDefinition.PeekOrDefault();
                if (currentType is not null)
                {
                    if (keyword == ".pack")
                    {
                        currentType.PackingSize = value;
                    }
                    else if (keyword == ".size")
                    {
                        currentType.ClassSize = value;
                    }
                }
            }
            else if (context.propHead() is CILParser.PropHeadContext propHead)
            {
                var property = VisitPropHead(propHead).Value;
                var currentType = _currentTypeDefinition.PeekOrDefault();
                if (currentType is not null)
                {
                    currentType.Properties.Add(property);
                    var accessors = VisitPropDecls(context.propDecls()).Value;
                    foreach (var accessor in accessors)
                    {
                        property.Accessors.Add(accessor);
                    }
                }
            }
            else if (context.eventHead() is CILParser.EventHeadContext eventHead)
            {
                var evt = VisitEventHead(eventHead).Value;
                var currentType = _currentTypeDefinition.PeekOrDefault();
                if (currentType is not null)
                {
                    currentType.Events.Add(evt);
                    var accessors = VisitEventDecls(context.eventDecls()).Value;
                    foreach (var accessor in accessors)
                    {
                        evt.Accessors.Add(accessor);
                    }
                }
            }

            return GrammarResult.SentinelValue.Result;
        }
        public GrammarResult VisitClassDecls(CILParser.ClassDeclsContext context) => VisitChildren(context);


        GrammarResult ICILVisitor<GrammarResult>.VisitClassHead(CILParser.ClassHeadContext context) => VisitClassHead(context);
        public GrammarResult.Literal<EntityRegistry.TypeDefinitionEntity> VisitClassHead(CILParser.ClassHeadContext context)
        {
            string typeFullName = VisitDottedName(context.dottedName()).Value;
            int typeFullNameLastDot = typeFullName.LastIndexOf('.');
            string typeNS;
            if (_currentTypeDefinition.Count != 0)
            {
                if (typeFullNameLastDot == -1)
                {
                    typeNS = string.Empty;
                }
                else
                {
                    typeNS = typeFullName.Substring(0, typeFullNameLastDot);
                }
            }
            else
            {
                if (typeFullNameLastDot == -1)
                {
                    typeNS = _currentNamespace.PeekOrDefault() ?? string.Empty;
                }
                else
                {
                    typeNS = $"{_currentNamespace.PeekOrDefault()}{typeFullName.Substring(0, typeFullNameLastDot)}";
                }
            }

            bool isNewType = false;

            var typeDefinition = _entityRegistry.GetOrCreateTypeDefinition(
                _currentTypeDefinition.PeekOrDefault(),
                typeNS,
                typeFullNameLastDot != -1
                    ? typeFullName.Substring(typeFullNameLastDot)
                    : typeFullName,
                (newTypeDef) =>
                {
                    isNewType = true;
                    EntityRegistry.WellKnownBaseType? fallbackBase = _options.NoAutoInherit ? null : EntityRegistry.WellKnownBaseType.System_Object;
                    bool requireSealed = false;
                    var classAttrs = context.classAttr();
                    newTypeDef.Attributes = classAttrs.Select(VisitClassAttr).Aggregate(
                        (TypeAttributes)0,
                        (acc, result) =>
                        {
                            var (attribute, implicitBase, attrRequireSealed) = result.Value;
                            if (implicitBase is not null)
                            {
                                // COMPAT: Any base type specified by an attribute is ignored if
                                // the user specified an explicit base type in an 'extends' clause.
                                fallbackBase = implicitBase;
                            }
                            // COMPAT: When a flags value is specified as an integer, it overrides
                            // all of the provided flags, including any compat sentinel flags that will require
                            // the sealed modifier to be provided.
                            if (!attribute.ShouldAppend)
                            {
                                requireSealed = attrRequireSealed;
                                return attribute.Value;
                            }
                            requireSealed |= attrRequireSealed;
                            // Note: We check attribute.Value != 0 because HasFlag(0) always returns true,
                            // but AutoLayout (0) and AnsiClass (0) should not clear other flags.
                            if (attribute.Value != 0 && TypeAttributes.LayoutMask.HasFlag(attribute.Value))
                            {
                                return (acc & ~TypeAttributes.LayoutMask) | attribute.Value;
                            }
                            if (attribute.Value != 0 && TypeAttributes.StringFormatMask.HasFlag(attribute.Value))
                            {
                                return (acc & ~TypeAttributes.StringFormatMask) | attribute.Value;
                            }
                            if (TypeAttributes.VisibilityMask.HasFlag(attribute.Value))
                            {
                                return (acc & ~TypeAttributes.VisibilityMask) | attribute.Value;
                            }
                            if (attribute.Value == TypeAttributes.RTSpecialName)
                            {
                                // COMPAT: ILASM ignores the rtspecialname directive on a type.
                                return acc;
                            }
                            if ((attribute.Value & TypeAttributes.Interface) != 0)
                            {
                                // COMPAT: interface implies abstract
                                return acc | TypeAttributes.Interface | TypeAttributes.Abstract;
                            }

                            return acc | attribute.Value;
                        });


                    for (int i = 0; i < VisitTyparsClause(context.typarsClause()).Value.Length; i++)
                    {
                        EntityRegistry.GenericParameterEntity? param = VisitTyparsClause(context.typarsClause()).Value[i];
                        param.Owner = newTypeDef;
                        param.Index = i;
                        newTypeDef.GenericParameters.Add(param);
                        foreach (var constraint in param.Constraints)
                        {
                            constraint.Owner = param;
                            newTypeDef.GenericParameterConstraints.Add(constraint);
                        }
                    }

                    // Temporarily push the new type as the current type definition so we can resolve type parameters
                    // that are used in the base type and interface types.
                    _currentTypeDefinition.Push(newTypeDef);

                    if (context.extendsClause() is CILParser.ExtendsClauseContext extends)
                    {
                        newTypeDef.BaseType = VisitExtendsClause(context.extendsClause()).Value;
                    }

                    if (context.implClause() is CILParser.ImplClauseContext impl)
                    {
                        newTypeDef.InterfaceImplementations.AddRange(VisitImplClause(context.implClause()).Value);
                    }

                    _currentTypeDefinition.Pop();

                    newTypeDef.BaseType ??= _entityRegistry.ResolveImplicitBaseType(fallbackBase);

                    // When the user has provided a type definition for a type that directly inherits
                    // System.ValueType but has not sealed it, emit a warning and add the 'sealed' modifier.
                    if (!newTypeDef.Attributes.HasFlag(TypeAttributes.Sealed) &&
                        (requireSealed // COMPAT: when both the sentinel values for 'value' and 'enum' are explicitly
                                       // specified, the sealed modifier is required even though
                                       // the base type isn't System.ValueType.
                        || _entityRegistry.SystemValueTypeType.Equals(newTypeDef.BaseType)))
                    {
                        _diagnostics.Add(
                            new Diagnostic(
                                DiagnosticIds.UnsealedValueType,
                                DiagnosticSeverity.Error,
                                string.Format(DiagnosticMessageTemplates.UnsealedValueType, newTypeDef.Name),
                                Location.From(context.dottedName().Stop, _documents)));
                        newTypeDef.Attributes |= TypeAttributes.Sealed;
                    }
                });

            if (!isNewType)
            {
                // COMPAT: Still visit some of the clauses to ensure the provided types are still imported,
                // even if unused.
                _ = context.extendsClause()?.Accept(this);
                _ = context.typarsClause().Accept(this);
            }

            return new(typeDefinition);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitClassName(CILParser.ClassNameContext context) => VisitClassName(context);
        public GrammarResult.Literal<EntityRegistry.TypeEntity> VisitClassName(CILParser.ClassNameContext context)
        {
            if (context.THIS() is not null)
            {
                if (_currentTypeDefinition.Count == 0)
                {
                    ReportError(DiagnosticIds.ThisOutsideClass, DiagnosticMessageTemplates.ThisOutsideClass, context);
                    return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle)));
                }
                var thisType = _currentTypeDefinition.Peek();
                return new(thisType);
            }
            else if (context.BASE() is not null)
            {
                if (_currentTypeDefinition.Count == 0)
                {
                    ReportError(DiagnosticIds.BaseOutsideClass, DiagnosticMessageTemplates.BaseOutsideClass, context);
                    return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle)));
                }
                var baseType = _currentTypeDefinition.Peek().BaseType;
                if (baseType is null)
                {
                    ReportError(DiagnosticIds.NoBaseType, DiagnosticMessageTemplates.NoBaseType, context);
                    return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle)));
                }
                return new(baseType);
            }
            else if (context.NESTER() is not null)
            {
                if (_currentTypeDefinition.Count < 2)
                {
                    ReportError(DiagnosticIds.NesterOutsideNestedClass, DiagnosticMessageTemplates.NesterOutsideNestedClass, context);
                    return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle)));
                }
                var nesterType = _currentTypeDefinition.Peek().ContainingType!;
                return new(nesterType);
            }
            else if (context.slashedName() is CILParser.SlashedNameContext slashedName)
            {
                EntityRegistry.EntityBase? resolutionContext = null;
                if (context.dottedName() is CILParser.DottedNameContext dottedAssemblyOrModuleName)
                {
                    if (context.MODULE() is not null)
                    {
                        string moduleName = VisitDottedName(dottedAssemblyOrModuleName).Value;
                        resolutionContext = _entityRegistry.FindModuleReference(moduleName);
                        if (resolutionContext is null)
                        {
                            ReportError(DiagnosticIds.ModuleNotFound, string.Format(DiagnosticMessageTemplates.ModuleNotFound, moduleName), context);
                            return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle)));
                        }
                    }
                    else
                    {
                        resolutionContext = _entityRegistry.GetOrCreateAssemblyReference(VisitDottedName(dottedAssemblyOrModuleName).Value, newRef => { });
                    }
                }
                else if (context.mdtoken() is CILParser.MdtokenContext typeRefScope)
                {
                    resolutionContext = VisitMdtoken(typeRefScope).Value;
                }
                else if (context.PTR() is not null)
                {
                    resolutionContext = new EntityRegistry.FakeTypeEntity(default(ModuleDefinitionHandle));
                }

                if (resolutionContext is not null)
                {
                    EntityRegistry.TypeReferenceEntity typeRef = _entityRegistry.GetOrCreateTypeReference(resolutionContext, VisitSlashedName(slashedName).Value);
                    return new(typeRef);
                }

                Debug.Assert(resolutionContext is null);

                return new(ResolveTypeDef());

                // Resolve typedef references
                EntityRegistry.TypeEntity ResolveTypeDef()
                {
                    TypeName typeName = VisitSlashedName(slashedName).Value;
                    if (typeName.ContainingTypeName is null)
                    {
                        // Check for typedef.
                        var typedefResult = TryResolveTypedefAsType(typeName.DottedName);
                        if (typedefResult is not null)
                        {
                            return typedefResult;
                        }
                    }
                    Stack<TypeName> containingTypes = new();
                    for (TypeName? containingType = typeName; containingType is not null; containingType = containingType.ContainingTypeName)
                    {
                        containingTypes.Push(containingType);
                    }
                    EntityRegistry.TypeDefinitionEntity? typeDef = null;
                    while (containingTypes.Count != 0)
                    {
                        TypeName containingType = containingTypes.Pop();

                        (string ns, string name) = NameHelpers.SplitDottedNameToNamespaceAndName(containingType.DottedName);

                        typeDef = _entityRegistry.FindTypeDefinition(
                            typeDef,
                            ns,
                            name);

                        if (typeDef is null)
                        {
                            ReportError(DiagnosticIds.TypeNotFound, string.Format(DiagnosticMessageTemplates.TypeNotFound, containingType.DottedName), context);
                            return new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle));
                        }
                    }

                    return typeDef!;
                }
            }
            else if (context.mdtoken() is CILParser.MdtokenContext typeToken)
            {
                EntityRegistry.EntityBase resolvedToken = VisitMdtoken(typeToken).Value;

                if (resolvedToken is not EntityRegistry.TypeEntity type)
                {
                    return new(new EntityRegistry.FakeTypeEntity(resolvedToken.Handle));
                }
                return new(type);
            }

            throw new UnreachableException();
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitClassSeq(CILParser.ClassSeqContext context) => VisitClassSeq(context);
        public GrammarResult.FormattedBlob VisitClassSeq(CILParser.ClassSeqContext context)
        {
            // We're going to add all of the elements in the sequence as prefix blobs to this blob.
            BlobBuilder objSeqBlob = new(0);
            foreach (var item in context.classSeqElement())
            {
                objSeqBlob.LinkPrefix(VisitClassSeqElement(item).Value);
            }
            return new(objSeqBlob);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitClassSeqElement(CILParser.ClassSeqElementContext context) => VisitClassSeqElement(context);

        public GrammarResult.FormattedBlob VisitClassSeqElement(CILParser.ClassSeqElementContext context)
        {
            BlobBuilder blob = new();
            if (context.className() is CILParser.ClassNameContext className)
            {
                if (VisitClassName(className).Value is EntityRegistry.IHasReflectionNotation notation)
                {
                    blob.WriteSerializedString(notation.ReflectionNotation);
                }
                else
                {
                    blob.WriteSerializedString("");
                }
                return new(blob);
            }

            blob.WriteSerializedString(context.SQSTRING()?.Symbol.Text);
            return new(blob);
        }
        public GrammarResult VisitCompControl(CILParser.CompControlContext context)
        {
            // All compilation control directives that need special handling will be handled
            // directly in the token stream before parsing.
            // Any that reach here can be ignored.
            return GrammarResult.SentinelValue.Result;
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitCompQstring(CILParser.CompQstringContext context)
        {
            return VisitCompQstring(context);
        }

        private static GrammarResult.String VisitCompQstring(CILParser.CompQstringContext context)
        {
            StringBuilder builder = new();
            foreach (var item in context.QSTRING())
            {
                builder.Append(StringHelpers.ParseQuotedString(item.Symbol.Text));
            }
            return new(builder.ToString());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitCorflags(CILParser.CorflagsContext context) => VisitCorflags(context);
        public GrammarResult.Literal<int> VisitCorflags(CILParser.CorflagsContext context) => VisitInt32(context.int32());

        GrammarResult ICILVisitor<GrammarResult>.VisitCustomAttrDecl(CILParser.CustomAttrDeclContext context) => VisitCustomAttrDecl(context);
        public GrammarResult.Literal<EntityRegistry.CustomAttributeEntity?> VisitCustomAttrDecl(CILParser.CustomAttrDeclContext context)
        {
            if (context.dottedName() is { } dottedName)
            {
                // This is a typedef reference for a custom attribute
                string alias = VisitDottedName(dottedName).Value;
                var resolved = TryResolveTypedefAsCustomAttribute(alias);
                if (resolved is not null)
                {
                    return new(_entityRegistry.CreateCustomAttribute(resolved.Value.Constructor, resolved.Value.Value));
                }
                // Typedef not found - could report diagnostic here
                return new(null);
            }
            if (context.customDescrWithOwner() is {} descrWithOwner)
            {
                // Visit the custom attribute descriptor to record it,
                // but don't return it as it will already have its owner recorded.
                _ = VisitCustomDescrWithOwner(descrWithOwner);
                return new(null);
            }
            if (context.customDescr() is {} descr)
            {
#nullable disable // Disable nullability to work around lack of variance.
                return VisitCustomDescr(descr);
#nullable restore
            }
            throw new UnreachableException();
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitCustomDescrInMethodBody(CILParser.CustomDescrInMethodBodyContext context) => VisitCustomDescrInMethodBody(context);
        public GrammarResult.Literal<EntityRegistry.CustomAttributeEntity?> VisitCustomDescrInMethodBody(CILParser.CustomDescrInMethodBodyContext context)
        {
            if (context.customDescrWithOwner() is {} descrWithOwner)
            {
                // Visit the custom attribute descriptor to record it,
                // but don't return it as it will already have its owner recorded.
                _ = VisitCustomDescrWithOwner(descrWithOwner);
                return new(null);
            }
            if (context.customDescr() is {} descr)
            {
#nullable disable // Disable nullability to work around lack of variance.
                return VisitCustomDescr(descr);
#nullable restore
            }
            throw new UnreachableException();
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitCustomBlobArgs(CILParser.CustomBlobArgsContext context) => VisitCustomBlobArgs(context);
        public GrammarResult.FormattedBlob VisitCustomBlobArgs(CILParser.CustomBlobArgsContext context)
        {
            BlobBuilder blob = new();
            foreach (var item in context.serInit())
            {
                VisitSerInit(item).Value.WriteContentTo(blob);
            }
            return new(blob);
        }

        private const int CustomAttributeBlobFormatVersion = 1;

        GrammarResult ICILVisitor<GrammarResult>.VisitCustomBlobDescr(CILParser.CustomBlobDescrContext context) => VisitCustomBlobDescr(context);
        public GrammarResult.FormattedBlob VisitCustomBlobDescr(CILParser.CustomBlobDescrContext context)
        {
            var blob = new BlobBuilder();
            blob.WriteInt32(CustomAttributeBlobFormatVersion);
            VisitCustomBlobArgs(context.customBlobArgs()).Value.WriteContentTo(blob);
            VisitCustomBlobNVPairs(context.customBlobNVPairs()).Value.WriteContentTo(blob);
            return new(blob);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitCustomBlobNVPairs(CILParser.CustomBlobNVPairsContext context) => VisitCustomBlobNVPairs(context);
        public GrammarResult.FormattedBlob VisitCustomBlobNVPairs(CILParser.CustomBlobNVPairsContext context)
        {
            var blob = new BlobBuilder();
            var fieldOrProps = context.fieldOrProp();
            var types = context.serializType();
            var names = context.dottedName();
            var values = context.serInit();

            blob.WriteInt16((short)fieldOrProps.Length);

            for (int i = 0; i < fieldOrProps.Length; i++)
            {
                var fieldOrProp = fieldOrProps[i].GetText() == "field" ? CustomAttributeNamedArgumentKind.Field : CustomAttributeNamedArgumentKind.Property;
                var type = VisitSerializType(types[i]).Value;
                var name = VisitDottedName(names[i]).Value;
                var value = VisitSerInit(values[i]).Value;
                blob.WriteByte((byte)fieldOrProp);
                type.WriteContentTo(blob);
                blob.WriteSerializedString(name);
                value.WriteContentTo(blob);
            }
            return new(blob);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitCustomDescr(CILParser.CustomDescrContext context) => VisitCustomDescr(context);
        public GrammarResult.Literal<EntityRegistry.CustomAttributeEntity> VisitCustomDescr(CILParser.CustomDescrContext context)
        {
            var ctor = VisitCustomType(context.customType()).Value;
            BlobBuilder value;
            if (context.customBlobDescr() is {} customBlobDescr)
            {
                value = VisitCustomBlobDescr(customBlobDescr).Value;
            }
            else if (context.bytes() is {} bytes)
            {
                value = new();
                value.WriteBytes(VisitBytes(bytes).Value);
            }
            else if (context.compQstring() is {} str)
            {
                value = new();
                value.WriteUTF8(VisitCompQstring(str).Value);
                // COMPAT: We treat this string as a string-reprensentation of a blob,
                // so we don't emit the null terminator.
            }
            else
            {
                throw new UnreachableException();
            }

            return new(_entityRegistry.CreateCustomAttribute(ctor, value));
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitCustomDescrWithOwner(CILParser.CustomDescrWithOwnerContext context) => VisitCustomDescrWithOwner(context);
        public GrammarResult.Literal<EntityRegistry.CustomAttributeEntity> VisitCustomDescrWithOwner(CILParser.CustomDescrWithOwnerContext context)
        {
            var ctor = VisitCustomType(context.customType()).Value;
            BlobBuilder value;
            if (context.customBlobDescr() is {} customBlobDescr)
            {
                value = VisitCustomBlobDescr(customBlobDescr).Value;
            }
            else if (context.bytes() is {} bytes)
            {
                value = new();
                value.WriteBytes(VisitBytes(bytes).Value);
            }
            else if (context.compQstring() is {} str)
            {
                value = new();
                value.WriteUTF8(VisitCompQstring(str).Value);
                // COMPAT: We treat this string as a string-reprensentation of a blob,
                // so we don't emit the null terminator.
            }
            else
            {
                throw new UnreachableException();
            }

            var attr = _entityRegistry.CreateCustomAttribute(ctor, value);

            attr.Owner = VisitOwnerType(context.ownerType()).Value;

            return new(attr);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitCustomType(CILParser.CustomTypeContext context) => VisitCustomType(context);
        public GrammarResult.Literal<EntityRegistry.EntityBase> VisitCustomType(CILParser.CustomTypeContext context) => VisitMethodRef(context.methodRef());

        public GrammarResult VisitDataDecl(CILParser.DataDeclContext context)
        {
            _ = VisitDdHead(context.ddHead());
            _ = VisitDdBody(context.ddBody());
            return GrammarResult.SentinelValue.Result;
        }
        public GrammarResult VisitDdBody(CILParser.DdBodyContext context)
        {
            if (context.ddItemList() is CILParser.DdItemListContext ddItemList)
            {
                _ = VisitDdItemList(ddItemList);
            }
            else
            {
                _ = VisitDdItem(context.ddItem());
            }
            return GrammarResult.SentinelValue.Result;
        }
        public GrammarResult VisitDdHead(CILParser.DdHeadContext context)
        {
            if (context.id() is CILParser.IdContext id)
            {
                string name = VisitId(id).Value;
                if (!_mappedFieldDataNames.ContainsKey(name))
                {
                    _mappedFieldDataNames.Add(name, _mappedFieldData.Count);
                }
            }
            return GrammarResult.SentinelValue.Result;
        }
        public GrammarResult VisitDdItem(CILParser.DdItemContext context)
        {
            if (context.compQstring() is CILParser.CompQstringContext str)
            {
                var value = VisitCompQstring(str).Value;
                _mappedFieldData.WriteUTF16(value);
                return GrammarResult.SentinelValue.Result;
            }
            else if (context.id() is CILParser.IdContext id)
            {
                // Reference to another data label - this will be patched with the target's RVA
                // during PE serialization by VTableExportPEBuilder.ApplyDataLabelFixups()
                string name = VisitId(id).Value;
                if (!_mappedFieldDataReferenceFixups.TryGetValue(name, out var fixups))
                {
                    _mappedFieldDataReferenceFixups[name] = fixups = new();
                }

                // Reserve 4 bytes for the RVA that will be patched later
                fixups.Add(_mappedFieldData.ReserveBytes(4));
                return GrammarResult.SentinelValue.Result;
            }
            else if (context.bytes() is CILParser.BytesContext bytes)
            {
                _mappedFieldData.WriteBytes(VisitBytes(bytes).Value);
                return GrammarResult.SentinelValue.Result;
            }

            int itemCount = VisitDdItemCount(context.ddItemCount()).Value;

            if (context.INT8() is not null)
            {
                _mappedFieldData.WriteBytes(context.int32() is CILParser.Int32Context int32 ? (byte)VisitInt32(int32).Value : (byte)0, itemCount);
            }
            else if (context.INT16() is not null)
            {
                for (int i = 0; i < itemCount; i++)
                {
                    _mappedFieldData.WriteInt16(context.int32() is CILParser.Int32Context int32 ? (short)VisitInt32(int32).Value : (short)0);
                }
            }
            else if (context.INT32_() is not null)
            {
                for (int i = 0; i < itemCount; i++)
                {
                    _mappedFieldData.WriteInt32(context.int32() is CILParser.Int32Context int32 ? VisitInt32(int32).Value : 0);
                }
            }
            else if (context.INT64_() is not null)
            {
                for (int i = 0; i < itemCount; i++)
                {
                    _mappedFieldData.WriteInt64(context.int64() is CILParser.Int64Context int64 ? VisitInt64(int64).Value : 0);
                }
            }
            else if (context.FLOAT32() is not null)
            {
                for (int i = 0; i < itemCount; i++)
                {
                    _mappedFieldData.WriteSingle(context.float64() is CILParser.Float64Context float64 ? (float)VisitFloat64(float64).Value : 0);
                }
            }
            else if (context.FLOAT64_() is not null)
            {
                for (int i = 0; i < itemCount; i++)
                {
                    _mappedFieldData.WriteDouble(context.float64() is CILParser.Float64Context float64 ? VisitFloat64(float64).Value : 0);
                }
            }
            return GrammarResult.SentinelValue.Result;
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitDdItemCount(CILParser.DdItemCountContext context) => VisitDdItemCount(context);
        public GrammarResult.Literal<int> VisitDdItemCount(CILParser.DdItemCountContext context) => new(context.int32() is CILParser.Int32Context ? VisitInt32(context.int32()).Value : 1);
        public GrammarResult VisitDdItemList(CILParser.DdItemListContext context)
        {
            foreach (var item in context.ddItem())
            {
                VisitDdItem(item);
            }
            return GrammarResult.SentinelValue.Result;
        }

        private readonly Stack<string> _currentNamespace = new();

        private readonly Stack<EntityRegistry.TypeDefinitionEntity> _currentTypeDefinition = new();

        public GrammarResult VisitDecl(CILParser.DeclContext context)
        {
            if (context.nameSpaceHead() is CILParser.NameSpaceHeadContext ns)
            {
                string namespaceName = VisitNameSpaceHead(ns).Value;
                _currentNamespace.Push($"{_currentNamespace.PeekOrDefault()}.{namespaceName}");
                VisitDecls(context.decls());
                _currentNamespace.Pop();
                return GrammarResult.SentinelValue.Result;
            }
            if (context.classHead() is CILParser.ClassHeadContext classHead)
            {
                _currentTypeDefinition.Push(VisitClassHead(classHead).Value);
                VisitClassDecls(context.classDecls());
                _currentTypeDefinition.Pop();
                return GrammarResult.SentinelValue.Result;
            }
            if (context.methodHead() is CILParser.MethodHeadContext methodHead)
            {
                _currentMethod = new(VisitMethodHead(methodHead).Value);
                VisitMethodDecls(context.methodDecls());
                _currentMethod = null;
                return GrammarResult.SentinelValue.Result;
            }
            if (context.fieldDecl() is { } fieldDecl)
            {
                _ = VisitFieldDecl(fieldDecl);
                return GrammarResult.SentinelValue.Result;
            }
            if (context.dataDecl() is { } dataDecl)
            {
                _ = VisitDataDecl(dataDecl);
                return GrammarResult.SentinelValue.Result;
            }
            if (context.vtableDecl() is { } vtable)
            {
                _ = VisitVtableDecl(vtable);
                return GrammarResult.SentinelValue.Result;
            }
            if (context.vtfixupDecl() is { } vtFixup)
            {
                _ = VisitVtfixupDecl(vtFixup);
                return GrammarResult.SentinelValue.Result;
            }
            if (context.extSourceSpec() is { } extSourceSpec)
            {
                _ = VisitExtSourceSpec(extSourceSpec);
                return GrammarResult.SentinelValue.Result;
            }
            if (context.fileDecl() is { } fileDecl)
            {
                _ = VisitFileDecl(fileDecl);
                return GrammarResult.SentinelValue.Result;
            }
            if (context.assemblyBlock() is { } assemblyBlock)
            {
                _ = VisitAssemblyBlock(assemblyBlock);
                return GrammarResult.SentinelValue.Result;
            }
            if (context.assemblyRefHead() is { } assemblyRef)
            {
                var asmRef = VisitAssemblyRefHead(assemblyRef).Value;
                _currentAssemblyOrRef = asmRef;
                foreach (var decl in context.assemblyRefDecls().assemblyRefDecl())
                {
                    _ = VisitAssemblyRefDecl(decl);
                }
                _currentAssemblyOrRef = null;
            }
            if (context.exptypeHead() is { } exptypeHead)
            {
                var (attrs, dottedName) = VisitExptypeHead(exptypeHead).Value;
                (string typeNamespace, string name) = NameHelpers.SplitDottedNameToNamespaceAndName(dottedName);
                var (impl, typeDefId, customAttrs) = VisitExptypeDecls(context.exptypeDecls()).Value;
                if (impl is null)
                {
                    // COMPAT: Like native ilasm, warn and skip the exported type when implementation is not specified
                    ReportWarning(DiagnosticIds.MissingExportedTypeImplementation,
                        string.Format(DiagnosticMessageTemplates.MissingExportedTypeImplementation, dottedName),
                        exptypeHead);
                    return GrammarResult.SentinelValue.Result;
                }
                var exp = _entityRegistry.GetOrCreateExportedType(impl, typeNamespace, name, exp =>
                {
                    exp.Attributes = attrs;
                    exp.TypeDefinitionId = typeDefId;
                });
                foreach (var attr in customAttrs)
                {
                    attr.Owner = exp;
                }
                return GrammarResult.SentinelValue.Result;
            }
            if (context.manifestResHead() is { } manifestResHead)
            {
                var (name, alias, flags) = VisitManifestResHead(manifestResHead).Value;
                var (implementation, offset, attrs) = VisitManifestResDecls(context.manifestResDecls()).Value;
                if (implementation is null)
                {
                    offset = (uint)_manifestResources.Count;
                    byte[] resourceData = _resourceLocator(alias);
                    // ECMA-335: Each resource is prefixed with a 4-byte length
                    _manifestResources.WriteInt32(resourceData.Length);
                    _manifestResources.WriteBytes(resourceData);
                }
                var res = _entityRegistry.CreateManifestResource(name, offset);
                res.Attributes = flags;
                res.Implementation = implementation;
                foreach (var attr in attrs)
                {
                    attr.Owner = res;
                }
                return GrammarResult.SentinelValue.Result;
            }
            if (context.moduleHead() is { } moduleHead)
            {
                if (moduleHead.dottedName() is null)
                {
                    _entityRegistry.Module.Name = null;
                }
                else if (moduleHead.ChildCount == 2)
                {
                    _entityRegistry.Module.Name = VisitDottedName(moduleHead.dottedName()).Value;
                }
                else
                {
                    var name = VisitDottedName(moduleHead.dottedName()).Value;
                    _entityRegistry.GetOrCreateModuleReference(name, _ => { });
                }
                return GrammarResult.SentinelValue.Result;
            }
            if (context.subsystem() is { } subsystem)
            {
                _subsystem = (Subsystem)VisitSubsystem(subsystem).Value;
            }
            if (context.corflags() is { } corflags)
            {
                _corflags = (CorFlags)VisitCorflags(corflags).Value;
            }
            if (context.alignment() is { } alignment)
            {
                _alignment = VisitAlignment(alignment).Value;
            }
            if (context.imagebase() is { } imagebase)
            {
                _imageBase = VisitImagebase(imagebase).Value;
            }
            if (context.stackreserve() is { } stackreserve)
            {
                _stackReserve = VisitStackreserve(stackreserve).Value;
            }
            if (context.languageDecl() is { } languageDecl)
            {
                VisitLanguageDecl(languageDecl);
            }
            if (context.typedefDecl() is { } typedefDecl)
            {
                VisitTypedefDecl(typedefDecl);
            }
            if (context.typelist() is { } typelist)
            {
                foreach (var name in typelist.className())
                {
                    _ = VisitClassName(name);
                }
            }
            if (context.mscorlib() is { } mscorlib)
            {
                VisitMscorlib(mscorlib);
            }
            return GrammarResult.SentinelValue.Result;
        }

        public GrammarResult VisitDecls(CILParser.DeclsContext context)
        {
            foreach (var decl in context.decl())
            {
                _ = VisitDecl(decl);
            }
            return GrammarResult.SentinelValue.Result;
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitDottedName(CILParser.DottedNameContext context)
        {
            return VisitDottedName(context);
        }

        public static GrammarResult.String VisitDottedName(CILParser.DottedNameContext context)
        {
            return new(context.GetText());
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitElementType(CILParser.ElementTypeContext context) => VisitElementType(context);
        public GrammarResult.FormattedBlob VisitElementType(CILParser.ElementTypeContext context)
        {
            BlobBuilder blob = new(5);
            if (context.OBJECT() is not null)
            {
                blob.WriteByte((byte)SignatureTypeCode.Object);
            }
            else if (context.className() is CILParser.ClassNameContext className)
            {
                EntityRegistry.TypeEntity typeEntity = VisitClassName(className).Value;
                if (context.VALUE() is not null || context.VALUETYPE() is not null)
                {
                    blob.WriteByte((byte)SignatureTypeKind.ValueType);
                    blob.WriteTypeEntity(typeEntity);
                }
                else
                {
                    blob.WriteByte((byte)SignatureTypeKind.Class);
                    blob.WriteTypeEntity(typeEntity);
                }
            }
            else if (context.callConv() is CILParser.CallConvContext callConv)
            {
                // Emit function pointer signature.
                blob.WriteByte((byte)SignatureTypeCode.FunctionPointer);
                byte sigCallConv = VisitCallConv(callConv).Value;
                blob.WriteByte(sigCallConv);
                var signatureArgs = VisitSigArgs(context.sigArgs()).Value;
                int numArgs = signatureArgs.Count(arg => !arg.IsSentinel);
                blob.WriteCompressedInteger(numArgs);
                blob.LinkSuffix(VisitType(context.type()).Value);
                foreach (var arg in signatureArgs)
                {
                    blob.LinkSuffix(arg.SignatureBlob);
                }
            }
            else if (context.ELLIPSIS() is not null)
            {
                blob.WriteByte((byte)SignatureTypeCode.Sentinel);
                blob.LinkSuffix(VisitType(context.type()).Value);
            }
            else if (context.METHOD_TYPE_PARAMETER() is not null)
            {
                if (context.int32() is CILParser.Int32Context int32)
                {
                    // COMPAT: Always write a reference to a generic method parameter by index
                    // even if we aren't in a method or the index is out of range. We want to be able to write invalid IL like this.
                    blob.WriteByte((byte)SignatureTypeCode.GenericMethodParameter);
                    blob.WriteCompressedInteger(VisitInt32(int32).Value);
                }
                else
                {
                    string dottedName = VisitDottedName(context.dottedName()).Value;
                    if (_currentMethod is null)
                    {
                        ReportError(DiagnosticIds.MethodTypeParameterOutsideMethod, string.Format(DiagnosticMessageTemplates.MethodTypeParameterOutsideMethod, dottedName), context);
                        blob.WriteByte((byte)SignatureTypeCode.GenericMethodParameter);
                        blob.WriteCompressedInteger(0);
                    }
                    else
                    {
                        blob.WriteByte((byte)SignatureTypeCode.GenericMethodParameter);
                        bool foundParameter = false;
                        for (int i = 0; i < _currentMethod.Definition.GenericParameters.Count; i++)
                        {
                            EntityRegistry.GenericParameterEntity? genericParameter = _currentMethod.Definition.GenericParameters[i];
                            if (genericParameter.Name == dottedName)
                            {
                                foundParameter = true;
                                blob.WriteCompressedInteger(i);
                                break;
                            }
                        }
                        if (!foundParameter)
                        {
                            // BREAK-COMPAT: ILASM would silently emit an invalid signature when a method uses an invalid method type parameter but doesn't have method type parameters.
                            // The signature used completely invalid undocumented codes (that were really sentinel values for how ilasm later detected errors due to how the parsing model worked with a YACC-based parser)
                            // and when a method had no type parameters, it didn't run the code to process out these values and emit errors.
                            // This seems like a scenario that doesn't need to be brought forward.
                            // Instead, we'll just emit a reference to "generic method parameter" 0 and report an error.

                            ReportError(DiagnosticIds.GenericParameterNotFound, string.Format(DiagnosticMessageTemplates.GenericParameterNotFound, dottedName), context);
                            blob.WriteCompressedInteger(0);
                        }
                    }
                }
            }
            else if (context.TYPE_PARAMETER() is not null)
            {
                if (context.int32() is CILParser.Int32Context int32)
                {
                    // COMPAT: Always write a reference to a generic type parameter by index
                    // even if we aren't in a type or the index is out of range. We want to be able to write invalid IL like this.
                    blob.WriteByte((byte)SignatureTypeCode.GenericTypeParameter);
                    blob.WriteCompressedInteger(VisitInt32(int32).Value);
                }
                else
                {
                    string dottedName = VisitDottedName(context.dottedName()).Value;
                    if (_currentTypeDefinition.Count == 0)
                    {
                        ReportError(DiagnosticIds.TypeParameterOutsideType, string.Format(DiagnosticMessageTemplates.TypeParameterOutsideType, dottedName), context);
                        blob.WriteByte((byte)SignatureTypeCode.GenericTypeParameter);
                        blob.WriteCompressedInteger(0);
                    }
                    else
                    {
                        blob.WriteByte((byte)SignatureTypeCode.GenericTypeParameter);
                        bool foundParameter = false;
                        for (int i = 0; i < _currentTypeDefinition.Peek().GenericParameters.Count; i++)
                        {
                            EntityRegistry.GenericParameterEntity? genericParameter = _currentTypeDefinition.Peek().GenericParameters[i];
                            if (genericParameter.Name == dottedName)
                            {
                                foundParameter = true;
                                blob.WriteCompressedInteger(i);
                                break;
                            }
                        }
                        if (!foundParameter)
                        {
                            // BREAK-COMPAT: ILASM would silently emit an invalid signature when a type uses an invalid method type parameter but doesn't have any type parameters.
                            // The signature used completely invalid undocumented codes (that were really sentinel values for how ilasm later detected errors due to how the parsing model worked with a YACC-based parser)
                            // and when a method had no type parameters, it didn't run the code to process out these values and emit errors.
                            // This seems like a scenario that doesn't need to be brought forward.
                            // Instead, we'll just emit a reference to "generic method parameter" 0 and report an error.

                            ReportError(DiagnosticIds.GenericParameterNotFound, string.Format(DiagnosticMessageTemplates.GenericParameterNotFound, dottedName), context);
                            blob.WriteCompressedInteger(0);
                        }
                    }
                }
            }
            else if (context.TYPEDREF() is not null)
            {
                blob.WriteByte((byte)SignatureTypeCode.TypedReference);
            }
            else if (context.VOID() is not null)
            {
                blob.WriteByte((byte)SignatureTypeCode.Void);
            }
            else if (context.NATIVE_INT() is not null)
            {
                blob.WriteByte((byte)SignatureTypeCode.IntPtr);
            }
            else if (context.NATIVE_UINT() is not null)
            {
                blob.WriteByte((byte)SignatureTypeCode.UIntPtr);
            }
            else if (context.simpleType() is CILParser.SimpleTypeContext simpleType)
            {
                blob.WriteByte((byte)VisitSimpleType(simpleType).Value);
            }
            else if (context.dottedName() is CILParser.DottedNameContext dottedName)
            {
                // Typedef reference - resolve and write the type blob
                string alias = VisitDottedName(dottedName).Value;
                var resolved = TryResolveTypedefAsTypeBlob(alias);
                if (resolved is not null)
                {
                    // Copy the content to avoid modifying the stored blob
                    resolved.WriteContentTo(blob);
                }
                else
                {
                    ReportError(DiagnosticIds.TypedefNotFound, string.Format(DiagnosticMessageTemplates.TypedefNotFound, alias), context);
                }
            }
            else
            {
                throw new UnreachableException();
            }
            return new(blob);
        }

        public GrammarResult VisitErrorNode(IErrorNode node) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);

        // esHead is '.line' or '#line' - this is just the keyword, actual parsing is in VisitExtSourceSpec.
        public GrammarResult VisitEsHead(CILParser.EsHeadContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);

        GrammarResult ICILVisitor<GrammarResult>.VisitEventAttr(CILParser.EventAttrContext context) => VisitEventAttr(context);
        public GrammarResult.Flag<EventAttributes> VisitEventAttr(CILParser.EventAttrContext context)
        {
            return context.GetText() switch
            {
                "specialname" => new(EventAttributes.SpecialName),
                "rtspecialname" => new(0), // COMPAT: Ignore
                _ => throw new UnreachableException(),
            };
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitEventDecl(CILParser.EventDeclContext context) => VisitEventDecl(context);
        public GrammarResult.Literal<(MethodSemanticsAttributes, EntityRegistry.EntityBase)?> VisitEventDecl(CILParser.EventDeclContext context)
        {
            if (context.ChildCount != 2)
            {
                return new(null);
            }
            string accessor = context.GetChild(0).GetText();
            EntityRegistry.EntityBase memberReference = VisitMethodRef(context.methodRef()).Value;
            MethodSemanticsAttributes methodSemanticsAttributes = accessor switch
            {
                ".addon" => MethodSemanticsAttributes.Adder,
                ".removeon" => MethodSemanticsAttributes.Remover,
                ".fire" => MethodSemanticsAttributes.Raiser,
                ".other" => MethodSemanticsAttributes.Other,
                _ => throw new UnreachableException(),
            };
            return new((methodSemanticsAttributes, memberReference));
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitEventDecls(CILParser.EventDeclsContext context) => VisitEventDecls(context);
        public GrammarResult.Sequence<(MethodSemanticsAttributes, EntityRegistry.EntityBase)> VisitEventDecls(CILParser.EventDeclsContext context)
            => new(
                context.eventDecl()
                .Select(decl => VisitEventDecl(decl).Value)
                .Where(decl => decl is not null)
                .Select(decl => decl!.Value).ToImmutableArray());

        GrammarResult ICILVisitor<GrammarResult>.VisitEventHead(CILParser.EventHeadContext context) => VisitEventHead(context);
        public GrammarResult.Literal<EntityRegistry.EventEntity> VisitEventHead(CILParser.EventHeadContext context)
        {
            string name = VisitDottedName(context.dottedName()).Value;
            EventAttributes eventAttributes = context.eventAttr().Select(attr => VisitEventAttr(attr).Value).Aggregate((a, b) => a | b);
            return new(new EntityRegistry.EventEntity(eventAttributes, VisitTypeSpec(context.typeSpec()).Value, name));
        }

        public GrammarResult VisitExportHead(CILParser.ExportHeadContext context) => throw new NotImplementedException("Obsolete syntax");
        GrammarResult ICILVisitor<GrammarResult>.VisitExptAttr(CILParser.ExptAttrContext context) => VisitExptAttr(context);
        public static GrammarResult.Flag<TypeAttributes> VisitExptAttr(CILParser.ExptAttrContext context)
        {
            return context.GetText() switch
            {
                "private" => new(TypeAttributes.NotPublic, TypeAttributes.VisibilityMask),
                "public" => new(TypeAttributes.Public, TypeAttributes.VisibilityMask),
                "forwarder" => new(TypeAttributes.Forwarder),
                "nestedpublic" => new(TypeAttributes.NestedPublic, TypeAttributes.VisibilityMask),
                "nestedprivate" => new(TypeAttributes.NestedPrivate, TypeAttributes.VisibilityMask),
                "nestedfamily" => new(TypeAttributes.NestedFamily, TypeAttributes.VisibilityMask),
                "nestedassembly" => new(TypeAttributes.NestedAssembly, TypeAttributes.VisibilityMask),
                "nestedfamandassem" => new(TypeAttributes.NestedFamANDAssem, TypeAttributes.VisibilityMask),
                "nestedfamorassem" => new(TypeAttributes.NestedFamORAssem, TypeAttributes.VisibilityMask),
                _ => throw new UnreachableException(),
            };
        }

        // Type exports and forwarders are implemented via VisitExptypeDecls
        public GrammarResult VisitExptypeDecl(CILParser.ExptypeDeclContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);

        GrammarResult ICILVisitor<GrammarResult>.VisitExptypeDecls(CILParser.ExptypeDeclsContext context) => VisitExptypeDecls(context);
        public GrammarResult.Literal<(EntityRegistry.EntityBase? implementation, int typedefId, ImmutableArray<EntityRegistry.CustomAttributeEntity> attrs)> VisitExptypeDecls(CILParser.ExptypeDeclsContext context)
        {
            // COMPAT: The following order specifies the precedence of the various export kinds.
            // File, Assembly, Class (enclosing type), invalid token.
            // We'll process through all of the options here and then return the one that is valid.
            // We'll also record custom attributes here.
            EntityRegistry.EntityBase? implementationEntity = null;
            int typedefId = 0;
            var attrs = ImmutableArray.CreateBuilder<EntityRegistry.CustomAttributeEntity>();
            var declarations = context.exptypeDecl();
            for (int i = 0; i < declarations.Length; i++)
            {
                if (declarations[i].customAttrDecl() is { } attr)
                {
                    if (VisitCustomAttrDecl(attr).Value is EntityRegistry.CustomAttributeEntity customAttribute)
                    {
                        attrs.Add(customAttribute);
                    }
                    continue;
                }
                if (declarations[i].mdtoken() is { } mdToken)
                {
                    var entity = VisitMdtoken(mdToken).Value;
                    if (entity is null or EntityRegistry.FakeTypeEntity)
                    {
                        ReportError(DiagnosticIds.InvalidMetadataToken, DiagnosticMessageTemplates.InvalidMetadataToken, declarations[i]);
                    }
                    implementationEntity = ResolveBetterEntity(entity);
                    continue;
                }
                string kind = declarations[i].GetText();
                if (kind.StartsWith(".file"))
                {
                    string fileName = VisitDottedName(declarations[i].dottedName()).Value;
                    implementationEntity = _entityRegistry.FindFile(fileName);
                    if (implementationEntity is null)
                    {
                        ReportError(DiagnosticIds.FileNotFound, string.Format(DiagnosticMessageTemplates.FileNotFound, fileName), declarations[i]);
                    }
                }
                else if (kind.StartsWith(".assembly"))
                {
                    string assemblyName = VisitDottedName(declarations[i].dottedName()).Value;
                    implementationEntity = _entityRegistry.FindAssemblyReference(assemblyName);
                    if (implementationEntity is null)
                    {
                        ReportError(DiagnosticIds.AssemblyNotFound, string.Format(DiagnosticMessageTemplates.AssemblyNotFound, assemblyName), declarations[i]);
                    }
                }
                else if (kind.StartsWith(".class"))
                {
                    if (declarations[i].int32() is CILParser.Int32Context int32)
                    {
                        typedefId = VisitInt32(int32).Value;
                    }
                    else
                    {
                        _ = VisitSlashedName(declarations[i].slashedName());
                        var containing = ResolveExportedType(declarations[i].slashedName());
                        if (containing is null)
                        {
                            ReportError(DiagnosticIds.ExportedTypeNotFound, string.Format(DiagnosticMessageTemplates.ExportedTypeNotFound, declarations[i].slashedName().GetText()), declarations[i]);
                        }
                        else
                        {
                            implementationEntity = ResolveBetterEntity(containing);
                        }
                    }
                }
            }

            return new((implementationEntity, typedefId, attrs.ToImmutable()));

            EntityRegistry.EntityBase? ResolveBetterEntity(EntityRegistry.EntityBase? newImplementation)
            {
                return (implementationEntity, newImplementation) switch
                {
                    (null, _) => newImplementation,
                    (_, null) => implementationEntity,
                    (_, EntityRegistry.FileEntity) => newImplementation,
                    (EntityRegistry.FileEntity, _) => implementationEntity,
                    (_, EntityRegistry.AssemblyEntity) => newImplementation,
                    (EntityRegistry.AssemblyEntity, _) => implementationEntity,
                    (_, EntityRegistry.TypeEntity) => newImplementation,
                    (EntityRegistry.TypeEntity, _) => implementationEntity,
                    _ => throw new UnreachableException(),
                };
            }

            // Resolve ExportedType reference
            EntityRegistry.ExportedTypeEntity? ResolveExportedType(CILParser.SlashedNameContext slashedName)
            {
                TypeName typeName = VisitSlashedName(slashedName).Value;
                if (typeName.ContainingTypeName is null)
                {
                    // Check for typedef - typedefs resolve to TypeEntity, not ExportedTypeEntity
                    // so we skip the typedef check for exported type resolution
                }
                Stack<TypeName> containingTypes = new();
                for (TypeName? containingType = typeName; containingType is not null; containingType = containingType.ContainingTypeName)
                {
                    containingTypes.Push(containingType);
                }
                EntityRegistry.ExportedTypeEntity? exportedType = null;
                while (containingTypes.Count != 0)
                {
                    TypeName containingType = containingTypes.Pop();

                    (string ns, string name) = NameHelpers.SplitDottedNameToNamespaceAndName(containingType.DottedName);

                    exportedType = _entityRegistry.FindExportedType(
                        exportedType,
                        ns,
                        name);

                    if (exportedType is null)
                    {
                        ReportError(DiagnosticIds.ExportedTypeNotFound, string.Format(DiagnosticMessageTemplates.ExportedTypeNotFound, containingType.DottedName), slashedName);
                        return null;
                    }
                }

                return exportedType!;
            }
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitExptypeHead(CILParser.ExptypeHeadContext context) => VisitExptypeHead(context);
        public GrammarResult.Literal<(TypeAttributes attrs, string dottedName)> VisitExptypeHead(CILParser.ExptypeHeadContext context)
        {
            var attrs = context.exptAttr().Select(VisitExptAttr).Aggregate((TypeAttributes)0, (a, b) => a | b);
            return new((attrs, VisitDottedName(context.dottedName()).Value));
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitExtendsClause(CILParser.ExtendsClauseContext context) => VisitExtendsClause(context);

        public GrammarResult.Literal<EntityRegistry.TypeEntity?> VisitExtendsClause(CILParser.ExtendsClauseContext context)
        {
            if (context.typeSpec() is CILParser.TypeSpecContext typeSpec)
            {
                return new(VisitTypeSpec(typeSpec).Value);
            }
            else
            {
                return new(null);
            }
        }

        public GrammarResult VisitExtSourceSpec(CILParser.ExtSourceSpecContext context)
        {
            // Parse .line directive to extract source location info
            // Grammar: esHead int32 (',' int32)? (':' int32 (',' int32)?)? (SQSTRING | QSTRING)?
            var int32s = context.int32();
            var sqstring = context.SQSTRING();
            var qstring = context.QSTRING();

            // Extract line/column info based on number of int32s
            int startLine = 0, endLine = 0, startColumn = 0, endColumn = 0;

            if (int32s.Length >= 1)
            {
                startLine = VisitInt32(int32s[0]).Value;
                endLine = startLine;
            }
            if (int32s.Length >= 2)
            {
                // Could be endLine or startColumn depending on separator
                string contextText = context.GetText();
                if (contextText.Contains(',') && contextText.IndexOf(',') < contextText.IndexOf(':'))
                {
                    // Format: startLine,endLine:...
                    endLine = VisitInt32(int32s[1]).Value;
                }
                else
                {
                    // Format: line:column...
                    startColumn = VisitInt32(int32s[1]).Value;
                    endColumn = startColumn;
                }
            }
            if (int32s.Length >= 3)
            {
                startColumn = VisitInt32(int32s[2]).Value;
                endColumn = startColumn;
            }
            if (int32s.Length >= 4)
            {
                endColumn = VisitInt32(int32s[3]).Value;
            }

            // Extract filename if present
            string? filePath = null;
            if (sqstring is not null)
            {
                filePath = StringHelpers.ParseQuotedString(sqstring.GetText());
            }
            else if (qstring is not null)
            {
                filePath = StringHelpers.ParseQuotedString(qstring.GetText());
            }

            // Update current document path if specified
            if (filePath is not null)
            {
                _currentDocumentPath = filePath;
            }

            // If we're in a method, record the sequence point
            if (_currentMethod is not null && _currentDocumentPath is not null)
            {
                int ilOffset = _currentMethod.Definition.MethodBody.Offset;
                _currentMethod.Definition.DebugInfo.DocumentPath ??= _currentDocumentPath;

                // 0xFEEFEE indicates a hidden sequence point
                if (startLine == 0xFEEFEE)
                {
                    _currentMethod.Definition.DebugInfo.SequencePoints.Add(
                        EntityRegistry.SequencePoint.Hidden(ilOffset));
                }
                else
                {
                    _currentMethod.Definition.DebugInfo.SequencePoints.Add(
                        new EntityRegistry.SequencePoint(ilOffset, startLine, startColumn, endLine, endColumn));
                }
            }

            return GrammarResult.SentinelValue.Result;
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitF32seq(CILParser.F32seqContext context) => VisitF32seq(context);
        public GrammarResult.FormattedBlob VisitF32seq(CILParser.F32seqContext context)
        {
            var builder = ImmutableArray.CreateBuilder<float>();

            foreach (var item in context.children)
            {
                builder.Add((float)(item switch
                {
                    CILParser.Int32Context int32 => VisitInt32(int32).Value,
                    CILParser.Float64Context float64 => VisitFloat64(float64).Value,
                    _ => throw new UnreachableException()
                }));
            }
            return new(builder.MoveToImmutable().SerializeSequence());
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitF64seq(CILParser.F64seqContext context) => VisitF64seq(context);
        public GrammarResult.FormattedBlob VisitF64seq(CILParser.F64seqContext context)
        {
            var builder = ImmutableArray.CreateBuilder<double>();

            foreach (var item in context.children)
            {
                builder.Add((double)(item switch
                {
                    CILParser.Int64Context int64 => VisitInt64(int64).Value,
                    CILParser.Float64Context float64 => VisitFloat64(float64).Value,
                    _ => throw new UnreachableException()
                }));
            }
            return new(builder.MoveToImmutable().SerializeSequence());
        }

        public GrammarResult VisitFaultClause(CILParser.FaultClauseContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);

        GrammarResult ICILVisitor<GrammarResult>.VisitFieldAttr(CILParser.FieldAttrContext context) => VisitFieldAttr(context);
        public GrammarResult.Flag<FieldAttributes> VisitFieldAttr(CILParser.FieldAttrContext context)
        {
            if (context.int32() is { } int32)
            {
                return new((FieldAttributes)VisitInt32(int32).Value, ShouldAppend: false);
            }

            return context.GetText() switch
            {
                "static" => new(FieldAttributes.Static),
                "public" => new(FieldAttributes.Public, FieldAttributes.FieldAccessMask),
                "private" => new(FieldAttributes.Private, FieldAttributes.FieldAccessMask),
                "family" => new(FieldAttributes.Family, FieldAttributes.FieldAccessMask),
                "initonly" => new(FieldAttributes.InitOnly),
                "rtspecialname" => new(0), // COMPAT: Don't emit rtspecialname
                "specialname" => new(FieldAttributes.SpecialName),
                "assembly" => new(FieldAttributes.Assembly, FieldAttributes.FieldAccessMask),
                "famandassem" => new(FieldAttributes.FamANDAssem, FieldAttributes.FieldAccessMask),
                "famorassem" => new(FieldAttributes.FamORAssem, FieldAttributes.FieldAccessMask),
                "privatescope" => new(FieldAttributes.PrivateScope, FieldAttributes.FieldAccessMask),
                "literal" => new(FieldAttributes.Literal),
#pragma warning disable SYSLIB0050 // FieldAttributes.NotSeralized is obsolete
                "notserialized" => new(FieldAttributes.NotSerialized),
#pragma warning restore SYSLIB0050 // FieldAttributes.NotSeralized is obsolete
                _ => throw new UnreachableException()
            };
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitFieldDecl(CILParser.FieldDeclContext context) => VisitFieldDecl(context);
        public GrammarResult VisitFieldDecl(CILParser.FieldDeclContext context)
        {
            var fieldAttrs = context.fieldAttr().Select(VisitFieldAttr).Aggregate((FieldAttributes)0, (a, b) => a | b);
            var fieldType = VisitType(context.type()).Value;
            var marshalBlobs = context.marshalBlob();
            var marshalBlob = marshalBlobs.Length > 0 ? VisitMarshalBlob(marshalBlobs[marshalBlobs.Length - 1]).Value : null;
            var name = VisitDottedName(context.dottedName()).Value;
            var rvaOffset = VisitAtOpt(context.atOpt()).Value;
            var fieldOffset = VisitRepeatOpt(context.repeatOpt()).Value;
            var constantValue = VisitInitOpt(context.initOpt()).Value;

            var signature = new BlobEncoder(new BlobBuilder());
            _ = signature.Field();
            fieldType.WriteContentTo(signature.Builder);

            var field = EntityRegistry.CreateUnrecordedFieldDefinition(fieldAttrs, _currentTypeDefinition.PeekOrDefault()!, name, signature.Builder);

            if (field is not null)
            {
                field.MarshallingDescriptor = marshalBlob;
                field.DataDeclarationName = rvaOffset;
                field.Offset = fieldOffset;
                if (constantValue is not NoConstantSentinel)
                {
                    field.ConstantValue = constantValue;
                    field.HasConstant = true;
                }
            }

            return GrammarResult.SentinelValue.Result;
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitFieldInit(CILParser.FieldInitContext context) => VisitFieldInit(context);
        public GrammarResult.Literal<object?> VisitFieldInit(CILParser.FieldInitContext context)
        {
            // fieldInit: fieldSerInit | compQstring | NULLREF;
            if (context.NULLREF() is not null)
            {
                return new(null);
            }
            if (context.compQstring() is CILParser.CompQstringContext compQstring)
            {
                return new(VisitCompQstring(compQstring).Value);
            }
            if (context.fieldSerInit() is CILParser.FieldSerInitContext fieldSerInit)
            {
                // fieldSerInit returns a blob with type byte prefix - extract the actual value
                var blob = VisitFieldSerInit(fieldSerInit).Value;
                return new(ExtractConstantFromSerInit(blob));
            }
            return new(null);
        }

        private static object? ExtractConstantFromSerInit(BlobBuilder blob)
        {
            var bytes = blob.ToImmutableArray();
            if (bytes.Length == 0)
            {
                return null;
            }

            var typeCode = (SerializationTypeCode)bytes[0];
            var valueBytes = bytes.AsSpan().Slice(1);

            return typeCode switch
            {
                SerializationTypeCode.Boolean => valueBytes.Length >= 1 && valueBytes[0] != 0,
                SerializationTypeCode.Char => valueBytes.Length >= 2 ? BitConverter.ToChar(valueBytes) : '\0',
                SerializationTypeCode.SByte => valueBytes.Length >= 1 ? (sbyte)valueBytes[0] : (sbyte)0,
                SerializationTypeCode.Byte => valueBytes.Length >= 1 ? valueBytes[0] : (byte)0,
                SerializationTypeCode.Int16 => valueBytes.Length >= 2 ? BitConverter.ToInt16(valueBytes) : (short)0,
                SerializationTypeCode.UInt16 => valueBytes.Length >= 2 ? BitConverter.ToUInt16(valueBytes) : (ushort)0,
                SerializationTypeCode.Int32 => valueBytes.Length >= 4 ? BitConverter.ToInt32(valueBytes) : 0,
                SerializationTypeCode.UInt32 => valueBytes.Length >= 4 ? BitConverter.ToUInt32(valueBytes) : 0u,
                SerializationTypeCode.Int64 => valueBytes.Length >= 8 ? BitConverter.ToInt64(valueBytes) : 0L,
                SerializationTypeCode.UInt64 => valueBytes.Length >= 8 ? BitConverter.ToUInt64(valueBytes) : 0uL,
                SerializationTypeCode.Single => valueBytes.Length >= 4 ? BitConverter.ToSingle(valueBytes) : 0f,
                SerializationTypeCode.Double => valueBytes.Length >= 8 ? BitConverter.ToDouble(valueBytes) : 0d,
                SerializationTypeCode.String => Encoding.Unicode.GetString(valueBytes),
                // Type is encoded as a SerString (compressed length followed by UTF-8 type name)
                SerializationTypeCode.Type => ExtractSerString(valueBytes),
                // SZArray: element type followed by element count followed by elements
                // Return the raw bytes for arrays since we can't easily represent them
                SerializationTypeCode.SZArray => valueBytes.ToArray(),
                // TaggedObject: type tag followed by value - return raw bytes
                SerializationTypeCode.TaggedObject => valueBytes.ToArray(),
                // Enum: type name (SerString) followed by underlying value - return raw bytes
                SerializationTypeCode.Enum => valueBytes.ToArray(),
                // For unknown/future type codes, return the raw bytes to preserve the data
                _ => bytes.AsSpan().ToArray()
            };
        }

        /// <summary>
        /// Extracts a SerString (compressed length + UTF-8 string) from the given bytes.
        /// Returns null if the first byte is 0xFF (null string marker).
        /// </summary>
        private static string? ExtractSerString(ReadOnlySpan<byte> bytes)
        {
            if (bytes.Length == 0)
            {
                return null;
            }
            // 0xFF indicates null string
            if (bytes[0] == 0xFF)
            {
                return null;
            }
            // Decode compressed length
            int length;
            int bytesRead;
            if ((bytes[0] & 0x80) == 0)
            {
                // 1-byte length
                length = bytes[0];
                bytesRead = 1;
            }
            else if ((bytes[0] & 0xC0) == 0x80)
            {
                // 2-byte length
                if (bytes.Length < 2) return null;
                length = ((bytes[0] & 0x3F) << 8) | bytes[1];
                bytesRead = 2;
            }
            else
            {
                // 4-byte length
                if (bytes.Length < 4) return null;
                length = ((bytes[0] & 0x1F) << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
                bytesRead = 4;
            }
            if (bytes.Length < bytesRead + length)
            {
                return null;
            }
            return Encoding.UTF8.GetString(bytes.Slice(bytesRead, length));
        }

        public GrammarResult VisitFieldOrProp(CILParser.FieldOrPropContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);

        GrammarResult ICILVisitor<GrammarResult>.VisitFieldRef(CILParser.FieldRefContext context) => VisitFieldRef(context);
        public GrammarResult.Literal<EntityRegistry.EntityBase> VisitFieldRef(CILParser.FieldRefContext context)
        {
            if (context.type() is not CILParser.TypeContext type)
            {
                // This is a typedef reference for a field member
                string alias = VisitDottedName(context.dottedName()).Value;
                var resolved = TryResolveTypedefAsMember(alias);
                if (resolved is not null)
                {
                    return new(resolved);
                }
                ReportError(DiagnosticIds.TypedefNotFound, string.Format(DiagnosticMessageTemplates.TypedefNotFound, alias), context);
                return new(_entityRegistry.CreateLazilyRecordedMemberReference(_entityRegistry.ModuleType, alias, new BlobBuilder()));
            }

            var fieldTypeSig = VisitType(type).Value;
            EntityRegistry.TypeEntity definingType = _currentTypeDefinition.PeekOrDefault() ?? _entityRegistry.ModuleType;
            if (context.typeSpec() is CILParser.TypeSpecContext typeSpec)
            {
                definingType = VisitTypeSpec(typeSpec).Value;
            }

            var name = VisitDottedName(context.dottedName()).Value;

            var fieldSig = new BlobBuilder(fieldTypeSig.Count + 1);
            fieldSig.WriteByte((byte)SignatureKind.Field);
            fieldTypeSig.WriteContentTo(fieldSig);
            return new(_entityRegistry.CreateLazilyRecordedMemberReference(definingType, name, fieldSig));
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitFieldSerInit(CILParser.FieldSerInitContext context) => VisitFieldSerInit(context);
        public GrammarResult.FormattedBlob VisitFieldSerInit(CILParser.FieldSerInitContext context)
        {
            // The max length for the majority of the blobs is 9 bytes. 1 for the type of blob, 8 for the max 64-bit value.
            // Byte arrays can be larger, so we handle that case separately.
            const int CommonMaxBlobLength = 9;
            BlobBuilder builder;
            var bytesNode = context.bytes();
            if (bytesNode is not null)
            {
                var bytesResult = VisitBytes(bytesNode);
                // Our blob length is the number of bytes in the byte array + the code for the byte array.
                builder = new BlobBuilder(bytesResult.Value.Length + 1);
                builder.WriteByte((byte)SerializationTypeCode.String);
                builder.WriteBytes(bytesResult.Value);
                return new(builder);
            }
            builder = new BlobBuilder(CommonMaxBlobLength);

            int tokenType = ((ITerminalNode)context.GetChild(0)).Symbol.Type;

            builder.WriteByte((byte)GetTypeCodeForToken(tokenType));

            switch (tokenType)
            {
                case CILParser.BOOL:
                    builder.WriteBoolean(VisitTruefalse(context.truefalse()).Value);
                    break;
                case CILParser.INT8:
                case CILParser.UINT8:
                    builder.WriteByte((byte)VisitInt32(context.int32()).Value);
                    break;
                case CILParser.CHAR:
                case CILParser.INT16:
                case CILParser.UINT16:
                    builder.WriteInt16((short)VisitInt32(context.int32()).Value);
                    break;
                case CILParser.INT32_:
                case CILParser.UINT32:
                    builder.WriteInt32(VisitInt32(context.int32()).Value);
                    break;
                case CILParser.INT64_:
                case CILParser.UINT64:
                    builder.WriteInt64(VisitInt64(context.int64()).Value);
                    break;
                case CILParser.FLOAT32:
                    {
                        if (context.float64() is CILParser.Float64Context float64)
                        {
                            builder.WriteSingle((float)VisitFloat64(float64).Value);
                        }
                        if (context.int32() is CILParser.Int32Context int32)
                        {
                            int value = VisitInt32(int32).Value;
                            builder.WriteSingle(BitConverter.Int32BitsToSingle(value));
                        }
                        break;
                    }
                case CILParser.FLOAT64:
                    {
                        if (context.float64() is CILParser.Float64Context float64)
                        {
                            builder.WriteDouble(VisitFloat64(float64).Value);
                        }
                        if (context.int64() is CILParser.Int64Context int64)
                        {
                            long value = VisitInt64(int64).Value;
                            builder.WriteDouble(BitConverter.Int64BitsToDouble(value));
                        }
                        break;
                    }
            }

            return new(builder);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitFileAttr(CILParser.FileAttrContext context) => VisitFileAttr(context);
        public GrammarResult.Literal<bool> VisitFileAttr(CILParser.FileAttrContext context)
            => context.ChildCount != 0 ? new(false) : new(true);
        GrammarResult ICILVisitor<GrammarResult>.VisitFileDecl(CILParser.FileDeclContext context) => VisitFileDecl(context);
        public GrammarResult.Literal<EntityRegistry.FileEntity> VisitFileDecl(CILParser.FileDeclContext context)
        {
            string dottedName = VisitDottedName(context.dottedName()).Value;
            ImmutableArray<byte>? hash = context.HASH() is not null ? VisitBytes(context.bytes()).Value : null;
            var hashBlob = hash is not null ? new BlobBuilder() : null;
            hashBlob?.WriteBytes(hash!.Value);

            bool hasMetadata = context.fileAttr().Aggregate(true, (acc, attr) => acc || VisitFileAttr(attr).Value);
            bool isEntrypoint = context.fileEntry().Aggregate(true, (acc, attr) => acc || VisitFileEntry(attr).Value);
            var entity = _entityRegistry.GetOrCreateFile(dottedName, hasMetadata, hashBlob);
            if (isEntrypoint)
            {
                _entityRegistry.EntryPoint = entity;
            }
            return new(entity);
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitFileEntry(CILParser.FileEntryContext context) => VisitFileEntry(context);
        public GrammarResult.Literal<bool> VisitFileEntry(CILParser.FileEntryContext context)
            => context.ChildCount != 0 ? new(true) : new(false);

        GrammarResult ICILVisitor<GrammarResult>.VisitFilterClause(CILParser.FilterClauseContext context) => VisitFilterClause(context);
        public GrammarResult.Literal<LabelHandle> VisitFilterClause(CILParser.FilterClauseContext context)
        {
            if (context.scopeBlock() is CILParser.ScopeBlockContext scopeBlock)
            {
                LabelHandle start = _currentMethod!.Definition.MethodBody.DefineLabel();
                _currentMethod.Definition.MethodBody.MarkLabel(start);
                _ = VisitScopeBlock(scopeBlock);
                return new(start);
            }
            if (context.id() is CILParser.IdContext id)
            {
                var start = _currentMethod!.Labels.TryGetValue(VisitId(id).Value, out LabelHandle startLabel) ? startLabel : _currentMethod.Labels[VisitId(id).Value] = _currentMethod.Definition.MethodBody.DefineLabel();
                return new(start);
            }
            if (context.int32() is CILParser.Int32Context offset)
            {
                var start = _currentMethod!.Definition.MethodBody.DefineLabel();
                _currentMethod.Definition.MethodBody.MarkLabel(start, VisitInt32(offset).Value);
                return new(start);
            }
            throw new UnreachableException();
        }

        public GrammarResult VisitFinallyClause(CILParser.FinallyClauseContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);

        GrammarResult ICILVisitor<GrammarResult>.VisitFloat64(CILParser.Float64Context context) => VisitFloat64(context);
        public GrammarResult.Literal<double> VisitFloat64(CILParser.Float64Context context)
        {
            if (context.FLOAT64() is ITerminalNode float64)
            {
                string text = float64.Symbol.Text;
                bool neg = text.StartsWith('-');
                if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out double result))
                {
                    result = neg ? double.MaxValue : double.MinValue;
                }
                return new(result);
            }
            else if (context.int32() is CILParser.Int32Context int32)
            {
                int value = VisitInt32(int32).Value;
                return new(BitConverter.Int32BitsToSingle(value));
            }
            else if (context.int64() is CILParser.Int64Context int64)
            {
                long value = VisitInt64(int64).Value;
                return new(BitConverter.Int64BitsToDouble(value));
            }
            throw new UnreachableException();
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitGenArity(CILParser.GenArityContext context) => VisitGenArity(context);
        public GrammarResult.Literal<int> VisitGenArity(CILParser.GenArityContext context)
            => context.genArityNotEmpty() is CILParser.GenArityNotEmptyContext genArity ? VisitGenArityNotEmpty(genArity) : new(0);

        GrammarResult ICILVisitor<GrammarResult>.VisitGenArityNotEmpty(CILParser.GenArityNotEmptyContext context) => VisitGenArityNotEmpty(context);
        public GrammarResult.Literal<int> VisitGenArityNotEmpty(CILParser.GenArityNotEmptyContext context) => VisitInt32(context.int32());

        GrammarResult ICILVisitor<GrammarResult>.VisitHandlerBlock(CILParser.HandlerBlockContext context) => VisitHandlerBlock(context);

        public GrammarResult.Literal<(LabelHandle Start, LabelHandle End)> VisitHandlerBlock(CILParser.HandlerBlockContext context)
        {
            if (context.scopeBlock() is CILParser.ScopeBlockContext scopeBlock)
            {
                LabelHandle start = _currentMethod!.Definition.MethodBody.DefineLabel();
                _currentMethod.Definition.MethodBody.MarkLabel(start);
                _ = VisitScopeBlock(scopeBlock);
                LabelHandle end = _currentMethod.Definition.MethodBody.DefineLabel();
                _currentMethod.Definition.MethodBody.MarkLabel(end);
                return new((start, end));
            }
            var ids = context.id();
            if (ids.Length == 2)
            {
                var start = _currentMethod!.Labels.TryGetValue(VisitId(ids[0]).Value, out LabelHandle startLabel) ? startLabel : _currentMethod.Labels[VisitId(ids[0]).Value] = _currentMethod.Definition.MethodBody.DefineLabel();
                var end = _currentMethod!.Labels.TryGetValue(VisitId(ids[1]).Value, out LabelHandle endLabel) ? endLabel : _currentMethod.Labels[VisitId(ids[1]).Value] = _currentMethod.Definition.MethodBody.DefineLabel();
                return new((start, end));
            }
            var offsets = context.int32();
            if (offsets.Length == 2)
            {
                var start = _currentMethod!.Definition.MethodBody.DefineLabel();
                var end = _currentMethod.Definition.MethodBody.DefineLabel();
                _currentMethod.Definition.MethodBody.MarkLabel(start, VisitInt32(offsets[0]).Value);
                _currentMethod.Definition.MethodBody.MarkLabel(end, VisitInt32(offsets[1]).Value);
                return new((start, end));
            }
            throw new UnreachableException();
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitHexbytes(CILParser.HexbytesContext context)
        {
            return VisitHexbytes(context);
        }

        public static GrammarResult.Sequence<byte> VisitHexbytes(CILParser.HexbytesContext context)
        {
            ITerminalNode[] bytes = context.HEXBYTE();
            var builder = ImmutableArray.CreateBuilder<byte>(bytes.Length);
            foreach (var @byte in bytes)
            {
                builder.Add(byte.Parse(@byte.Symbol.Text, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture));
            }
            return new(builder.MoveToImmutable());
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitI16seq(CILParser.I16seqContext context) => VisitI16seq(context);
        public GrammarResult.FormattedBlob VisitI16seq(CILParser.I16seqContext context)
        {
            var values = context.int32();
            var builder = ImmutableArray.CreateBuilder<short>(values.Length);
            foreach (var value in values)
            {
                builder.Add((short)VisitInt32(value).Value);
            }
            return new(builder.MoveToImmutable().SerializeSequence());
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitI32seq(CILParser.I32seqContext context) => VisitI32seq(context);
        public GrammarResult.FormattedBlob VisitI32seq(CILParser.I32seqContext context)
        {
            var values = context.int32();
            var builder = ImmutableArray.CreateBuilder<int>(values.Length);
            foreach (var value in values)
            {
                builder.Add(VisitInt32(value).Value);
            }
            return new(builder.MoveToImmutable().SerializeSequence());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitI8seq(CILParser.I8seqContext context) => VisitI8seq(context);
        public GrammarResult.FormattedBlob VisitI8seq(CILParser.I8seqContext context)
        {
            var values = context.int32();
            var builder = ImmutableArray.CreateBuilder<byte>(values.Length);
            foreach (var value in values)
            {
                builder.Add((byte)VisitInt32(value).Value);
            }
            return new(builder.MoveToImmutable().SerializeSequence());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitI64seq(CILParser.I64seqContext context) => VisitI64seq(context);
        public GrammarResult.FormattedBlob VisitI64seq(CILParser.I64seqContext context)
        {
            var values = context.int64();
            var builder = ImmutableArray.CreateBuilder<long>(values.Length);
            foreach (var value in values)
            {
                builder.Add(VisitInt64(value).Value);
            }
            return new(builder.MoveToImmutable().SerializeSequence());
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitId(CILParser.IdContext context) => VisitId(context);
        public static GrammarResult.String VisitId(CILParser.IdContext context)
        {
            return new GrammarResult.String(context.GetText());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitIidParamIndex(CILParser.IidParamIndexContext context) => VisitIidParamIndex(context);
        public GrammarResult.Literal<int?> VisitIidParamIndex(CILParser.IidParamIndexContext context)
            => context.int32() is CILParser.Int32Context int32 ? new(VisitInt32(int32).Value) : new(null);

        GrammarResult ICILVisitor<GrammarResult>.VisitImagebase(CILParser.ImagebaseContext context) => VisitImagebase(context);
        public GrammarResult.Literal<long> VisitImagebase(CILParser.ImagebaseContext context) => VisitInt64(context.int64());

        GrammarResult ICILVisitor<GrammarResult>.VisitImplAttr(ILAssembler.CILParser.ImplAttrContext context) => VisitImplAttr(context);
        public GrammarResult.Flag<MethodImplAttributes> VisitImplAttr(CILParser.ImplAttrContext context)
        {
            if (context.int32() is CILParser.Int32Context int32)
            {
                return new((MethodImplAttributes)VisitInt32(int32).Value, ShouldAppend: false);
            }
            string attribute = context.GetText();
            return attribute switch
            {
                "native" => new(MethodImplAttributes.Native, MethodImplAttributes.CodeTypeMask),
                "cil" => new(MethodImplAttributes.IL, MethodImplAttributes.CodeTypeMask),
                "optil" => new(MethodImplAttributes.OPTIL, MethodImplAttributes.CodeTypeMask),
                "managed" => new(MethodImplAttributes.Managed, MethodImplAttributes.ManagedMask),
                "unmanaged" => new(MethodImplAttributes.Unmanaged, MethodImplAttributes.ManagedMask),
                "forwardref" => new(MethodImplAttributes.ForwardRef),
                "preservesig" => new(MethodImplAttributes.PreserveSig),
                "runtime" => new(MethodImplAttributes.Runtime, MethodImplAttributes.CodeTypeMask),
                "internalcall" => new(MethodImplAttributes.InternalCall),
                "synchronized" => new(MethodImplAttributes.Synchronized),
                "noinlining" => new(MethodImplAttributes.NoInlining),
                "aggressiveinlining" => new(MethodImplAttributes.AggressiveInlining),
                "nooptimization" => new(MethodImplAttributes.NoOptimization),
                "aggressiveoptimization" => new(MethodImplAttributes.AggressiveOptimization),
                "async" => new(MethodImplAttributes.Async),
                _ => throw new UnreachableException(),
            };
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitImplClause(CILParser.ImplClauseContext context) => VisitImplClause(context);
        public GrammarResult.Sequence<EntityRegistry.InterfaceImplementationEntity> VisitImplClause(CILParser.ImplClauseContext context) => context.implList() is {} implList ? VisitImplList(implList) : new(ImmutableArray<EntityRegistry.InterfaceImplementationEntity>.Empty);

        GrammarResult ICILVisitor<GrammarResult>.VisitImplList(CILParser.ImplListContext context) => VisitImplList(context);
        public GrammarResult.Sequence<EntityRegistry.InterfaceImplementationEntity> VisitImplList(CILParser.ImplListContext context)
        {
            var builder = ImmutableArray.CreateBuilder<EntityRegistry.InterfaceImplementationEntity>();
            foreach (var impl in context.typeSpec())
            {
                builder.Add(EntityRegistry.CreateUnrecordedInterfaceImplementation(_currentTypeDefinition.PeekOrDefault()!, VisitTypeSpec(impl).Value));
            }
            return new(builder.ToImmutable());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitInitOpt(CILParser.InitOptContext context) => VisitInitOpt(context);
        public GrammarResult.Literal<object?> VisitInitOpt(CILParser.InitOptContext context)
        {
            if (context.fieldInit() is CILParser.FieldInitContext fieldInit)
            {
                return VisitFieldInit(fieldInit);
            }
            // No initializer - return a sentinel indicating no constant
            return new(NoConstantSentinel.Instance);
        }

        // Sentinel to distinguish "no constant" from "constant is null"
        private sealed class NoConstantSentinel
        {
            public static readonly NoConstantSentinel Instance = new();
            private NoConstantSentinel() { }
        }

        public GrammarResult VisitInstr(CILParser.InstrContext context)
        {
            var instrContext = context.GetRuleContext<ParserRuleContext>(0);
            ILOpCode opcode = ((GrammarResult.Literal<ILOpCode>)instrContext.Accept(this)).Value;
            switch (instrContext.RuleIndex)
            {
                case CILParser.RULE_instr_brtarget:
                    {
                        ParserRuleContext argument = context.GetRuleContext<ParserRuleContext>(1);
                        if (argument is CILParser.IdContext id)
                        {
                            string label = VisitId(id).Value;
                            if (!_currentMethod!.Labels.TryGetValue(label, out var handle))
                            {
                                handle = _currentMethod.Definition.MethodBody.DefineLabel();
                                _currentMethod.Labels[label] = handle;
                                // Track undefined label references for later validation
                                if (!_currentMethod.UndefinedLabelReferences.ContainsKey(label))
                                {
                                    _currentMethod.UndefinedLabelReferences[label] = context;
                                }
                            }
                            _currentMethod.Definition.MethodBody.Branch(opcode, handle);
                        }
                        if (argument is CILParser.Int32Context int32)
                        {
                            int offset = VisitInt32(int32).Value;
                            LabelHandle label = _currentMethod!.Definition.MethodBody.DefineLabel();
                            _currentMethod.Definition.MethodBody.Branch(opcode, label);
                            _currentMethod.Definition.MethodBody.MarkLabel(label, _currentMethod.Definition.MethodBody.Offset + offset);
                        }
                    }
                    break;
                case CILParser.RULE_instr_field:
                    {
                        _currentMethod!.Definition.MethodBody.OpCode(opcode);
                        if (context.mdtoken() is CILParser.MdtokenContext mdtoken)
                        {
                            _currentMethod.Definition.MethodBody.Token(VisitMdtoken(mdtoken).Value.Handle);
                        }
                        else
                        {
                            var fieldRef = VisitFieldRef(context.fieldRef()).Value;
                            if (fieldRef is EntityRegistry.MemberReferenceEntity memberRef)
                            {
                                memberRef.RecordBlobToWriteResolvedHandle(_currentMethod.Definition.MethodBody.CodeBuilder.ReserveBytes(4));
                            }
                            else
                            {
                                _currentMethod.Definition.MethodBody.Token(fieldRef.Handle);
                            }
                        }
                    }
                    break;
                case CILParser.RULE_instr_i:
                    {
                        int arg = VisitInt32(context.int32()).Value;
                        if (opcode == ILOpCode.Ldc_i4 || opcode == ILOpCode.Ldc_i4_s)
                        {
                            _currentMethod!.Definition.MethodBody.LoadConstantI4(arg);
                        }
                        else
                        {
                            _currentMethod!.Definition.MethodBody.OpCode(opcode);
                            _currentMethod.Definition.MethodBody.CodeBuilder.WriteByte((byte)arg);
                        }
                    }
                    break;
                case CILParser.RULE_instr_i8:
                    Debug.Assert(opcode == ILOpCode.Ldc_i8);
                    _currentMethod!.Definition.MethodBody.LoadConstantI8(VisitInt64(context.int64()).Value);
                    break;
                case CILParser.RULE_instr_method:
                    {
                        if (opcode == ILOpCode.Callvirt || opcode == ILOpCode.Newobj)
                        {
                            _expectInstance = true;
                        }
                        _currentMethod!.Definition.MethodBody.OpCode(opcode);
                        var methodRef = VisitMethodRef(context.methodRef()).Value;
                        if (methodRef is EntityRegistry.MemberReferenceEntity memberRef)
                        {
                            memberRef.RecordBlobToWriteResolvedHandle(_currentMethod.Definition.MethodBody.CodeBuilder.ReserveBytes(4));
                        }
                        else
                        {
                            _currentMethod.Definition.MethodBody.Token(methodRef.Handle);
                        }
                        // Reset the instance flag for the next instruction.
                        if (opcode == ILOpCode.Callvirt || opcode == ILOpCode.Newobj)
                        {
                            _expectInstance = false;
                        }
                    }
                    break;
                case CILParser.RULE_instr_none:
                    _currentMethod!.Definition.MethodBody.OpCode(opcode);
                    break;
                case CILParser.RULE_instr_r:
                    {
                        double value;
                        ParserRuleContext argument = context.GetRuleContext<ParserRuleContext>(1);
                        if (argument is CILParser.Float64Context float64)
                        {
                            value = VisitFloat64(float64).Value;
                        }
                        else if (argument is CILParser.Int64Context int64)
                        {
                            long intValue = VisitInt64(int64).Value;
                            value = BitConverter.Int64BitsToDouble(intValue);
                        }
                        else if (argument is CILParser.BytesContext bytesContext)
                        {
                            var bytes = VisitBytes(bytesContext).Value.ToArray();
                            if (bytes.Length >= 8)
                            {
                                value = BitConverter.ToDouble(bytes, 0);
                            }
                            else if (bytes.Length >= 4)
                            {
                                value = BitConverter.ToSingle(bytes, 0);
                            }
                            else
                            {
                                ReportError(DiagnosticIds.ByteArrayTooShort, DiagnosticMessageTemplates.ByteArrayTooShort, bytesContext);
                                value = 0.0d;
                            }
                        }
                        else
                        {
                            throw new UnreachableException();
                        }
                        if (opcode == ILOpCode.Ldc_r4)
                        {
                            _currentMethod!.Definition.MethodBody.LoadConstantR4((float)value);
                        }
                        else
                        {
                            _currentMethod!.Definition.MethodBody.LoadConstantR8(value);
                        }
                    }
                    break;
                case CILParser.RULE_instr_sig:
                    {
                        BlobBuilder signature = new();
                        byte callConv = VisitCallConv(context.callConv()).Value;
                        signature.WriteByte(callConv);
                        var args = VisitSigArgs(context.sigArgs()).Value;
                        signature.WriteCompressedInteger(args.Length);
                        // Write return type
                        VisitType(context.type()).Value.WriteContentTo(signature);
                        // Write arg signatures
                        foreach (var arg in args)
                        {
                            arg.SignatureBlob.WriteContentTo(signature);
                        }
                        _currentMethod!.Definition.MethodBody.Token(_entityRegistry.GetOrCreateStandaloneSignature(signature).Handle);
                    }
                    break;
                case CILParser.RULE_instr_string:
                    Debug.Assert(opcode == ILOpCode.Ldstr);
                    string str;
                    if (context.bytes() is CILParser.BytesContext rawBytes)
                    {
                        ReadOnlySpan<byte> bytes = VisitBytes(rawBytes).Value.AsSpan();
                        ReadOnlySpan<char> bytesAsChars = MemoryMarshal.Cast<byte, char>(bytes);
                        if (!BitConverter.IsLittleEndian)
                        {
                            for (int i = 0; i < bytesAsChars.Length; i++)
                            {
                                BinaryPrimitives.ReverseEndianness(bytesAsChars[i]);
                            }
                        }
                        str = bytesAsChars.ToString();
                    }
                    else
                    {
                        var userString = context.compQstring();
                        Debug.Assert(userString is not null);
                        str = VisitCompQstring(userString!).Value;
                        if (context.ANSI() is not null)
                        {
                            // Emit the string not as a UTF-16 string (as per the spec), but directly as an ANSI string.
                            // Although the string is marked as ANSI, this always used the UTF-8 code page
                            // so we can emit this as UTF-8 bytes.
                            int byteCount = Encoding.UTF8.GetByteCount(str);
                            // Ensure we have an even number of bytes.
                            if ((byteCount % 1) != 0)
                            {
                                byteCount++;
                            }

                            Span<byte> utf8Bytes = new byte[byteCount];
                            Encoding.UTF8.GetBytes(str, utf8Bytes);

                            str = new string(MemoryMarshal.Cast<byte, char>(utf8Bytes));
                        }
                    }
                    _currentMethod!.Definition.MethodBody.LoadString(_metadataBuilder.GetOrAddUserString(str));
                    break;
                case CILParser.RULE_instr_switch:
                    {
                        var labels = new List<(LabelHandle Label, int? Offset)>();
                        foreach (var label in context.labels().children)
                        {
                            if (label is CILParser.IdContext id)
                            {
                                string labelName = VisitId(id).Value;
                                if (!_currentMethod!.Labels.TryGetValue(labelName, out var handle))
                                {
                                    handle = _currentMethod.Definition.MethodBody.DefineLabel();
                                    _currentMethod.Labels[labelName] = handle;
                                    // Track undefined label references for later validation
                                    if (!_currentMethod.UndefinedLabelReferences.ContainsKey(labelName))
                                    {
                                        _currentMethod.UndefinedLabelReferences[labelName] = context;
                                    }
                                }
                                labels.Add((handle, null));
                            }
                            else if (label is CILParser.Int32Context int32)
                            {
                                int offset = VisitInt32(int32).Value;
                                LabelHandle labelHandle = _currentMethod!.Definition.MethodBody.DefineLabel();
                                labels.Add((labelHandle, offset));
                            }
                        }
                        var switchEncoder = _currentMethod!.Definition.MethodBody.Switch(labels.Count);
                        foreach (var label in labels)
                        {
                            switchEncoder.Branch(label.Label);
                        }
                        // Now that we've emitted the switch instruction, we can go back and mark the offset-based target labels
                        foreach (var label in labels)
                        {
                            if (label.Offset is int offset)
                            {
                                _currentMethod.Definition.MethodBody.MarkLabel(label.Label, _currentMethod.Definition.MethodBody.Offset + offset);
                            }
                        }
                    }
                    break;
                case CILParser.RULE_instr_tok:
                    var tok = VisitOwnerType(context.ownerType()).Value;
                    _currentMethod!.Definition.MethodBody.OpCode(opcode);
                    _currentMethod.Definition.MethodBody.Token(tok.Handle);
                    break;
                case CILParser.RULE_instr_type:
                    {
                        var arg = VisitTypeSpec(context.typeSpec()).Value;
                        _currentMethod!.Definition.MethodBody.OpCode(opcode);
                        _currentMethod.Definition.MethodBody.Token(arg.Handle);
                    }
                    break;
                case CILParser.RULE_instr_var:
                    {
                        string instrName = opcode.ToString();
                        bool isShortForm = instrName.EndsWith("_s");
                        _currentMethod!.Definition.MethodBody.OpCode(opcode);
                        if (context.int32() is CILParser.Int32Context int32)
                        {
                            int value = VisitInt32(int32).Value;
                            if (isShortForm)
                            {
                                // Emit a byte instead of the int for the short form
                                _currentMethod.Definition.MethodBody.CodeBuilder.WriteByte((byte)value);
                            }
                            else
                            {
                                _currentMethod.Definition.MethodBody.CodeBuilder.WriteInt32(value);
                            }
                        }
                        else
                        {
                            Debug.Assert(context.id() is not null);
                            string varName = VisitId(context.id()!).Value;
                            int? index = null;
                            if (instrName.Contains("arg"))
                            {
                                if (_currentMethod!.ArgumentNames.TryGetValue(varName, out var argIndex))
                                {
                                    index = argIndex;

                                    if (_currentMethod.Definition.SignatureHeader.IsInstance)
                                    {
                                        index++;
                                    }
                                }
                                else
                                {
                                    ReportError(DiagnosticIds.ArgumentNotFound, string.Format(DiagnosticMessageTemplates.ArgumentNotFound, varName), context);
                                }
                            }
                            else
                            {
                                for (int i = _currentMethod!.LocalsScopes.Count - 1; i >= 0 ; i--)
                                {
                                    if (_currentMethod.LocalsScopes[i].TryGetValue(varName, out var localIndex))
                                    {
                                        index = localIndex;
                                        break;
                                    }
                                }
                                if (index is null)
                                {
                                    ReportError(DiagnosticIds.LocalNotFound, string.Format(DiagnosticMessageTemplates.LocalNotFound, varName), context);
                                }
                            }

                            index ??= -1;

                            if (isShortForm)
                            {
                                // Emit a byte instead of the int for the short form
                                _currentMethod.Definition.MethodBody.CodeBuilder.WriteByte((byte)index.Value);
                            }
                            else
                            {
                                _currentMethod.Definition.MethodBody.CodeBuilder.WriteInt32(index.Value);
                            }
                        }
                    }
                    break;
            }
            return GrammarResult.SentinelValue.Result;
        }

        public GrammarResult.Literal<ILOpCode> VisitInstr_brtarget(CILParser.Instr_brtargetContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_field(CILParser.Instr_fieldContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_i(CILParser.Instr_iContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_i8(CILParser.Instr_i8Context context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_method(CILParser.Instr_methodContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_none(CILParser.Instr_noneContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_r(CILParser.Instr_rContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_sig(CILParser.Instr_sigContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_string(CILParser.Instr_stringContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_switch(CILParser.Instr_switchContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_tok(CILParser.Instr_tokContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_type(CILParser.Instr_typeContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        public GrammarResult.Literal<ILOpCode> VisitInstr_var(CILParser.Instr_varContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
        private static ILOpCode ParseOpCodeFromToken(IToken token)
        {
            return (ILOpCode)Enum.Parse(typeof(ILOpCode), token.Text.Replace('.', '_'), ignoreCase: true);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitInstr(CILParser.InstrContext context) => VisitInstr(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_brtarget(CILParser.Instr_brtargetContext context) => VisitInstr_brtarget(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_field(CILParser.Instr_fieldContext context) => VisitInstr_field(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_i(CILParser.Instr_iContext context) => VisitInstr_i(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_i8(CILParser.Instr_i8Context context) => VisitInstr_i8(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_method(CILParser.Instr_methodContext context) => VisitInstr_method(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_none(CILParser.Instr_noneContext context) => VisitInstr_none(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_r(CILParser.Instr_rContext context) => VisitInstr_r(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_sig(CILParser.Instr_sigContext context) => VisitInstr_sig(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_string(CILParser.Instr_stringContext context) => VisitInstr_string(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_switch(CILParser.Instr_switchContext context) => VisitInstr_switch(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_tok(CILParser.Instr_tokContext context) => VisitInstr_tok(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_type(CILParser.Instr_typeContext context) => VisitInstr_type(context);
        GrammarResult ICILVisitor<GrammarResult>.VisitInstr_var(CILParser.Instr_varContext context) => VisitInstr_var(context);

        private static bool ParseIntegerValue(ReadOnlySpan<char> value, out long result)
        {
            NumberStyles parseStyle = NumberStyles.None;
            bool negate = false;
            if (value.StartsWith("-".AsSpan()))
            {
                negate = true;
            }

            if (value.StartsWith("0x".AsSpan()))
            {
                parseStyle = NumberStyles.AllowHexSpecifier;
                value = value.Slice(2);
            }
            else if (value.StartsWith("0".AsSpan()))
            {
                // Octal support isn't built-in, so we'll do it manually.
                result = 0;
                for (int i = 0; i < value.Length; i++, result *= 8)
                {
                    int digitValue = value[i] - '0';
                    if (digitValue < 0 || digitValue > 7)
                    {
                        // COMPAT: native ilasm skips invalid digits silently
                        continue;
                    }
                    result += digitValue;
                }
                return true;
            }

            bool success = long.TryParse(value.ToString(), parseStyle, CultureInfo.InvariantCulture, out result);
            if (!success)
            {
                return false;
            }

            result *= negate ? -1 : 1;
            return true;
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitInt32(CILParser.Int32Context context)
        {
            return VisitInt32(context);
        }

        public GrammarResult.Literal<int> VisitInt32(CILParser.Int32Context context)
        {
            IToken node = context.INT32().Symbol;

            ReadOnlySpan<char> value = node.Text.AsSpan();

            if (!ParseIntegerValue(value, out long num))
            {
                _diagnostics.Add(new Diagnostic(
                    DiagnosticIds.LiteralOutOfRange,
                    DiagnosticSeverity.Error,
                    string.Format(DiagnosticMessageTemplates.LiteralOutOfRange, node.Text),
                    Location.From(node, _documents)));
                return new GrammarResult.Literal<int>(0);
            }

            return new GrammarResult.Literal<int>((int)num);
        }


        GrammarResult ICILVisitor<GrammarResult>.VisitInt64(CILParser.Int64Context context)
        {
            return VisitInt64(context);
        }

        public GrammarResult.Literal<long> VisitInt64(CILParser.Int64Context context)
        {
            IToken node = context.GetChild<ITerminalNode>(0).Symbol;

            ReadOnlySpan<char> value = node.Text.AsSpan();

            if (!ParseIntegerValue(value, out long num))
            {
                _diagnostics.Add(new Diagnostic(
                    DiagnosticIds.LiteralOutOfRange,
                    DiagnosticSeverity.Error,
                    string.Format(DiagnosticMessageTemplates.LiteralOutOfRange, node.Text),
                    Location.From(node, _documents)));
                return new GrammarResult.Literal<long>(0);
            }

            return new GrammarResult.Literal<long>(num);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitIntOrWildcard(CILParser.IntOrWildcardContext context) => VisitIntOrWildcard(context);
        public GrammarResult.Literal<int?> VisitIntOrWildcard(CILParser.IntOrWildcardContext context) => context.int32() is {} int32 ? new(VisitInt32(int32).Value) : new(null);

        private void ValidateLabelReferences()
        {
            if (_currentMethod is null)
            {
                return;
            }

            // Report errors for any labels that were referenced but never declared
            foreach (var undefinedLabel in _currentMethod.UndefinedLabelReferences)
            {
                string labelName = undefinedLabel.Key;
                ParserRuleContext context = undefinedLabel.Value;

                // Only report if the label was never declared
                if (!_currentMethod.DeclaredLabels.Contains(labelName))
                {
                    ReportError(DiagnosticIds.LabelNotFound,
                        string.Format(DiagnosticMessageTemplates.LabelNotFound, labelName),
                        context);
                }
            }
        }

        public GrammarResult VisitLabels(CILParser.LabelsContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);

        GrammarResult ICILVisitor<GrammarResult>.VisitLabelDecl(CILParser.LabelDeclContext context) => VisitLabelDecl(context);
        public GrammarResult VisitLabelDecl(CILParser.LabelDeclContext context)
        {
            var labelId = context.id();
            string labelName = VisitId(labelId).Value;
            _currentMethod!.DeclaredLabels.Add(labelName);
            if (!_currentMethod!.Labels.TryGetValue(labelName, out var label))
            {
                label = _currentMethod.Definition.MethodBody.DefineLabel();
                _currentMethod.Labels[labelName] = label;
            }
            _currentMethod.Definition.MethodBody.MarkLabel(label);
            return GrammarResult.SentinelValue.Result;
        }

        public GrammarResult VisitLanguageDecl(CILParser.LanguageDeclContext context)
        {
            // .language SQSTRING (',' SQSTRING (',' SQSTRING)?)?
            // First GUID: language (e.g., C#, VB, IL)
            // Second GUID: vendor (optional)
            // Third GUID: document type (optional)
            var strings = context.SQSTRING();
            if (strings.Length >= 1 && Guid.TryParse(StringHelpers.ParseQuotedString(strings[0].GetText()), out var languageGuid))
            {
                _currentLanguageGuid = languageGuid;
            }
            if (strings.Length >= 2 && Guid.TryParse(StringHelpers.ParseQuotedString(strings[1].GetText()), out var vendorGuid))
            {
                _currentLanguageVendorGuid = vendorGuid;
            }
            if (strings.Length >= 3 && Guid.TryParse(StringHelpers.ParseQuotedString(strings[2].GetText()), out var docTypeGuid))
            {
                _currentDocumentTypeGuid = docTypeGuid;
            }
            return GrammarResult.SentinelValue.Result;
        }

        public GrammarResult VisitManifestResDecl(CILParser.ManifestResDeclContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        GrammarResult ICILVisitor<GrammarResult>.VisitManifestResDecls(CILParser.ManifestResDeclsContext context) => VisitManifestResDecls(context);
        public GrammarResult.Literal<(EntityRegistry.EntityBase? implementation, uint offset, ImmutableArray<EntityRegistry.CustomAttributeEntity> attributes)> VisitManifestResDecls(CILParser.ManifestResDeclsContext context)
        {
            EntityRegistry.EntityBase? implementation = null;
            uint offset = 0;
            var attributes = ImmutableArray.CreateBuilder<EntityRegistry.CustomAttributeEntity>();
            // COMPAT: Priority order for implementation is the following
            // AssemblyRef, File, nil
            foreach (var decl in context.manifestResDecl())
            {
                if (decl.customAttrDecl() is CILParser.CustomAttrDeclContext customAttrDecl)
                {
                    if (VisitCustomAttrDecl(customAttrDecl).Value is { } attr)
                    {
                        attributes.Add(attr);
                    }
                }
                string kind = decl.GetChild(0).GetText();
                if (kind == ".file" && implementation is not EntityRegistry.AssemblyReferenceEntity)
                {
                    string fileName = VisitDottedName(decl.dottedName()).Value;
                    var file = _entityRegistry.FindFile(fileName);
                    if (file is null)
                    {
                        ReportError(DiagnosticIds.FileNotFound, string.Format(DiagnosticMessageTemplates.FileNotFound, fileName), decl);
                    }
                    else
                    {
                        implementation = file;
                        offset = (uint)VisitInt32(decl.int32()).Value;
                    }
                }
                else if (kind == ".assembly")
                {
                    string assemblyName = VisitDottedName(decl.dottedName()).Value;
                    var asm = _entityRegistry.FindAssemblyReference(assemblyName);
                    if (asm is null)
                    {
                        ReportError(DiagnosticIds.AssemblyNotFound, string.Format(DiagnosticMessageTemplates.AssemblyNotFound, assemblyName), decl);
                    }
                    else
                    {
                        implementation = asm;
                    }
                }
            }

            return new((implementation, offset, attributes.ToImmutable()));
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitManifestResHead(CILParser.ManifestResHeadContext context) => VisitManifestResHead(context);
        public GrammarResult.Literal<(string name, string alias, ManifestResourceAttributes attr)> VisitManifestResHead(CILParser.ManifestResHeadContext context)
        {
            var dottedNames = context.dottedName();
            string name = VisitDottedName(dottedNames[0]).Value;
            string alias = dottedNames.Length == 2 ? VisitDottedName(dottedNames[1]).Value : name;
            ManifestResourceAttributes attr = 0;
            foreach (var attrContext in context.manresAttr())
            {
                attr |= VisitManresAttr(attrContext).Value;
            }

            return new((name, alias, attr));
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitManresAttr(CILParser.ManresAttrContext context) => VisitManresAttr(context);
        public GrammarResult.Flag<ManifestResourceAttributes> VisitManresAttr(CILParser.ManresAttrContext context)
        {
            return context.GetText() switch
            {
                "public" => new(ManifestResourceAttributes.Public),
                "private" => new(ManifestResourceAttributes.Private),
                _ => throw new UnreachableException()
            };
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitMarshalBlob(CILParser.MarshalBlobContext context) => VisitMarshalBlob(context);
        public GrammarResult.FormattedBlob VisitMarshalBlob(CILParser.MarshalBlobContext context)
        {
            if (context.hexbytes() is CILParser.HexbytesContext hexBytes)
            {
                var bytes = VisitHexbytes(hexBytes).Value;
                var blob = new BlobBuilder(bytes.Length);
                blob.WriteBytes(bytes);
                return new(blob);
            }

            return VisitNativeType(context.nativeType());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitMarshalClause(CILParser.MarshalClauseContext context) => VisitMarshalClause(context);
        public GrammarResult.FormattedBlob VisitMarshalClause(CILParser.MarshalClauseContext context)
        {
            if (context.ChildCount == 0)
            {
                return new(new BlobBuilder(0));
            }

            return VisitMarshalBlob(context.marshalBlob());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitMdtoken(ILAssembler.CILParser.MdtokenContext context) => VisitMdtoken(context);
        public GrammarResult.Literal<EntityRegistry.EntityBase> VisitMdtoken(CILParser.MdtokenContext context)
        {
            return new(_entityRegistry.ResolveHandleToEntity(MetadataTokens.EntityHandle(VisitInt32(context.int32()).Value)));
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitMemberRef(CILParser.MemberRefContext context) => VisitMemberRef(context);
        public GrammarResult.Literal<EntityRegistry.EntityBase> VisitMemberRef(CILParser.MemberRefContext context)
        {
            if (context.mdtoken() is CILParser.MdtokenContext mdToken)
            {
                return VisitMdtoken(mdToken);
            }

            if (context.methodRef() is CILParser.MethodRefContext methodRef)
            {
                return VisitMethodRef(methodRef);
            }
            if (context.fieldRef() is CILParser.FieldRefContext fieldRef)
            {
                return VisitFieldRef(fieldRef);
            }

            throw new UnreachableException();
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitMethAttr(CILParser.MethAttrContext context) => VisitMethAttr(context);
        public GrammarResult.Flag<MethodAttributes> VisitMethAttr(CILParser.MethAttrContext context)
        {
            if (context.int32() is CILParser.Int32Context int32)
            {
                return new((MethodAttributes)VisitInt32(int32).Value, ShouldAppend: false);
            }
            string attribute = context.GetText();
            return attribute switch
            {
                "static" => new(MethodAttributes.Static),
                "public" => new(MethodAttributes.Public, MethodAttributes.MemberAccessMask),
                "private" => new(MethodAttributes.Private, MethodAttributes.MemberAccessMask),
                "family" => new(MethodAttributes.Family, MethodAttributes.MemberAccessMask),
                "final" => new(MethodAttributes.Final),
                "specialname" => new(MethodAttributes.SpecialName),
                "virtual" => new(MethodAttributes.Virtual),
                "strict" => new(MethodAttributes.CheckAccessOnOverride),
                "abstract" => new(MethodAttributes.Abstract),
                "assembly" => new(MethodAttributes.Assembly, MethodAttributes.MemberAccessMask),
                "famandassem" => new(MethodAttributes.FamANDAssem, MethodAttributes.MemberAccessMask),
                "famorassem" => new(MethodAttributes.FamORAssem, MethodAttributes.MemberAccessMask),
                "privatescope" => new(MethodAttributes.PrivateScope, MethodAttributes.MemberAccessMask),
                "hidebysig" => new(MethodAttributes.HideBySig),
                "newslot" => new(MethodAttributes.NewSlot),
                "rtspecialname" => new(0), // COMPAT: Rtspecialname is ignored
                "unmanagedexp" => new(MethodAttributes.UnmanagedExport),
                "reqsecobj" => new(MethodAttributes.RequireSecObject),
                _ => throw new UnreachableException(),
            };
        }
        public GrammarResult VisitMethodDecl(CILParser.MethodDeclContext context)
        {
            Debug.Assert(_currentMethod is not null);
            var currentMethod = _currentMethod!;

            if (context.EMITBYTE() is not null)
            {
                currentMethod.Definition.MethodBody.CodeBuilder.WriteByte((byte)VisitInt32(context.GetChild<CILParser.Int32Context>(0)).Value);
            }
            else if (context.ENTRYPOINT() is not null)
            {
                _entityRegistry.EntryPoint = currentMethod.Definition;
            }
            else if (context.ZEROINIT() is not null)
            {
                currentMethod.Definition.BodyAttributes = MethodBodyAttributes.InitLocals;
            }
            else if (context.MAXSTACK() is not null)
            {
                currentMethod.Definition.MaxStack = VisitInt32(context.GetChild<CILParser.Int32Context>(0)).Value;
            }
            else if (context.LOCALS() is not null)
            {
                if (context.ChildCount == 3)
                {
                    // init keyword specified
                    currentMethod.Definition.BodyAttributes = MethodBodyAttributes.InitLocals;
                }
                var localsScope = currentMethod.LocalsScopes.Count != 0 ? currentMethod.LocalsScopes[currentMethod.LocalsScopes.Count - 1] : new();
                var newLocals = VisitSigArgs(context.sigArgs()).Value;
                foreach (var loc in newLocals)
                {
                    // BREAK-COMPAT: We don't allow specifying a local's slot via the [in], [out], or [opt] parameter attributes, or the custom int override.
                    // This only worked in ilasm due to how ilasm reused fields.
                    // We're only going to support allowing this tool to determine the slot numbers.
                    // This blocks two different locals in two different scopes from resuing the same slot
                    // but that is a very rare scenario (even using more than one .locals block in a method in IL is quite rare)

                    // If the local is named, add it to our name-lookup dictionary.
                    // Otherwise, it will only be accessible via its index.
                    if (loc.Name is not null)
                    {
                        localsScope.Add(loc.Name, currentMethod.AllLocals.Count);
                    }
                    currentMethod.AllLocals.Add(loc);
                }
            }
            else if (context.labelDecl() is CILParser.LabelDeclContext labelDecl)
            {
                var labelId = labelDecl.id();
                string labelName = VisitId(labelId).Value;
                currentMethod.DeclaredLabels.Add(labelName);
                if (!currentMethod.Labels.TryGetValue(labelName, out var label))
                {
                    label = currentMethod.Definition.MethodBody.DefineLabel();
                    currentMethod.Labels[labelName] = label;
                }
                currentMethod.Definition.MethodBody.MarkLabel(label);
            }
            else if (context.EXPORT() is not null)
            {
                // .export [ordinal] or .export [ordinal] as alias
                int ordinal = VisitInt32(context.int32()[0]).Value;
                string? alias = context.id() is { } aliasId ? VisitId(aliasId).Value : null;

                currentMethod.Definition.ExportOrdinal = ordinal;
                currentMethod.Definition.ExportAlias = alias;
            }
            else if (context.VTENTRY() is not null)
            {
                // .vtentry vtableIndex : slotIndex
                int vtableEntry = VisitInt32(context.int32()[0]).Value;
                int vtableSlot = VisitInt32(context.int32()[1]).Value;

                currentMethod.Definition.VTableEntry = vtableEntry;
                currentMethod.Definition.VTableSlot = vtableSlot;
            }
            else if (context.OVERRIDE() is not null)
            {
                BlobBuilder signature = currentMethod.Definition.MethodSignature!;
                if (context.callConv() is {} callConv)
                {
                    // We have an explicitly specified signature, so we need to parse it.
                    signature = new();
                    var callConvByte = VisitCallConv(callConv).Value;
                    var arity = VisitGenArity(context.genArity()).Value;
                    if (arity > 0)
                    {
                        callConvByte |= (byte)SignatureAttributes.Generic;
                    }
                    signature.WriteByte(callConvByte);
                    if (arity > 0)
                    {
                        signature.WriteCompressedInteger(arity);
                    }
                    var args = VisitSigArgs(context.sigArgs()).Value;
                    signature.WriteCompressedInteger(args.Length);
                    VisitType(context.type()).Value.WriteContentTo(signature);
                    foreach (var arg in args)
                    {
                        arg.SignatureBlob.WriteContentTo(signature);
                    }
                }

                var ownerType = VisitTypeSpec(context.typeSpec()).Value;
                var methodName = VisitMethodName(context.methodName()).Value;
                var methodRef = _entityRegistry.CreateLazilyRecordedMemberReference(ownerType, methodName, signature);
                _currentTypeDefinition.PeekOrDefault()!.MethodImplementations.Add(EntityRegistry.CreateUnrecordedMethodImplementation(currentMethod.Definition, methodRef));
            }
            else if (context.PARAM() is not null)
            {
                // BREAK-COMPAT: We require attributes on parameters, generic parameters, and constraints
                // to be specified directly after the .param directive, not at any point later in the method.
                // This matches the IL outputted by ILDASM, ILSpy, and other tools in the ecosystem.
                // Attributes not specified directly after the .param directive are applied to the method itself.
                var customAttrDeclarations = context.customAttrDecl();
                if (context.TYPE() is not null)
                {
                    // Type parameters
                    EntityRegistry.GenericParameterEntity? param = null;
                    if (context.int32() is { } int32)
                    {
                        int index = VisitInt32(int32[0]).Value;
                        if (index < 0 || index >= currentMethod.Definition.GenericParameters.Count)
                        {
                            ReportError(DiagnosticIds.GenericParameterIndexOutOfRange,
                                string.Format(DiagnosticMessageTemplates.GenericParameterIndexOutOfRange, index),
                                context);
                            return GrammarResult.SentinelValue.Result;
                        }
                        param = currentMethod.Definition.GenericParameters[index];
                    }
                    else
                    {
                        string name = VisitDottedName(context.dottedName()).Value;
                        foreach (var genericParam in currentMethod.Definition.GenericParameters)
                        {
                            if (genericParam.Name == name)
                            {
                                param = genericParam;
                                break;
                            }
                        }
                        if (param is null)
                        {
                            ReportError(DiagnosticIds.UnknownGenericParameter,
                                string.Format(DiagnosticMessageTemplates.UnknownGenericParameter, name),
                                context);
                            return GrammarResult.SentinelValue.Result;
                        }
                    }
                    foreach (var attr in customAttrDeclarations ?? Array.Empty<CILParser.CustomAttrDeclContext>())
                    {
                        var customAttrDecl = VisitCustomAttrDecl(attr).Value;
                        customAttrDecl?.Owner = param;
                    }
                }
                else if (context.CONSTRAINT() is not null)
                {
                    // constraints
                    EntityRegistry.GenericParameterEntity? param = null;
                    if (context.int32() is { } int32)
                    {
                        int index = VisitInt32(int32[0]).Value;
                        if (index < 0 || index >= currentMethod.Definition.GenericParameters.Count)
                        {
                            ReportError(DiagnosticIds.GenericParameterIndexOutOfRange,
                                string.Format(DiagnosticMessageTemplates.GenericParameterIndexOutOfRange, index),
                                context);
                            return GrammarResult.SentinelValue.Result;
                        }
                        param = currentMethod.Definition.GenericParameters[index];
                    }
                    else
                    {
                        string name = VisitDottedName(context.dottedName()).Value;
                        foreach (var genericParam in currentMethod.Definition.GenericParameters)
                        {
                            if (genericParam.Name == name)
                            {
                                param = genericParam;
                                break;
                            }
                        }
                        if (param is null)
                        {
                            ReportError(DiagnosticIds.UnknownGenericParameter,
                                string.Format(DiagnosticMessageTemplates.UnknownGenericParameter, name),
                                context);
                            return GrammarResult.SentinelValue.Result;
                        }
                    }
                    EntityRegistry.GenericParameterConstraintEntity? constraint = null;
                    var baseType = VisitTypeSpec(context.typeSpec()).Value;
                    foreach (var constraintEntity in param.Constraints)
                    {
                        if (constraintEntity.BaseType == baseType)
                        {
                            constraint = constraintEntity;
                            break;
                        }
                    }
                    if (constraint is null)
                    {
                        constraint = EntityRegistry.CreateGenericConstraint(baseType);
                        constraint.Owner = param;
                        param.Constraints.Add(constraint);
                        currentMethod.Definition.GenericParameterConstraints.Add(constraint);
                    }
                    foreach (var attr in customAttrDeclarations ?? Array.Empty<CILParser.CustomAttrDeclContext>())
                    {
                        var customAttrDecl = VisitCustomAttrDecl(attr).Value;
                        customAttrDecl?.Owner = constraint;
                    }
                }
                else
                {
                    // Adding attributes to parameters.
                    int index = VisitInt32(context.int32()[0]).Value;
                    if (index < 0 || index >= currentMethod.Definition.Parameters.Count)
                    {
                        ReportError(DiagnosticIds.ParameterIndexOutOfRange,
                            string.Format(DiagnosticMessageTemplates.ParameterIndexOutOfRange, index),
                            context);
                        return GrammarResult.SentinelValue.Result;
                    }

                    // Handle initOpt to get the Constant table entry if a constant value is provided.
                    var constantValue = VisitInitOpt(context.initOpt()).Value;
                    var param = currentMethod.Definition.Parameters[index];
                    if (constantValue is not NoConstantSentinel)
                    {
                        param.ConstantValue = constantValue;
                        param.HasConstant = true;
                    }
                    foreach (var attr in customAttrDeclarations ?? Array.Empty<CILParser.CustomAttrDeclContext>())
                    {
                        var customAttrDecl = VisitCustomAttrDecl(attr).Value;
                        if (customAttrDecl is not null)
                        {
                            customAttrDecl.Owner = param;
                            param.HasCustomAttributes = true;
                        }
                    }
                }
            }
            else if (context.secDecl() is {} secDecl)
            {
                var declarativeSecurity = VisitSecDecl(secDecl).Value;
                declarativeSecurity?.Parent = currentMethod.Definition;
            }
            else if (context.GetChild(0) is CILParser.InstrContext instr)
            {
                _ = VisitInstr(instr);
            }
            else
            {
                // Handle other methodDecl alternatives
                var child = context.children[0];
                _ = child.Accept(this);
            }
            return GrammarResult.SentinelValue.Result;
        }


        public GrammarResult VisitMethodDecls(CILParser.MethodDeclsContext context)
        {
            foreach (var decl in context.methodDecl())
            {
                VisitMethodDecl(decl);
            }
            return GrammarResult.SentinelValue.Result;
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitMethodHead(CILParser.MethodHeadContext context) => VisitMethodHead(context);
        public GrammarResult.Literal<EntityRegistry.MethodDefinitionEntity> VisitMethodHead(CILParser.MethodHeadContext context)
        {
            string name = VisitMethodName(context.methodName()).Value;
            var containingType = _currentTypeDefinition.PeekOrDefault() ?? _entityRegistry.ModuleType;
            var methodDefinition = EntityRegistry.CreateUnrecordedMethodDefinition(containingType, name);

            BlobBuilder methodSignature = new();
            byte sigHeader = VisitCallConv(context.callConv()).Value;

            // Set the current method for type parameter and signature parsing
            // so we can resolve generic parameters correctly.
            _currentMethod = new(methodDefinition);
            var typeParameters = VisitTyparsClause(context.typarsClause()).Value;
            if (typeParameters.Length != 0)
            {
                sigHeader |= (byte)SignatureAttributes.Generic;
            }
            methodDefinition.MethodAttributes = context.methAttr().Aggregate((MethodAttributes)0, (acc, attr) => acc | VisitMethAttr(attr));

            if (methodDefinition.MethodAttributes.HasFlag(MethodAttributes.Abstract) && !methodDefinition.ContainingType.Attributes.HasFlag(TypeAttributes.Abstract))
            {
                ReportError(DiagnosticIds.AbstractMethodNotInAbstractType,
                    string.Format(DiagnosticMessageTemplates.AbstractMethodNotInAbstractType, methodDefinition.Name),
                    context);
            }

            (EntityRegistry.ModuleReferenceEntity Module, string? EntryPoint, MethodImportAttributes Attributes)? pInvokeInformation = null;
            foreach (var pInvokeInfo in context.pinvImpl())
            {
                var (moduleName, entryPoint, attributes) = VisitPinvImpl(pInvokeInfo).Value;
                if (moduleName is null)
                {
                    ReportError(DiagnosticIds.InvalidPInvokeSignature,
                        DiagnosticMessageTemplates.InvalidPInvokeSignature,
                        pInvokeInfo);
                    continue;
                }
                pInvokeInformation = (_entityRegistry.GetOrCreateModuleReference(moduleName, _ => { }), entryPoint, attributes);
            }
            methodDefinition.MethodImportInformation = pInvokeInformation;

            SignatureHeader parsedHeader = new(sigHeader);
            if (methodDefinition.MethodAttributes.HasFlag(MethodAttributes.Static) && (parsedHeader.IsInstance || parsedHeader.HasExplicitThis))
            {
                // Error on static + instance.
            }
            if (parsedHeader.HasExplicitThis && !parsedHeader.IsInstance)
            {
                // Warn on explicit-this + non-instance
                parsedHeader = new(sigHeader |= (byte)SignatureAttributes.Instance);
            }
            methodSignature.WriteByte(sigHeader);
            if (typeParameters.Length != 0)
            {
                methodSignature.WriteCompressedInteger(typeParameters.Length);
            }
            for (int i = 0; i < typeParameters.Length; i++)
            {
                EntityRegistry.GenericParameterEntity? param = typeParameters[i];
                param.Owner = methodDefinition;
                param.Index = i;
                methodDefinition.GenericParameters.Add(param);
                foreach (var constraint in param.Constraints)
                {
                    constraint.Owner = param;
                    methodDefinition.GenericParameterConstraints.Add(constraint);
                }
            }

            var args = VisitSigArgs(context.sigArgs()).Value;
            methodSignature.WriteCompressedInteger(args.Length);

            SignatureArg returnValue = new(VisitParamAttr(context.paramAttr()).Value, VisitType(context.type()).Value, VisitMarshalClause(context.marshalClause()).Value, null);

            returnValue.SignatureBlob.WriteContentTo(methodSignature);
            methodDefinition.Parameters.Add(EntityRegistry.CreateParameter(returnValue.Attributes, returnValue.Name, returnValue.MarshallingDescriptor, 0));
            for (int i = 0; i < args.Length; i++)
            {
                SignatureArg? arg = args[i];
                arg.SignatureBlob.WriteContentTo(methodSignature);
                methodDefinition.Parameters.Add(EntityRegistry.CreateParameter(arg.Attributes, arg.Name, arg.MarshallingDescriptor, i + 1));
            }
            // We've parsed all signature information. We can reset the current method now (the caller will handle setting/unsetting it for the method body).
            _currentMethod = null;
            methodDefinition.SignatureHeader = parsedHeader;
            methodDefinition.MethodSignature = methodSignature;

            methodDefinition.ImplementationAttributes = context.implAttr().Aggregate((MethodImplAttributes)0, (acc, attr) => acc | VisitImplAttr(attr));
            if (!EntityRegistry.TryAddMethodDefinitionToContainingType(methodDefinition))
            {
                ReportError(DiagnosticIds.DuplicateMethod,
                    DiagnosticMessageTemplates.DuplicateMethod,
                    context);
            }

            return new(methodDefinition);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitMethodName(CILParser.MethodNameContext context) => VisitMethodName(context);
        public GrammarResult.String VisitMethodName(CILParser.MethodNameContext context)
        {
            IParseTree child = context.GetChild(0);
            if (child is ITerminalNode terminal)
            {
                return new(terminal.Symbol.Text);
            }
            Debug.Assert(child is CILParser.DottedNameContext);
            return (GrammarResult.String)child.Accept(this);
        }

        private bool _expectInstance;
        private Subsystem _subsystem = Subsystem.WindowsCui;
        private CorFlags _corflags = CorFlags.ILOnly;
        private int _alignment = 0x200;
        private long _imageBase = 0x00400000;
        private long _stackReserve;

        GrammarResult ICILVisitor<GrammarResult>.VisitMethodRef(CILParser.MethodRefContext context) => VisitMethodRef(context);
        public GrammarResult.Literal<EntityRegistry.EntityBase> VisitMethodRef(CILParser.MethodRefContext context)
        {
            if (context.mdtoken() is CILParser.MdtokenContext token)
            {
                return new(VisitMdtoken(token).Value);
            }
            if (context.dottedName() is CILParser.DottedNameContext dottedName)
            {
                // This is a typedef reference for a method member
                string alias = VisitDottedName(dottedName).Value;
                var resolved = TryResolveTypedefAsMember(alias);
                if (resolved is not null)
                {
                    return new(resolved);
                }
                ReportError(DiagnosticIds.TypedefNotFound, string.Format(DiagnosticMessageTemplates.TypedefNotFound, alias), context);
                return new(_entityRegistry.CreateLazilyRecordedMemberReference(_entityRegistry.ModuleType, alias, new BlobBuilder()));
            }
            BlobBuilder methodRefSignature = new();
            byte callConv = VisitCallConv(context.callConv()).Value;
            EntityRegistry.TypeEntity owner = _currentTypeDefinition.PeekOrDefault() ?? _entityRegistry.ModuleType;
            if (context.typeSpec() is CILParser.TypeSpecContext typeSpec)
            {
                owner = VisitTypeSpec(typeSpec).Value;
            }
            string name = VisitMethodName(context.methodName()).Value;
            BlobBuilder? methodSpecSignature = null;
            int numGenericParameters = 0;
            if (context.typeArgs() is CILParser.TypeArgsContext typeArgs)
            {
                var types = typeArgs.type();
                numGenericParameters = types.Length;
                if (types.Length != 0)
                {
                    methodSpecSignature = new();
                    methodSpecSignature.WriteByte((byte)SignatureKind.MethodSpecification);
                    VisitTypeArgs(typeArgs).Value.WriteContentTo(methodSpecSignature);
                }
            }
            else if (context.genArityNotEmpty() is CILParser.GenArityNotEmptyContext genArityNotEmpty)
            {
                numGenericParameters = VisitGenArityNotEmpty(genArityNotEmpty).Value;
            }
            if (numGenericParameters != 0)
            {
                callConv |= (byte)SignatureAttributes.Generic;
            }
            if (_expectInstance && (callConv & (byte)SignatureAttributes.Instance) == 0)
            {
                ReportWarning(DiagnosticIds.MissingInstanceCallConv,
                    DiagnosticMessageTemplates.MissingInstanceCallConv,
                    context);
                callConv |= (byte)SignatureAttributes.Instance;
            }
            methodRefSignature.WriteByte(callConv);
            if (numGenericParameters != 0)
            {
                methodRefSignature.WriteCompressedInteger(numGenericParameters);
            }
            var args = VisitSigArgs(context.sigArgs()).Value;
            methodRefSignature.WriteCompressedInteger(args.Length);
            // Write return type
            VisitType(context.type()).Value.WriteContentTo(methodRefSignature);
            // Write arg signatures
            foreach (var arg in args)
            {
                arg.SignatureBlob.WriteContentTo(methodRefSignature);
            }

            var memberRef = _entityRegistry.CreateLazilyRecordedMemberReference(owner, name, methodRefSignature);

            if (methodSpecSignature is not null)
            {
                return new(_entityRegistry.GetOrCreateMethodSpecification(memberRef, methodSpecSignature));
            }

            return new(memberRef);
        }
        public GrammarResult VisitModuleHead(CILParser.ModuleHeadContext context)
        {
            if (context.ChildCount > 2)
            {
                _ = _entityRegistry.GetOrCreateModuleReference(VisitDottedName(context.dottedName()).Value, _ => { });
                return GrammarResult.SentinelValue.Result;
            }

            _entityRegistry.Module.Name = VisitDottedName(context.dottedName()).Value;
            return GrammarResult.SentinelValue.Result;
        }

        // .mscorlib directive indicates the assembly being compiled is mscorlib itself.
        // This is currently a no-op; the flag would be used to affect type resolution
        // when support for compiling mscorlib is added.
        public GrammarResult VisitMscorlib(CILParser.MscorlibContext context) => GrammarResult.SentinelValue.Result;

        GrammarResult ICILVisitor<GrammarResult>.VisitNameSpaceHead(CILParser.NameSpaceHeadContext context) => VisitNameSpaceHead(context);

        public static GrammarResult.String VisitNameSpaceHead(CILParser.NameSpaceHeadContext context) => VisitDottedName(context.dottedName());

        GrammarResult ICILVisitor<GrammarResult>.VisitNameValPair(CILParser.NameValPairContext context) => VisitNameValPair(context);
        public GrammarResult.Literal<KeyValuePair<string, BlobBuilder>> VisitNameValPair(CILParser.NameValPairContext context)
        {
            return new(new(VisitCompQstring(context.compQstring()).Value, VisitCaValue(context.caValue()).Value));
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitNameValPairs(CILParser.NameValPairsContext context) => VisitNameValPairs(context);
        public GrammarResult.Sequence<KeyValuePair<string, BlobBuilder>> VisitNameValPairs(CILParser.NameValPairsContext context) => new(context.nameValPair().Select(pair => VisitNameValPair(pair).Value).ToImmutableArray());

        GrammarResult ICILVisitor<GrammarResult>.VisitNativeType(CILParser.NativeTypeContext context) => VisitNativeType(context);
        public GrammarResult.FormattedBlob VisitNativeType(CILParser.NativeTypeContext context)
        {
            if (context.nativeTypeArrayPointerInfo() is not CILParser.NativeTypeArrayPointerInfoContext[] arrayPointerInfo)
            {
                return VisitNativeTypeElement(context.nativeTypeElement());
            }
            var prefix = new BlobBuilder(arrayPointerInfo.Length);
            var elementType = VisitNativeTypeElement(context.nativeTypeElement()).Value;
            var suffix = new BlobBuilder();

            for (int i = arrayPointerInfo.Length - 1; i >= 0; i--)
            {
                if (arrayPointerInfo[i] is CILParser.PointerNativeTypeContext)
                {
                    ReportWarning(DiagnosticIds.DeprecatedNativeType,
                        string.Format(DiagnosticMessageTemplates.DeprecatedNativeType, "pointer in array"),
                        context);
                    const int NATIVE_TYPE_PTR = 0x10;
                    prefix.WriteByte(NATIVE_TYPE_PTR);
                }
                else
                {
                    prefix.WriteByte((byte)UnmanagedType.LPArray);
                    if (elementType.Count == 0)
                    {
                        // We need to have an element type for arrays,
                        // so write the invalid NATIVE_TYPE_MAX value so we have something parsable.
                        const int NATIVE_TYPE_MAX = 0x50;
                        elementType.WriteByte(NATIVE_TYPE_MAX);
                    }
                }
            }

            for (int i = 0; i < arrayPointerInfo.Length; i++)
            {
                if (arrayPointerInfo[i] is CILParser.PointerArrayTypeSizeContext size)
                {
                    suffix.WriteCompressedInteger(VisitInt32(size.int32()).Value);
                }
                else if (arrayPointerInfo[i] is CILParser.PointerArrayTypeSizeParamIndexContext sizeParamIndex)
                {
                    var ints = sizeParamIndex.int32();
                    suffix.WriteCompressedInteger(VisitInt32(ints[1]).Value);
                    suffix.WriteCompressedInteger(VisitInt32(ints[2]).Value);
                    suffix.WriteCompressedInteger(1); // Write that the paramIndex parameter was specified
                }
                else if (arrayPointerInfo[i] is CILParser.PointerArrayTypeParamIndexContext paramIndex)
                {
                    suffix.WriteCompressedInteger(0);
                    suffix.WriteCompressedInteger(VisitInt32(paramIndex.int32()).Value);
                    suffix.WriteCompressedInteger(0); // Write that the paramIndex parameter was not specified
                }
            }

            prefix.LinkSuffix(elementType);
            prefix.LinkSuffix(suffix);
            return new(prefix);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitNativeTypeElement(CILParser.NativeTypeElementContext context) => VisitNativeTypeElement(context);
        public GrammarResult.FormattedBlob VisitNativeTypeElement(CILParser.NativeTypeElementContext context)
        {
            var blob = new BlobBuilder(5);
            if (context.dottedName() is CILParser.DottedNameContext typedef)
            {
                // Native type typedefs are not yet fully supported
                // For now, report an error and return empty blob
                string alias = VisitDottedName(typedef).Value;
                ReportError(DiagnosticIds.TypedefNotFound, string.Format(DiagnosticMessageTemplates.TypedefNotFound, alias), context);
                return new(blob);
            }

            if (context.marshalType is null)
            {
                return new(blob);
            }

            switch (context.marshalType.Type)
            {
                case CILParser.CUSTOM:
                    {
                        blob.WriteByte((byte)UnmanagedType.CustomMarshaler);
                        CILParser.CompQstringContext[] strings = context.compQstring();
                        if (strings.Length == 4)
                        {
                            ReportWarning(DiagnosticIds.DeprecatedCustomMarshaller,
                                DiagnosticMessageTemplates.DeprecatedCustomMarshaller,
                                context);
                            blob.WriteSerializedString(VisitCompQstring(strings[0]).Value);
                            blob.WriteSerializedString(VisitCompQstring(strings[1]).Value);
                            blob.WriteSerializedString(VisitCompQstring(strings[2]).Value);
                            blob.WriteSerializedString(VisitCompQstring(strings[3]).Value);
                        }
                        else
                        {
                            Debug.Assert(strings.Length == 2);
                            blob.WriteCompressedInteger(0);
                            blob.WriteCompressedInteger(0);
                            blob.WriteSerializedString(VisitCompQstring(strings[0]).Value);
                            blob.WriteSerializedString(VisitCompQstring(strings[1]).Value);
                        }
                        break;
                    }
                case CILParser.SYSSTRING:
                    blob.WriteByte((byte)UnmanagedType.ByValTStr);
                    blob.WriteCompressedInteger(VisitInt32(context.int32()).Value);
                    break;
                case CILParser.ARRAY:
                    blob.WriteByte((byte)UnmanagedType.ByValArray);
                    blob.WriteCompressedInteger(VisitInt32(context.int32()).Value);
                    blob.LinkSuffix(VisitNativeType(context.nativeType()).Value);
                    break;
                case CILParser.VARIANT:
                    ReportWarning(DiagnosticIds.DeprecatedNativeType,
                        string.Format(DiagnosticMessageTemplates.DeprecatedNativeType, "VARIANT"),
                        context);
                    const int NATIVE_TYPE_VARIANT = 0xe;
                    blob.WriteByte(NATIVE_TYPE_VARIANT);
                    break;
#pragma warning disable CS0618 // Type or member is obsolete
                case CILParser.CURRENCY:
                    blob.WriteByte((byte)UnmanagedType.Currency);
                    break;
#pragma warning restore CS0618 // Type or member is obsolete
                case CILParser.SYSCHAR:
                    ReportWarning(DiagnosticIds.DeprecatedNativeType,
                        string.Format(DiagnosticMessageTemplates.DeprecatedNativeType, "SYSCHAR"),
                        context);
                    const int NATIVE_TYPE_SYSCHAR = 0xd;
                    blob.WriteByte(NATIVE_TYPE_SYSCHAR);
                    break;
                case CILParser.VOID:
                    ReportWarning(DiagnosticIds.DeprecatedNativeType,
                        string.Format(DiagnosticMessageTemplates.DeprecatedNativeType, "VOID"),
                        context);
                    const int NATIVE_TYPE_VOID = 0x1;
                    blob.WriteByte(NATIVE_TYPE_VOID);
                    break;
                case CILParser.BOOL:
                    blob.WriteByte((byte)UnmanagedType.Bool);
                    break;
                case CILParser.INT8:
                    blob.WriteByte((byte)UnmanagedType.I1);
                    break;
                case CILParser.INT16:
                    blob.WriteByte((byte)UnmanagedType.I2);
                    break;
                case CILParser.INT32_:
                    blob.WriteByte((byte)UnmanagedType.I4);
                    break;
                case CILParser.INT64_:
                    blob.WriteByte((byte)UnmanagedType.I8);
                    break;
                case CILParser.FLOAT32:
                    blob.WriteByte((byte)UnmanagedType.R4);
                    break;
                case CILParser.FLOAT64_:
                    blob.WriteByte((byte)UnmanagedType.R8);
                    break;
                case CILParser.ERROR:
                    blob.WriteByte((byte)UnmanagedType.Error);
                    break;
                case CILParser.UINT8:
                    blob.WriteByte((byte)UnmanagedType.U1);
                    break;
                case CILParser.UINT16:
                    blob.WriteByte((byte)UnmanagedType.U2);
                    break;
                case CILParser.UINT32:
                    blob.WriteByte((byte)UnmanagedType.U4);
                    break;
                case CILParser.UINT64:
                    blob.WriteByte((byte)UnmanagedType.U8);
                    break;
                case CILParser.DECIMAL:
                    ReportWarning(DiagnosticIds.DeprecatedNativeType,
                        string.Format(DiagnosticMessageTemplates.DeprecatedNativeType, "DECIMAL"),
                        context);
                    const int NATIVE_TYPE_DECIMAL = 0x11;
                    blob.WriteByte(NATIVE_TYPE_DECIMAL);
                    break;
                case CILParser.DATE:
                    ReportWarning(DiagnosticIds.DeprecatedNativeType,
                        string.Format(DiagnosticMessageTemplates.DeprecatedNativeType, "DATE"),
                        context);
                    const int NATIVE_TYPE_DATE = 0x12;
                    blob.WriteByte(NATIVE_TYPE_DATE);
                    break;
                case CILParser.BSTR:
                    blob.WriteByte((byte)UnmanagedType.BStr);
                    break;
                case CILParser.LPSTR:
                    blob.WriteByte((byte)UnmanagedType.LPStr);
                    break;
                case CILParser.LPWSTR:
                    blob.WriteByte((byte)UnmanagedType.LPWStr);
                    break;
                case CILParser.LPTSTR:
                    blob.WriteByte((byte)UnmanagedType.LPTStr);
                    break;
                case CILParser.OBJECTREF:
                    ReportWarning(DiagnosticIds.DeprecatedNativeType,
                        string.Format(DiagnosticMessageTemplates.DeprecatedNativeType, "OBJECTREF"),
                        context);
                    const int NATIVE_TYPE_OBJECTREF = 0x18;
                    blob.WriteByte(NATIVE_TYPE_OBJECTREF);
                    break;
                case CILParser.IUNKNOWN:
                    {
                        blob.WriteByte((byte)UnmanagedType.IUnknown);
                        if (VisitIidParamIndex(context.iidParamIndex()) is { Value: int index })
                        {
                            blob.WriteCompressedInteger(index);
                        }
                        break;
                    }
                case CILParser.IDISPATCH:
                    {
                        blob.WriteByte((byte)UnmanagedType.IDispatch);
                        if (VisitIidParamIndex(context.iidParamIndex()) is { Value: int index })
                        {
                            blob.WriteCompressedInteger(index);
                        }
                        break;
                    }
                case CILParser.STRUCT:
                    blob.WriteByte((byte)UnmanagedType.Struct);
                    break;
                case CILParser.INTERFACE:
                    {
                        blob.WriteByte((byte)UnmanagedType.Interface);
                        if (VisitIidParamIndex(context.iidParamIndex()) is { Value: int index })
                        {
                            blob.WriteCompressedInteger(index);
                        }
                        break;
                    }
                case CILParser.SAFEARRAY:
                    blob.WriteByte((byte)UnmanagedType.SafeArray);
                    blob.WriteCompressedInteger((int)VisitVariantType(context.variantType()).Value);
                    if (context.compQstring() is { Length: 1 } safeArrayCustomType)
                    {
                        string str = VisitCompQstring(safeArrayCustomType[0]).Value;
                        blob.WriteSerializedString(str);
                    }
                    else
                    {
                        blob.WriteCompressedInteger(0);
                    }
                    break;
                case CILParser.INT:
                    blob.WriteByte((byte)UnmanagedType.SysInt);
                    break;
                case CILParser.UINT:
                    blob.WriteByte((byte)UnmanagedType.SysUInt);
                    break;
                case CILParser.NESTEDSTRUCT:
                    ReportWarning(DiagnosticIds.DeprecatedNativeType,
                        string.Format(DiagnosticMessageTemplates.DeprecatedNativeType, "NESTEDSTRUCT"),
                        context);
                    const int NATIVE_TYPE_NESTEDSTRUCT = 0x21;
                    blob.WriteByte(NATIVE_TYPE_NESTEDSTRUCT);
                    break;
#pragma warning disable CS0618 // Type or member is obsolete
                case CILParser.BYVALSTR:
                    blob.WriteByte((byte)UnmanagedType.VBByRefStr);
                    break;
                case CILParser.ANSIBSTR:
                    blob.WriteByte((byte)UnmanagedType.AnsiBStr);
                    break;
                case CILParser.TBSTR:
                    blob.WriteByte((byte)UnmanagedType.TBStr);
                    break;
#pragma warning restore CS0618 // Type or member is obsolete
                case CILParser.VARIANTBOOL:
                    blob.WriteByte((byte)UnmanagedType.VariantBool);
                    break;
                case CILParser.METHOD:
                    blob.WriteByte((byte)UnmanagedType.FunctionPtr);
                    break;
                case CILParser.LPSTRUCT:
                    blob.WriteByte((byte)UnmanagedType.LPStruct);
                    break;
#pragma warning disable CS0618 // Type or member is obsolete
                case CILParser.ANY:
                    blob.WriteByte((byte)UnmanagedType.AsAny);
                    break;
#pragma warning restore CS0618 // Type or member is obsolete
            }

            return new(blob);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitObjSeq(CILParser.ObjSeqContext context) => VisitObjSeq(context);
        public GrammarResult.FormattedBlob VisitObjSeq(CILParser.ObjSeqContext context)
        {
            // We're going to add all of the elements in the sequence as prefix blobs to this blob.
            BlobBuilder objSeqBlob = new(0);
            foreach (var item in context.serInit())
            {
                objSeqBlob.LinkPrefix(VisitSerInit(item).Value);
            }
            return new(objSeqBlob);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitOwnerType(CILParser.OwnerTypeContext context) => VisitOwnerType(context);
        public GrammarResult.Literal<EntityRegistry.EntityBase> VisitOwnerType(CILParser.OwnerTypeContext context)
        {
            if (context.memberRef() is CILParser.MemberRefContext memberRef)
            {
                return VisitMemberRef(memberRef);
            }
            if (context.typeSpec() is CILParser.TypeSpecContext typeSpec)
            {
                return new(VisitTypeSpec(typeSpec).Value);
            }
            throw new UnreachableException();
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitParamAttr(CILParser.ParamAttrContext context) => VisitParamAttr(context);
        public GrammarResult.Literal<ParameterAttributes> VisitParamAttr(CILParser.ParamAttrContext context)
        {
            ParameterAttributes attributes = 0;
            foreach (var element in context.paramAttrElement())
            {
                attributes |= VisitParamAttrElement(element);
            }
            return new(attributes);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitParamAttrElement(CILParser.ParamAttrElementContext context) => VisitParamAttrElement(context);
        public GrammarResult.Flag<ParameterAttributes> VisitParamAttrElement(CILParser.ParamAttrElementContext context)
        {
            if (context.int32() is CILParser.Int32Context int32)
            {
                return new((ParameterAttributes)(VisitInt32(int32).Value + 1), ShouldAppend: false);
            }
            return context switch
            {
                { @in: not null } => new(ParameterAttributes.In),
                { @out: not null } => new(ParameterAttributes.Out),
                { opt: not null } => new(ParameterAttributes.Optional),
                _ => throw new UnreachableException()
            };
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitPinvAttr(CILParser.PinvAttrContext context) => VisitPinvAttr(context);
        public GrammarResult.Flag<MethodImportAttributes> VisitPinvAttr(CILParser.PinvAttrContext context)
        {
            if (context.int32() is CILParser.Int32Context int32)
            {
                return new((MethodImportAttributes)VisitInt32(int32).Value, ShouldAppend: false);
            }
            switch (context.GetText())
            {
                case "nomangle":
                    return new(MethodImportAttributes.ExactSpelling);
                case "ansi":
                    return new(MethodImportAttributes.CharSetAnsi);
                case "unicode":
                    return new(MethodImportAttributes.CharSetUnicode);
                case "autochar":
                    return new(MethodImportAttributes.CharSetAuto);
                case "lasterr":
                    return new(MethodImportAttributes.SetLastError);
                case "winapi":
                    return new(MethodImportAttributes.CallingConventionWinApi);
                case "cdecl":
                    return new(MethodImportAttributes.CallingConventionCDecl);
                case "stdcall":
                    return new(MethodImportAttributes.CallingConventionStdCall);
                case "thiscall":
                    return new(MethodImportAttributes.CallingConventionThisCall);
                case "fastcall":
                    return new(MethodImportAttributes.CallingConventionFastCall);
                case "bestfit:on":
                    return new(MethodImportAttributes.BestFitMappingEnable);
                case "bestfit:off":
                    return new(MethodImportAttributes.BestFitMappingDisable);
                case "charmaperror:on":
                    return new(MethodImportAttributes.ThrowOnUnmappableCharEnable);
                case "charmaperror:off":
                    return new(MethodImportAttributes.ThrowOnUnmappableCharDisable);
                default:
                    throw new UnreachableException();
            }
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitPinvImpl(CILParser.PinvImplContext context) => VisitPinvImpl(context);
        public GrammarResult.Literal<(string? ModuleName, string? EntryPointName, MethodImportAttributes Attributes)> VisitPinvImpl(CILParser.PinvImplContext context)
        {
            MethodImportAttributes attrs = MethodImportAttributes.None;
            foreach (var attr in context.pinvAttr())
            {
                attrs |= VisitPinvAttr(attr);
            }
            var names = context.compQstring();
            string? moduleName = names.Length > 0 ? VisitCompQstring(names[0]).Value : null;
            string? entryPointName = names.Length > 1 ? VisitCompQstring(names[1]).Value : null;
            return new((moduleName, entryPointName, attrs));
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitPropAttr(CILParser.PropAttrContext context) => VisitPropAttr(context);
        public static GrammarResult.Flag<PropertyAttributes> VisitPropAttr(CILParser.PropAttrContext context)
        {
            return context.GetText() switch
            {
                "specialname" => new(PropertyAttributes.SpecialName),
                "rtspecialname" => new(0), // COMPAT: Ignore
                _ => throw new UnreachableException(),
            };
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitPropDecl(CILParser.PropDeclContext context) => VisitPropDecl(context);
        public GrammarResult.Literal<(MethodSemanticsAttributes, EntityRegistry.EntityBase)?> VisitPropDecl(CILParser.PropDeclContext context)
        {
            if (context.ChildCount != 2)
            {
                return new(null);
            }
            string accessor = context.GetChild(0).GetText();
            EntityRegistry.EntityBase memberReference = VisitMethodRef(context.methodRef()).Value;
            MethodSemanticsAttributes methodSemanticsAttributes = accessor switch
            {
                ".set" => MethodSemanticsAttributes.Setter,
                ".get" => MethodSemanticsAttributes.Getter,
                ".other" => MethodSemanticsAttributes.Other,
                _ => throw new UnreachableException(),
            };
            return new((methodSemanticsAttributes, memberReference));
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitPropDecls(CILParser.PropDeclsContext context) => VisitPropDecls(context);
        public GrammarResult.Sequence<(MethodSemanticsAttributes, EntityRegistry.EntityBase)> VisitPropDecls(CILParser.PropDeclsContext context)
            => new(
                context.propDecl()
                .Select(decl => VisitPropDecl(decl).Value)
                .Where(decl => decl is not null)
                .Select(decl => decl!.Value).ToImmutableArray());

        GrammarResult ICILVisitor<GrammarResult>.VisitPropHead(ILAssembler.CILParser.PropHeadContext context) => VisitPropHead(context);
        public GrammarResult.Literal<EntityRegistry.PropertyEntity> VisitPropHead(CILParser.PropHeadContext context)
        {
            var propAttrs = context.propAttr().Select(VisitPropAttr).Aggregate((PropertyAttributes)0, (a, b) => a | b);
            var name = VisitDottedName(context.dottedName()).Value;

            var signature = new BlobBuilder();
            byte callConv = (byte)(VisitCallConv(context.callConv()).Value | (byte)SignatureKind.Property);
            signature.WriteByte(callConv);
            var args = VisitSigArgs(context.sigArgs()).Value;
            signature.WriteCompressedInteger(args.Length);
            VisitType(context.type()).Value.WriteContentTo(signature);
            foreach (var arg in args)
            {
                arg.SignatureBlob.WriteContentTo(signature);
            }

            // Handle initOpt to set the Constant table entry if a constant value is provided.
            var constantValue = VisitInitOpt(context.initOpt()).Value;
            var property = new EntityRegistry.PropertyEntity(propAttrs, signature, name);
            if (constantValue is not NoConstantSentinel)
            {
                property.ConstantValue = constantValue;
                property.HasConstant = true;
                property.Attributes |= PropertyAttributes.HasDefault;
            }
            return new(property);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitRepeatOpt(CILParser.RepeatOptContext context) => VisitRepeatOpt(context);
        public GrammarResult.Literal<int?> VisitRepeatOpt(CILParser.RepeatOptContext context) => context.int32() is {} int32 ? new(VisitInt32(int32).Value) : new(null);

        public GrammarResult VisitScopeBlock(CILParser.ScopeBlockContext context)
        {
            int numLocalsScopes = _currentMethod!.LocalsScopes.Count;
            _ = VisitMethodDecls(context.methodDecls());
            _currentMethod.LocalsScopes.RemoveRange(numLocalsScopes, _currentMethod.LocalsScopes.Count - numLocalsScopes);
            return GrammarResult.SentinelValue.Result;
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitSecAction(CILParser.SecActionContext context) => VisitSecAction(context);
        public static GrammarResult.Literal<DeclarativeSecurityAction> VisitSecAction(CILParser.SecActionContext context)
        {
            return context.GetText() switch
            {
                "request" => new(DeclarativeSecurityAction.Request),
                "demand" => new(DeclarativeSecurityAction.Demand),
                "assert" => new(DeclarativeSecurityAction.Assert),
                "deny" => new(DeclarativeSecurityAction.Deny),
                "permitonly" => new(DeclarativeSecurityAction.PermitOnly),
                "linkcheck" => new(DeclarativeSecurityAction.LinkDemand),
                "inheritcheck" => new(DeclarativeSecurityAction.InheritanceDemand),
                "reqmin" => new(DeclarativeSecurityAction.RequestMinimum),
                "reqopt" => new(DeclarativeSecurityAction.RequestOptional),
                "reqrefuse" => new(DeclarativeSecurityAction.RequestRefuse),
                "prejitgrant" => new(DeclarativeSecurityAction.PrejitGrant),
                "prejitdeny" => new(DeclarativeSecurityAction.PrejitDeny),
                "noncasdemand" => new(DeclarativeSecurityAction.NonCasDemand),
                "noncaslinkdemand" => new(DeclarativeSecurityAction.NonCasLinkDemand),
                "noncasinheritance" => new(DeclarativeSecurityAction.NonCasInheritanceDemand),
                _ => throw new UnreachableException()
            };
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitSecAttrBlob(CILParser.SecAttrBlobContext context) => VisitSecAttrBlob(context);
        public GrammarResult.FormattedBlob VisitSecAttrBlob(CILParser.SecAttrBlobContext context)
        {
            var blob = new BlobBuilder();

            string attributeName = string.Empty;

            if (context.typeSpec() is CILParser.TypeSpecContext typeSpec && VisitTypeSpec(typeSpec).Value is EntityRegistry.IHasReflectionNotation reflectionNotation)
            {
                attributeName = reflectionNotation.ReflectionNotation;
            }
            else if (context.SQSTRING() is { } sqstring)
            {
                attributeName = sqstring.GetText();
            }

            blob.WriteSerializedString(attributeName);
            VisitCustomBlobNVPairs(context.customBlobNVPairs()).Value.WriteContentTo(blob);

            return new(blob);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitSecAttrSetBlob(CILParser.SecAttrSetBlobContext context) => VisitSecAttrSetBlob(context);
        public GrammarResult.FormattedBlob VisitSecAttrSetBlob(CILParser.SecAttrSetBlobContext context)
        {
            BlobBuilder blob = new();
            var secAttributes = context.secAttrBlob();
            blob.WriteByte((byte)'.');
            blob.WriteCompressedInteger(secAttributes.Length);
            foreach (var secAttribute in secAttributes)
            {
                VisitSecAttrBlob(secAttribute).Value.WriteContentTo(blob);
            }
            return new(blob);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitSecDecl(CILParser.SecDeclContext context) => VisitSecDecl(context);
        public GrammarResult.Literal<EntityRegistry.DeclarativeSecurityAttributeEntity?> VisitSecDecl(CILParser.SecDeclContext context)
        {
            if (context.PERMISSION() is not null)
            {
                ReportError(DiagnosticIds.UnsupportedSecurityDeclaration,
                    DiagnosticMessageTemplates.UnsupportedSecurityDeclaration,
                    context);
                return new(null);
            }
            DeclarativeSecurityAction action = VisitSecAction(context.secAction()).Value;
            BlobBuilder value;
            if (context.secAttrSetBlob() is CILParser.SecAttrSetBlobContext setBlob)
            {
                value = VisitSecAttrSetBlob(setBlob).Value;
            }
            else if (context.bytes() is CILParser.BytesContext bytes)
            {
                value = new();
                value.WriteBytes(VisitBytes(bytes).Value);
            }
            else if (context.compQstring() is CILParser.CompQstringContext str)
            {
                value = new BlobBuilder();
                value.WriteUTF16(VisitCompQstring(str).Value);
                value.WriteUTF16("\0");
            }
            else
            {
                throw new UnreachableException();
            }
            return new(_entityRegistry.CreateDeclarativeSecurityAttribute(action, value));
        }

        internal abstract record ExceptionClause(LabelHandle Start, LabelHandle End)
        {
            internal sealed record Catch(EntityRegistry.TypeEntity Type, LabelHandle Start, LabelHandle End) : ExceptionClause(Start, End);

            internal sealed record Filter(LabelHandle FilterStart, LabelHandle Start, LabelHandle End) : ExceptionClause(Start, End);

            internal sealed record Finally(LabelHandle Start, LabelHandle End) : ExceptionClause(Start, End);

            internal sealed record Fault(LabelHandle Start, LabelHandle End) : ExceptionClause(Start, End);
        }

        public GrammarResult VisitSehBlock(CILParser.SehBlockContext context)
        {
            var (tryStart, tryEnd) = VisitTryBlock(context.tryBlock()).Value;
            foreach (var clause in VisitSehClauses(context.sehClauses()).Value)
            {
                switch (clause)
                {
                    case ExceptionClause.Finally finallyClause:
                        _currentMethod!.Definition.MethodBody.ControlFlowBuilder!.AddFinallyRegion(tryStart, tryEnd, finallyClause.Start, finallyClause.End);
                        break;
                    case ExceptionClause.Fault faultClause:
                        _currentMethod!.Definition.MethodBody.ControlFlowBuilder!.AddFaultRegion(tryStart, tryEnd, faultClause.Start, faultClause.End);
                        break;
                    case ExceptionClause.Catch catchClause:
                        _currentMethod!.Definition.MethodBody.ControlFlowBuilder!.AddCatchRegion(tryStart, tryEnd, catchClause.Start, catchClause.End, catchClause.Type.Handle);
                        break;
                    case ExceptionClause.Filter filterClause:
                        _currentMethod!.Definition.MethodBody.ControlFlowBuilder!.AddFilterRegion(tryStart, tryEnd, filterClause.Start, filterClause.End, filterClause.FilterStart);
                        break;
                    default:
                        throw new UnreachableException();
                }
            }
            return GrammarResult.SentinelValue.Result;
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitSehClause(CILParser.SehClauseContext context) => VisitSehClause(context);
        public GrammarResult.Literal<ExceptionClause> VisitSehClause(CILParser.SehClauseContext context)
        {
            var (start, end) = VisitHandlerBlock(context.handlerBlock()).Value;

            if (context.finallyClause() is not null)
            {
                return new(new ExceptionClause.Finally(start, end));
            }
            if (context.faultClause() is not null)
            {
                return new(new ExceptionClause.Fault(start, end));
            }
            if (context.catchClause() is CILParser.CatchClauseContext catchClause)
            {
                return new(new ExceptionClause.Catch(VisitCatchClause(catchClause).Value, start, end));
            }
            if (context.filterClause() is CILParser.FilterClauseContext filterClause)
            {
                return new(new ExceptionClause.Filter(VisitFilterClause(filterClause).Value, start, end));
            }

            throw new UnreachableException();
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitSehClauses(CILParser.SehClausesContext context) => VisitSehClauses(context);
        public GrammarResult.Sequence<ExceptionClause> VisitSehClauses(CILParser.SehClausesContext context) => new(context.sehClause().Select(clause => VisitSehClause(clause).Value).ToImmutableArray());

        GrammarResult ICILVisitor<GrammarResult>.VisitSerializType(CILParser.SerializTypeContext context) => VisitSerializType(context);
        public GrammarResult.FormattedBlob VisitSerializType(CILParser.SerializTypeContext context)
        {
            var blob = new BlobBuilder();
            if (context.ARRAY_TYPE_NO_BOUNDS() is not null)
            {
                blob.WriteByte((byte)SerializationTypeCode.SZArray);
            }
            VisitSerializTypeElement(context.serializTypeElement()).Value.WriteContentTo(blob);
            return new(blob);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitSerializTypeElement(CILParser.SerializTypeElementContext context) => VisitSerializTypeElement(context);
        public GrammarResult.FormattedBlob VisitSerializTypeElement(CILParser.SerializTypeElementContext context)
        {
            if (context.simpleType() is CILParser.SimpleTypeContext simpleType)
            {
                BlobBuilder blob = new(1);
                blob.WriteByte((byte)VisitSimpleType(simpleType).Value);
                return new(blob);
            }
            if (context.dottedName() is CILParser.DottedNameContext dottedName)
            {
                // Serialization type typedefs are not yet fully supported
                string alias = VisitDottedName(dottedName).Value;
                ReportError(DiagnosticIds.TypedefNotFound, string.Format(DiagnosticMessageTemplates.TypedefNotFound, alias), context);
                return new(new BlobBuilder(1));
            }
            if (context.TYPE() is not null)
            {
                BlobBuilder blob = new BlobBuilder(1);
                blob.WriteByte((byte)SerializationTypeCode.Type);
                return new(blob);
            }
            if (context.OBJECT() is not null)
            {
                BlobBuilder blob = new BlobBuilder(1);
                blob.WriteByte((byte)SerializationTypeCode.TaggedObject);
                return new(blob);
            }
            if (context.ENUM() is not null)
            {
                BlobBuilder blob = new BlobBuilder();
                blob.WriteByte((byte)SerializationTypeCode.Enum);
                if (context.SQSTRING() is ITerminalNode sqString)
                {
                    blob.WriteSerializedString(sqString.GetText());
                }
                else
                {
                    Debug.Assert(context.className() is not null);
                    blob.WriteSerializedString((VisitClassName(context.className()).Value as EntityRegistry.IHasReflectionNotation)?.ReflectionNotation ?? "");
                }
                return new(blob);
            }
            throw new UnreachableException();
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitSerInit(CILParser.SerInitContext context) => VisitSerInit(context);
        public GrammarResult.FormattedBlob VisitSerInit(CILParser.SerInitContext context)
        {
            if (context.fieldSerInit() is CILParser.FieldSerInitContext fieldSerInit)
            {
                return VisitFieldSerInit(fieldSerInit);
            }

            if (context.serInit() is CILParser.SerInitContext serInit)
            {
                Debug.Assert(context.OBJECT() is not null);
                BlobBuilder taggedObjectBlob = new(1);
                taggedObjectBlob.WriteByte((byte)SerializationTypeCode.TaggedObject);
                taggedObjectBlob.LinkSuffix(VisitSerInit(serInit).Value);
                return new(taggedObjectBlob);
            }

            if (context.int32() is not CILParser.Int32Context arrLength)
            {
                // The only cases where there is no int32 node is when the value is a string or type.
                BlobBuilder blob = new();
                blob.WriteByte((byte)GetTypeCodeForToken(((ITerminalNode)context.GetChild(0)).Symbol.Type));
                if (context.className() is CILParser.ClassNameContext className)
                {
                    blob.WriteSerializedString(VisitClassName(className).Value is EntityRegistry.IHasReflectionNotation reflection ? reflection.ReflectionNotation : string.Empty);
                }
                else
                {
                    blob.WriteSerializedString(context.SQSTRING()?.Symbol.Text);
                }
                return new(blob);
            }

            int tokenType = ((ITerminalNode)context.GetChild(0)).Symbol.Type;

            // 1 byte for ELEMENT_TYPE_SZARRAY, 1 byte for the array element type, 4 bytes for the length.
            BlobBuilder arrayHeader = new(6);
            arrayHeader.WriteByte((byte)SerializationTypeCode.SZArray);
            arrayHeader.WriteByte((byte)GetTypeCodeForToken(tokenType));
            arrayHeader.WriteInt32(VisitInt32(arrLength).Value);
            var sequenceResult = (GrammarResult.FormattedBlob)Visit(context.GetRuleContext<ParserRuleContext>(0));
            arrayHeader.LinkSuffix(sequenceResult.Value);
            return new(arrayHeader);
        }

        private static SerializationTypeCode GetTypeCodeForToken(int tokenType)
        {
            return tokenType switch
            {
                CILParser.INT8 => SerializationTypeCode.SByte,
                CILParser.UINT8 => SerializationTypeCode.Byte,
                CILParser.INT16 => SerializationTypeCode.Int16,
                CILParser.UINT16 => SerializationTypeCode.UInt16,
                CILParser.INT32_ => SerializationTypeCode.Int32,
                CILParser.UINT32 => SerializationTypeCode.UInt32,
                CILParser.INT64_ => SerializationTypeCode.Int64,
                CILParser.UINT64 => SerializationTypeCode.UInt64,
                CILParser.FLOAT32 => SerializationTypeCode.Single,
                CILParser.FLOAT64_ => SerializationTypeCode.Double,
                CILParser.CHAR => SerializationTypeCode.Char,
                CILParser.BOOL => SerializationTypeCode.Boolean,
                CILParser.STRING => SerializationTypeCode.String,
                CILParser.TYPE => SerializationTypeCode.Type,
                CILParser.OBJECT => SerializationTypeCode.TaggedObject,
                _ => throw new UnreachableException()
            };
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitSigArg(CILParser.SigArgContext context) => VisitSigArg(context);
        public GrammarResult.Literal<SignatureArg> VisitSigArg(CILParser.SigArgContext context)
        {
            if (context.ELLIPSIS() is not null)
            {
                return new(SignatureArg.CreateSentinelArgument());
            }
            return new(new SignatureArg(
                VisitParamAttr(context.paramAttr()).Value,
                VisitType(context.type()).Value,
                VisitMarshalClause(context.marshalClause()).Value,
                context.id() is CILParser.IdContext id ? VisitId(id).Value : null));
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitSigArgs(CILParser.SigArgsContext context) => VisitSigArgs(context);
        public GrammarResult.Sequence<SignatureArg> VisitSigArgs(CILParser.SigArgsContext context) => new(ImmutableArray.CreateRange(context.sigArg().Select(arg => VisitSigArg(arg).Value)));
        GrammarResult ICILVisitor<GrammarResult>.VisitSimpleType(CILParser.SimpleTypeContext context) => VisitSimpleType(context);
        public GrammarResult.Literal<SignatureTypeCode> VisitSimpleType(CILParser.SimpleTypeContext context)
        {
            return new(context.GetChild<ITerminalNode>(0).Symbol.Type switch
            {
                CILParser.CHAR => SignatureTypeCode.Char,
                CILParser.STRING => SignatureTypeCode.String,
                CILParser.BOOL => SignatureTypeCode.Boolean,
                CILParser.INT8 => SignatureTypeCode.SByte,
                CILParser.INT16 => SignatureTypeCode.Int16,
                CILParser.INT32_ => SignatureTypeCode.Int32,
                CILParser.INT64_ => SignatureTypeCode.Int64,
                CILParser.FLOAT32 => SignatureTypeCode.Single,
                CILParser.FLOAT64_ => SignatureTypeCode.Double,
                CILParser.UINT8 => SignatureTypeCode.Byte,
                CILParser.UINT16 => SignatureTypeCode.UInt16,
                CILParser.UINT32 => SignatureTypeCode.UInt32,
                CILParser.UINT64 => SignatureTypeCode.UInt64,
                _ => throw new UnreachableException()
            });
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitSlashedName(CILParser.SlashedNameContext context)
        {
            return VisitSlashedName(context);
        }

        public static GrammarResult.Literal<TypeName> VisitSlashedName(CILParser.SlashedNameContext context)
        {
            TypeName? currentTypeName = null;
            foreach (var item in context.dottedName())
            {
                currentTypeName = new TypeName(currentTypeName, VisitDottedName(item).Value);
            }
            // We'll always have at least one dottedName, so the value here will be non-null
            return new(currentTypeName!);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitSqstringSeq(CILParser.SqstringSeqContext context) => VisitSqstringSeq(context);

        public static GrammarResult.FormattedBlob VisitSqstringSeq(CILParser.SqstringSeqContext context)
        {
            var strings = ImmutableArray.CreateBuilder<string?>(context.ChildCount);
            foreach (var child in context.children)
            {
                string? str = null;

                if (child is ITerminalNode { Symbol: { Type: CILParser.SQSTRING, Text: string stringValue } })
                {
                    str = stringValue;
                }

                strings.Add(str);
            }
            return new(strings.ToImmutable().SerializeSequence());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitStackreserve(CILParser.StackreserveContext context) => VisitStackreserve(context);
        public GrammarResult.Literal<long> VisitStackreserve(CILParser.StackreserveContext context) => VisitInt64(context.int64());

        GrammarResult ICILVisitor<GrammarResult>.VisitSubsystem(CILParser.SubsystemContext context) => VisitSubsystem(context);
        public GrammarResult.Literal<int> VisitSubsystem(CILParser.SubsystemContext context) => VisitInt32(context.int32());

        public GrammarResult VisitTerminal(ITerminalNode node) => throw new UnreachableException();
        public GrammarResult VisitTls(CILParser.TlsContext context)
        {
            // TODO-SRM: System.Reflection.Metadata doesn't provide APIs to point a data declaration at a TLS slot or into the IL stream.
            // We have tests for the TLS case (CoreCLR only supports it on Win-x86), but not for the IL case.
            return GrammarResult.SentinelValue.Result;
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitTruefalse(CILParser.TruefalseContext context) => VisitTruefalse(context);

        public static GrammarResult.Literal<bool> VisitTruefalse(CILParser.TruefalseContext context)
        {
            return new(bool.Parse(context.GetText()));
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitTryBlock(CILParser.TryBlockContext context) => VisitTryBlock(context);

        public GrammarResult.Literal<(LabelHandle Start, LabelHandle End)> VisitTryBlock(CILParser.TryBlockContext context)
        {
            if (context.scopeBlock() is CILParser.ScopeBlockContext scopeBlock)
            {
                LabelHandle start = _currentMethod!.Definition.MethodBody.DefineLabel();
                _currentMethod.Definition.MethodBody.MarkLabel(start);
                _ = VisitScopeBlock(scopeBlock);
                LabelHandle end = _currentMethod.Definition.MethodBody.DefineLabel();
                _currentMethod.Definition.MethodBody.MarkLabel(end);
                return new((start, end));
            }
            var ids = context.id();
            if (ids.Length == 2)
            {
                var start = _currentMethod!.Labels.TryGetValue(VisitId(ids[0]).Value, out LabelHandle startLabel) ? startLabel : _currentMethod.Labels[VisitId(ids[0]).Value] = _currentMethod.Definition.MethodBody.DefineLabel();
                var end = _currentMethod!.Labels.TryGetValue(VisitId(ids[1]).Value, out LabelHandle endLabel) ? endLabel : _currentMethod.Labels[VisitId(ids[1]).Value] = _currentMethod.Definition.MethodBody.DefineLabel();
                return new((start, end));
            }
            var offsets = context.int32();
            if (offsets.Length == 2)
            {
                var start = _currentMethod!.Definition.MethodBody.DefineLabel();
                var end = _currentMethod.Definition.MethodBody.DefineLabel();
                _currentMethod.Definition.MethodBody.MarkLabel(start, VisitInt32(offsets[0]).Value);
                _currentMethod.Definition.MethodBody.MarkLabel(end, VisitInt32(offsets[1]).Value);
                return new((start, end));
            }
            throw new UnreachableException();
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitTyBound(CILParser.TyBoundContext context) => VisitTyBound(context);
        public GrammarResult.Sequence<EntityRegistry.GenericParameterConstraintEntity> VisitTyBound(CILParser.TyBoundContext? context)
        {
            // context or typeList can be null when there are no constraints
            if (context?.typeList() is not CILParser.TypeListContext typeList)
            {
                return new(ImmutableArray<EntityRegistry.GenericParameterConstraintEntity>.Empty);
            }
            // Filter out null types (from unresolved type parameters) before creating constraints
            return new(VisitTypeList(typeList).Value
                .Where(t => t is not null)
                .Select(EntityRegistry.CreateGenericConstraint)
                .ToImmutableArray());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitTypar(CILParser.TyparContext context) => VisitTypar(context);

        public GrammarResult.Literal<EntityRegistry.GenericParameterEntity> VisitTypar(CILParser.TyparContext context)
        {
            GenericParameterAttributes attributes = VisitTyparAttribs(context.typarAttribs()).Value;
            EntityRegistry.GenericParameterEntity genericParameter = EntityRegistry.CreateGenericParameter(attributes, VisitDottedName(context.dottedName()).Value);

            foreach (var constraint in VisitTyBound(context.tyBound()).Value)
            {
                genericParameter.Constraints.Add(constraint);
            }

            return new(genericParameter);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitTyparAttrib(CILParser.TyparAttribContext context) => VisitTyparAttrib(context);
        public GrammarResult.Flag<GenericParameterAttributes> VisitTyparAttrib(CILParser.TyparAttribContext context)
        {
            return context switch
            {
                { covariant: not null } => new(GenericParameterAttributes.Covariant),
                { contravariant: not null } => new(GenericParameterAttributes.Contravariant),
                { @class: not null } => new(GenericParameterAttributes.ReferenceTypeConstraint),
                { valuetype: not null } => new(GenericParameterAttributes.NotNullableValueTypeConstraint),
                { byrefLike: not null } => new(GenericParameterAttributes.AllowByRefLike),
                { ctor: not null } => new(GenericParameterAttributes.DefaultConstructorConstraint),
                { flags: CILParser.Int32Context int32 } => new((GenericParameterAttributes)VisitInt32(int32).Value),
                _ => throw new UnreachableException()
            };
        }
        GrammarResult ICILVisitor<GrammarResult>.VisitTyparAttribs(CILParser.TyparAttribsContext context) => VisitTyparAttribs(context);

        public GrammarResult.Literal<GenericParameterAttributes> VisitTyparAttribs(CILParser.TyparAttribsContext context) =>
            new(context.typarAttrib()
                .Select(VisitTyparAttrib)
                .Aggregate(
                    (GenericParameterAttributes)0, (agg, attr) => agg | attr));

        GrammarResult ICILVisitor<GrammarResult>.VisitTypars(CILParser.TyparsContext context) => VisitTypars(context);
        public GrammarResult.Sequence<EntityRegistry.GenericParameterEntity> VisitTypars(CILParser.TyparsContext context)
        {
            CILParser.TyparContext[] typeParameters = context.typar();
            ImmutableArray<EntityRegistry.GenericParameterEntity>.Builder builder = ImmutableArray.CreateBuilder<EntityRegistry.GenericParameterEntity>(typeParameters.Length);

            foreach (var typeParameter in typeParameters)
            {
                builder.Add(VisitTypar(typeParameter).Value);
            }
            return new(builder.MoveToImmutable());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitTyparsClause(CILParser.TyparsClauseContext context) => VisitTyparsClause(context);
        public GrammarResult.Sequence<EntityRegistry.GenericParameterEntity> VisitTyparsClause(CILParser.TyparsClauseContext context) => context.typars() is null ? new(ImmutableArray<EntityRegistry.GenericParameterEntity>.Empty) : VisitTypars(context.typars());

        GrammarResult ICILVisitor<GrammarResult>.VisitType(CILParser.TypeContext context) => VisitType(context);
        public GrammarResult.FormattedBlob VisitType(CILParser.TypeContext context)
        {
            // These blobs will likely be very small, so use a smaller default size.
            const int DefaultSignatureElementBlobSize = 10;
            BlobBuilder prefix = new(DefaultSignatureElementBlobSize);
            BlobBuilder suffix = new(DefaultSignatureElementBlobSize);
            BlobBuilder elementType = VisitElementType(context.elementType()).Value;

            // Prefix blob writes outer modifiers first.
            // Suffix blob writes inner modifiers first.
            // Since all blobs are prefix blobs and only some have suffix data,
            // We will go in reverse order to write the prefixes
            // and then go in forward order to write the suffixes.
            CILParser.TypeModifiersContext[] typeModifiers = context.typeModifiers();
            for (int i = typeModifiers.Length - 1; i >= 0; i--)
            {
                CILParser.TypeModifiersContext? modifier = typeModifiers[i];
                switch (modifier)
                {
                    case CILParser.SZArrayModifierContext:
                        prefix.WriteByte((byte)SignatureTypeCode.SZArray);
                        break;
                    case CILParser.ArrayModifierContext:
                        prefix.WriteByte((byte)SignatureTypeCode.Array);
                        break;
                    case CILParser.ByRefModifierContext:
                        prefix.WriteByte((byte)SignatureTypeCode.ByReference);
                        break;
                    case CILParser.PtrModifierContext:
                        prefix.WriteByte((byte)SignatureTypeCode.Pointer);
                        break;
                    case CILParser.PinnedModifierContext:
                        prefix.WriteByte((byte)SignatureTypeCode.Pinned);
                        break;
                    case CILParser.RequiredModifierContext modreq:
                        prefix.WriteByte((byte)SignatureTypeCode.RequiredModifier);
                        prefix.WriteTypeEntity(VisitTypeSpec(modreq.typeSpec()).Value);
                        break;
                    case CILParser.OptionalModifierContext modopt:
                        prefix.WriteByte((byte)SignatureTypeCode.OptionalModifier);
                        prefix.WriteTypeEntity(VisitTypeSpec(modopt.typeSpec()).Value);
                        break;
                    case CILParser.GenericArgumentsModifierContext:
                        prefix.WriteByte((byte)SignatureTypeCode.GenericTypeInstance);
                        break;
                }
            }

            foreach (var modifier in typeModifiers)
            {
                switch (modifier)
                {
                    case CILParser.ArrayModifierContext arr:
                        var bounds = VisitBounds(arr.bounds()).Value;
                        suffix.WriteCompressedInteger(bounds.Length);
                        int lowerBoundsDefined = 0;
                        int upperBoundsDefined = 0;
                        foreach (var bound in bounds)
                        {
                            if (bound.Lower is not null)
                            {
                                lowerBoundsDefined++;
                            }
                            if (bound.Upper is not null)
                            {
                                upperBoundsDefined++;
                            }
                        }
                        suffix.WriteCompressedInteger(upperBoundsDefined);
                        foreach (var bound in bounds)
                        {
                            suffix.WriteCompressedInteger(bound.Upper.GetValueOrDefault());
                        }
                        suffix.WriteCompressedInteger(lowerBoundsDefined);
                        foreach (var bound in bounds)
                        {
                            suffix.WriteCompressedSignedInteger(bound.Lower.GetValueOrDefault());
                        }
                        break;
                    case CILParser.GenericArgumentsModifierContext genericArgs:
                        VisitTypeArgs(genericArgs.typeArgs()).Value.WriteContentTo(suffix);
                        break;
                }
            }

            elementType.LinkSuffix(suffix);
            prefix.LinkSuffix(elementType);
            return new(prefix);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitTypeArgs(CILParser.TypeArgsContext context) => VisitTypeArgs(context);

        public GrammarResult.FormattedBlob VisitTypeArgs(CILParser.TypeArgsContext context)
        {
            BlobBuilder blob = new(4);
            var types = context.type();
            blob.WriteCompressedInteger(types.Length);
            foreach (var type in types)
            {
                blob.LinkSuffix(VisitType(type).Value);
            }
            return new(blob);
        }

        public GrammarResult VisitTypedefDecl(CILParser.TypedefDeclContext context)
        {
            string alias = VisitDottedName(context.dottedName()).Value;

            if (context.type() is CILParser.TypeContext type)
            {
                // .typedef type as alias
                // This creates an alias for a complete type signature (blob)
                var typeBlob = VisitType(type).Value;
                // Create a copy of the blob to avoid issues with linked BlobBuilders
                var copy = new BlobBuilder(typeBlob.Count);
                typeBlob.WriteContentTo(copy);
                _typedefs[alias] = new TypedefEntry.TypeBlob(copy);
            }
            else if (context.className() is CILParser.ClassNameContext className)
            {
                // .typedef className as alias
                var typeEntity = VisitClassName(className).Value;
                _typedefs[alias] = new TypedefEntry.Type(typeEntity);
            }
            else if (context.memberRef() is CILParser.MemberRefContext memberRef)
            {
                // .typedef memberRef as alias
                var member = VisitMemberRef(memberRef).Value;
                _typedefs[alias] = new TypedefEntry.Member(member);
            }
            else if (context.customDescr() is CILParser.CustomDescrContext customDescr)
            {
                // .typedef customDescr as alias
                var attr = VisitCustomDescr(customDescr).Value;
                if (attr is not null)
                {
                    _typedefs[alias] = new TypedefEntry.CustomAttribute(attr.Constructor, attr.Value);
                }
            }
            else if (context.customDescrWithOwner() is CILParser.CustomDescrWithOwnerContext customDescrWithOwner)
            {
                // .typedef customDescrWithOwner as alias
                var attr = VisitCustomDescrWithOwner(customDescrWithOwner).Value;
                if (attr is not null)
                {
                    _typedefs[alias] = new TypedefEntry.CustomAttribute(attr.Constructor, attr.Value);
                }
            }

            return GrammarResult.SentinelValue.Result;
        }

        /// <summary>
        /// Tries to resolve a typedef alias to a type entity.
        /// </summary>
        private EntityRegistry.TypeEntity? TryResolveTypedefAsType(string alias)
        {
            if (_typedefs.TryGetValue(alias, out var entry) && entry is TypedefEntry.Type typeEntry)
            {
                return typeEntry.Entity;
            }
            return null;
        }

        /// <summary>
        /// Tries to resolve a typedef alias to a type blob (complete type signature).
        /// </summary>
        private BlobBuilder? TryResolveTypedefAsTypeBlob(string alias)
        {
            if (_typedefs.TryGetValue(alias, out var entry))
            {
                if (entry is TypedefEntry.TypeBlob blobEntry)
                {
                    return blobEntry.Blob;
                }
                if (entry is TypedefEntry.Type typeEntry)
                {
                    // Encode the type entity as a CLASS reference for the blob
                    var blob = new BlobBuilder(5);
                    blob.WriteByte((byte)SignatureTypeKind.Class);
                    blob.WriteTypeEntity(typeEntry.Entity);
                    return blob;
                }
            }
            return null;
        }

        /// <summary>
        /// Tries to resolve a typedef alias to a member reference.
        /// </summary>
        private EntityRegistry.EntityBase? TryResolveTypedefAsMember(string alias)
        {
            if (_typedefs.TryGetValue(alias, out var entry) && entry is TypedefEntry.Member memberEntry)
            {
                return memberEntry.Entity;
            }
            return null;
        }

        /// <summary>
        /// Tries to resolve a typedef alias to a custom attribute.
        /// </summary>
        private (EntityRegistry.EntityBase Constructor, BlobBuilder Value)? TryResolveTypedefAsCustomAttribute(string alias)
        {
            if (_typedefs.TryGetValue(alias, out var entry) && entry is TypedefEntry.CustomAttribute attrEntry)
            {
                return (attrEntry.Constructor, attrEntry.Value);
            }
            return null;
        }

        public GrammarResult VisitTypelist(CILParser.TypelistContext context)
        {
            foreach (var name in context.className())
            {
                // We don't do anything with the class names here.
                // We just go through the name resolution process to ensure that the names are valid
                // and to provide TypeReference table rows.
                _ = VisitClassName(name);
            }
            return GrammarResult.SentinelValue.Result;
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitTypeList(CILParser.TypeListContext context) => VisitTypeList(context);
        public GrammarResult.Sequence<EntityRegistry.TypeEntity> VisitTypeList(CILParser.TypeListContext context)
        {
            CILParser.TypeSpecContext[] bounds = context.typeSpec();
            ImmutableArray<EntityRegistry.TypeEntity>.Builder builder = ImmutableArray.CreateBuilder<EntityRegistry.TypeEntity>(bounds.Length);
            foreach (var typeSpec in bounds)
            {
                builder.Add(VisitTypeSpec(typeSpec).Value);
            }
            return new(builder.MoveToImmutable());
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitTypeSpec(CILParser.TypeSpecContext context) => VisitTypeSpec(context);
        public GrammarResult.Literal<EntityRegistry.TypeEntity> VisitTypeSpec(CILParser.TypeSpecContext context)
        {
            if (context.className() is CILParser.ClassNameContext className)
            {
                return new(VisitClassName(className).Value);
            }
            else if (context.dottedName() is CILParser.DottedNameContext dottedName)
            {
                string nameToResolve = VisitDottedName(dottedName).Value;
                if (context.MODULE() is not null)
                {
                    EntityRegistry.ModuleReferenceEntity? module = _entityRegistry.FindModuleReference(nameToResolve);
                    if (module is null)
                    {
                        // report error
                        return new(new EntityRegistry.FakeTypeEntity(MetadataTokens.ModuleReferenceHandle(0)));
                    }
                    return new(new EntityRegistry.FakeTypeEntity(module.Handle));
                }
                else
                {
                    return new(new EntityRegistry.FakeTypeEntity(
                        _entityRegistry.GetOrCreateAssemblyReference(nameToResolve, newRef =>
                        {
                            // Report warning on implicit assembly reference creation.
                        }).Handle));
                }
            }
            else
            {
                Debug.Assert(context.type() != null);
                return new(_entityRegistry.GetOrCreateTypeSpec(VisitType(context.type()).Value));
            }
        }


        GrammarResult ICILVisitor<GrammarResult>.VisitVariantType(CILParser.VariantTypeContext context) => VisitVariantType(context);
        public GrammarResult.Literal<VarEnum> VisitVariantType(CILParser.VariantTypeContext context)
        {
            VarEnum variant = VisitVariantTypeElement(context.variantTypeElement()).Value;
            // The 0th child is the variant element type.
            for (int i = 1; i < context.ChildCount; i++)
            {
                ITerminalNode childToken = (ITerminalNode)context.children[i];
                if (childToken.Symbol.Type == CILParser.ARRAY_TYPE_NO_BOUNDS)
                {
                    variant |= VarEnum.VT_ARRAY;
                }
                else if (childToken.Symbol.Type == CILParser.VECTOR)
                {
                    variant |= VarEnum.VT_VECTOR;
                }
                else
                {
                    Debug.Assert(childToken.Symbol.Type == CILParser.REF);
                    variant |= VarEnum.VT_BYREF;
                }
            }
            return new(variant);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitVariantTypeElement(CILParser.VariantTypeElementContext context) => VisitVariantTypeElement(context);
        public GrammarResult.Literal<VarEnum> VisitVariantTypeElement(CILParser.VariantTypeElementContext context)
        {
            return new(context.GetChild<ITerminalNode>(0).Symbol.Type switch
            {
                CILParser.VARIANT => VarEnum.VT_VARIANT,
                CILParser.CURRENCY => VarEnum.VT_CY,
                CILParser.VOID => VarEnum.VT_VOID,
                CILParser.BOOL => VarEnum.VT_BOOL,
                CILParser.INT8 => VarEnum.VT_I1,
                CILParser.INT16 => VarEnum.VT_I2,
                CILParser.INT32_ => VarEnum.VT_I4,
                CILParser.INT64_ => VarEnum.VT_I8,
                CILParser.FLOAT32 => VarEnum.VT_R4,
                CILParser.FLOAT64_ => VarEnum.VT_R8,
                CILParser.UINT8 => VarEnum.VT_UI1,
                CILParser.UINT16 => VarEnum.VT_UI2,
                CILParser.UINT32 => VarEnum.VT_UI4,
                CILParser.UINT64 => VarEnum.VT_UI8,
                CILParser.PTR => VarEnum.VT_PTR,
                CILParser.DECIMAL => VarEnum.VT_DECIMAL,
                CILParser.DATE => VarEnum.VT_DATE,
                CILParser.BSTR => VarEnum.VT_BSTR,
                CILParser.LPSTR => VarEnum.VT_LPSTR,
                CILParser.LPWSTR => VarEnum.VT_LPWSTR,
                CILParser.IUNKNOWN => VarEnum.VT_UNKNOWN,
                CILParser.IDISPATCH => VarEnum.VT_DISPATCH,
                CILParser.SAFEARRAY => VarEnum.VT_SAFEARRAY,
                CILParser.INT => VarEnum.VT_INT,
                CILParser.UINT => VarEnum.VT_UINT,
                CILParser.ERROR => VarEnum.VT_ERROR,
                CILParser.HRESULT => VarEnum.VT_HRESULT,
                CILParser.CARRAY => VarEnum.VT_CARRAY,
                CILParser.USERDEFINED => VarEnum.VT_USERDEFINED,
                CILParser.RECORD => VarEnum.VT_RECORD,
                CILParser.FILETIME => VarEnum.VT_FILETIME,
                CILParser.BLOB => VarEnum.VT_BLOB,
                CILParser.STREAM => VarEnum.VT_STREAM,
                CILParser.STORAGE => VarEnum.VT_STORAGE,
                CILParser.STREAMED_OBJECT => VarEnum.VT_STREAMED_OBJECT,
                CILParser.STORED_OBJECT => VarEnum.VT_STORED_OBJECT,
                CILParser.BLOB_OBJECT => VarEnum.VT_BLOB_OBJECT,
                CILParser.CF => VarEnum.VT_CF,
                CILParser.CLSID => VarEnum.VT_CLSID,
                _ => throw new UnreachableException()
            });
        }

        public GrammarResult VisitVtableDecl(CILParser.VtableDeclContext context)
        {
            // Raw .vtable directive with bytes - not commonly used
            // For now, we don't support this legacy syntax
            throw new NotImplementedException("raw vtable fixups blob (.vtable) not supported - use .vtfixup instead");
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitVtfixupAttr(CILParser.VtfixupAttrContext context) => VisitVtfixupAttr(context);
        public GrammarResult.Literal<ushort> VisitVtfixupAttr(CILParser.VtfixupAttrContext context)
        {
            // vtfixupAttr: | vtfixupAttr INT32_ | vtfixupAttr INT64_ | vtfixupAttr 'fromunmanaged' | vtfixupAttr 'callmostderived' | vtfixupAttr 'retainappdomain'
            ushort flags = 0;
            foreach (var child in context.children ?? [])
            {
                string text = child.GetText();
                flags |= text switch
                {
                    "int32" => VTableFixupSupport.COR_VTABLE_32BIT,
                    "int64" => VTableFixupSupport.COR_VTABLE_64BIT,
                    "fromunmanaged" => VTableFixupSupport.COR_VTABLE_FROM_UNMANAGED,
                    "callmostderived" => VTableFixupSupport.COR_VTABLE_CALL_MOST_DERIVED,
                    "retainappdomain" => VTableFixupSupport.COR_VTABLE_FROM_UNMANAGED_RETAIN_APPDOMAIN,
                    _ => 0
                };
            }

            // Default to 32-bit if neither 32 nor 64 is specified
            if ((flags & (VTableFixupSupport.COR_VTABLE_32BIT | VTableFixupSupport.COR_VTABLE_64BIT)) == 0)
            {
                flags |= VTableFixupSupport.COR_VTABLE_32BIT;
            }

            return new(flags);
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitVtfixupDecl(CILParser.VtfixupDeclContext context) => VisitVtfixupDecl(context);
        public GrammarResult VisitVtfixupDecl(CILParser.VtfixupDeclContext context)
        {
            // vtfixupDecl: '.vtfixup' '[' int32 ']' vtfixupAttr 'at' id;
            int slotCount = VisitInt32(context.int32()).Value;
            ushort flags = VisitVtfixupAttr(context.vtfixupAttr()).Value;
            string dataLabel = VisitId(context.id()).Value;

            _vtableFixups.Add(new VTableFixupSupport.VTableFixupEntry(slotCount, flags, dataLabel));

            return GrammarResult.SentinelValue.Result;
        }

        /// <summary>
        /// Computes the total metadata size from MetadataSizes.
        /// This replicates the internal MetadataSizes.MetadataSize calculation.
        /// </summary>
        private static int ComputeMetadataSize(MetadataSizes sizes)
        {
            // Metadata header size (fixed structure):
            // - signature (4)
            // - major/minor version (4)
            // - reserved (4)
            // - version string length (4)
            // - version string padded to 4 bytes ("v4.0.30319" = 12 bytes padded)
            // - storage header (4)
            // - 5 stream headers (#~, #Strings, #US, #GUID, #Blob) = 76 bytes
            // Total header: ~108 bytes
            const int metadataHeaderSize = 108;

            // Stream storage: heaps (#Strings, #US, #GUID, #Blob) - we can get aligned sizes
            int heapStorageSize = 0;
            heapStorageSize += sizes.GetAlignedHeapSize(HeapIndex.String);
            heapStorageSize += sizes.GetAlignedHeapSize(HeapIndex.UserString);
            heapStorageSize += sizes.GetAlignedHeapSize(HeapIndex.Guid);
            heapStorageSize += sizes.GetAlignedHeapSize(HeapIndex.Blob);

            // Table stream (#~): header + table data
            // Header: Reserved(4) + Version(2) + HeapSizes(1) + RowIdBitWidth(1) + ValidMask(8) + SortedMask(8)
            //         + 4 bytes per present table for row counts
            int tableStreamSize = 24; // base header
            var rowCounts = sizes.RowCounts;

            // Count present tables and add 4 bytes each for row count
            for (int i = 0; i < rowCounts.Length; i++)
            {
                if (rowCounts[i] > 0)
                {
                    tableStreamSize += 4;
                }
            }

            // Add table data size with estimated row sizes
            // Row sizes depend on index sizes (2 or 4 bytes) which we don't have access to
            // For small assemblies, all indexes are 2 bytes
            tableStreamSize += rowCounts[(int)TableIndex.Module] * 10;       // 2+2+2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.TypeRef] * 6;       // 2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.TypeDef] * 14;      // 4+2+2+2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.Field] * 6;         // 2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.MethodDef] * 14;    // 4+2+2+2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.Param] * 6;         // 2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.InterfaceImpl] * 4; // 2+2
            tableStreamSize += rowCounts[(int)TableIndex.MemberRef] * 6;     // 2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.Constant] * 6;      // 2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.CustomAttribute] * 6; // 2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.FieldMarshal] * 4;  // 2+2
            tableStreamSize += rowCounts[(int)TableIndex.DeclSecurity] * 6;  // 2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.ClassLayout] * 8;   // 2+4+2
            tableStreamSize += rowCounts[(int)TableIndex.FieldLayout] * 6;   // 4+2
            tableStreamSize += rowCounts[(int)TableIndex.StandAloneSig] * 2; // 2
            tableStreamSize += rowCounts[(int)TableIndex.EventMap] * 4;      // 2+2
            tableStreamSize += rowCounts[(int)TableIndex.Event] * 6;         // 2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.PropertyMap] * 4;   // 2+2
            tableStreamSize += rowCounts[(int)TableIndex.Property] * 6;      // 2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.MethodSemantics] * 6; // 2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.MethodImpl] * 6;    // 2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.ModuleRef] * 2;     // 2
            tableStreamSize += rowCounts[(int)TableIndex.TypeSpec] * 2;      // 2
            tableStreamSize += rowCounts[(int)TableIndex.ImplMap] * 8;       // 2+2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.FieldRva] * 6;      // 4+2
            tableStreamSize += rowCounts[(int)TableIndex.Assembly] * 22;     // 16+2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.AssemblyRef] * 20;  // 12+2+2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.File] * 8;          // 4+2+2
            tableStreamSize += rowCounts[(int)TableIndex.ExportedType] * 14; // 8+2+2+2
            tableStreamSize += rowCounts[(int)TableIndex.ManifestResource] * 12; // 8+2+2
            tableStreamSize += rowCounts[(int)TableIndex.NestedClass] * 4;   // 2+2
            tableStreamSize += rowCounts[(int)TableIndex.GenericParam] * 8;  // 4+2+2
            tableStreamSize += rowCounts[(int)TableIndex.MethodSpec] * 4;    // 2+2
            tableStreamSize += rowCounts[(int)TableIndex.GenericParamConstraint] * 4; // 2+2

            // Align table stream to 4 bytes (includes +1 for terminating 0 byte)
            tableStreamSize = ((tableStreamSize + 1) + 3) & ~3;

            return metadataHeaderSize + heapStorageSize + tableStreamSize;
        }

        GrammarResult ICILVisitor<GrammarResult>.VisitOptionalModifier(CILParser.OptionalModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        GrammarResult ICILVisitor<GrammarResult>.VisitSZArrayModifier(CILParser.SZArrayModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        GrammarResult ICILVisitor<GrammarResult>.VisitRequiredModifier(CILParser.RequiredModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        GrammarResult ICILVisitor<GrammarResult>.VisitPtrModifier(CILParser.PtrModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        GrammarResult ICILVisitor<GrammarResult>.VisitPinnedModifier(CILParser.PinnedModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        GrammarResult ICILVisitor<GrammarResult>.VisitGenericArgumentsModifier(CILParser.GenericArgumentsModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        GrammarResult ICILVisitor<GrammarResult>.VisitByRefModifier(CILParser.ByRefModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        GrammarResult ICILVisitor<GrammarResult>.VisitArrayModifier(CILParser.ArrayModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        public GrammarResult VisitNativeTypeArrayPointerInfo(CILParser.NativeTypeArrayPointerInfoContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        public GrammarResult VisitPointerArrayTypeSize(CILParser.PointerArrayTypeSizeContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        public GrammarResult VisitPointerArrayTypeParamIndex(CILParser.PointerArrayTypeParamIndexContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        public GrammarResult VisitPointerNativeType(CILParser.PointerNativeTypeContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        public GrammarResult VisitPointerArrayTypeSizeParamIndex(CILParser.PointerArrayTypeSizeParamIndexContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
        public GrammarResult VisitPointerArrayTypeNoSizeData(CILParser.PointerArrayTypeNoSizeDataContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
    }
}