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

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Metadata;
using Antlr4.Runtime;

namespace ILAssembler;

internal sealed partial class GrammarActions
{
    private readonly Dictionary<
        EntityRegistry.TypeDefinitionEntity,
        List<PendingClassMethodOverride>> _pendingClassMethodOverrides = new();

    private sealed record PendingClassMethodOverride(
        EntityRegistry.MemberReferenceEntity Declaration,
        EntityRegistry.MemberReferenceEntity? ReferencedBody,
        string BodyName,
        BlobBuilder BodySignature,
        IToken Location);

    private bool PrepareClassMember()
    {
        _pendingClassCustomAttributeOwner = null;
        return !IsDeclarationSuppressed;
    }

    internal void ProcessClassDataDeclaration(CILParser.DataDeclContext context)
    {
        PrepareClassMember();
        _ = context;
    }

    internal void ProcessClassSecurityDeclaration(CILParser.SecDeclContext context)
    {
        if (!PrepareClassMember() || context.HasSyntaxError)
        {
            return;
        }

        EntityRegistry.DeclarativeSecurityAttributeEntity? security = MaterializeSecurityDeclaration(context);
        security?.Parent = _currentTypeDefinition.PeekOrDefault();
    }

    internal void ProcessClassSourceDirective(CILParser.ExtSourceSpecContext context)
    {
        PrepareClassMember();
        _ = context;
    }

    internal void ProcessClassLanguageDirective(CILParser.LanguageDeclContext context)
    {
        PrepareClassMember();
        _ = context;
    }

    internal void ProcessClassCustomAttribute(CILParser.CustomAttrDeclContext context)
    {
        if (IsDeclarationSuppressed || context.HasSyntaxError)
        {
            return;
        }

        if (MaterializeCustomAttributeDeclaration(context) is { } customAttribute)
        {
            customAttribute.Owner ??=
                _pendingClassCustomAttributeOwner ??
                _currentTypeDefinition.PeekOrDefault();
        }
    }

    internal void SetClassSize(IToken token)
    {
        if (PrepareClassMember() &&
            _currentTypeDefinition.PeekOrDefault() is { } currentType)
        {
            currentType.ClassSize = ParseInt32(token);
        }
    }

    internal void SetClassPackingSize(IToken token)
    {
        if (PrepareClassMember() &&
            _currentTypeDefinition.PeekOrDefault() is { } currentType)
        {
            currentType.PackingSize = ParseInt32(token);
        }
    }

    internal void ProcessClassExport(
        CILParser.ExportHeadContext export,
        CILParser.ExptypeDeclsContext declarations)
    {
        PrepareClassMember();
        _ = export;
        _ = declarations;
    }

    internal void ProcessClassCompilerControl()
    {
        PrepareClassMember();
    }

    internal void AddClassMethodOverride(
        CILParser.ClassDeclContext context,
        TypeSpecificationValue declarationOwner,
        string declarationName,
        byte bodyCallingConvention,
        TypeValue bodyReturnType,
        TypeSpecificationValue bodyOwner,
        string bodyName,
        ImmutableArray<SignatureArgumentValue> bodyArguments)
    {
        if (!PrepareClassMember())
        {
            return;
        }

        AddClassMethodOverrideCore(
            context,
            bodyCallingConvention,
            bodyReturnType,
            declarationOwner,
            declarationName,
            0,
            bodyArguments,
            bodyCallingConvention,
            bodyReturnType,
            bodyOwner,
            bodyName,
            0,
            bodyArguments);
    }

    internal void AddClassMethodOverride(
        CILParser.ClassDeclContext context,
        byte declarationCallingConvention,
        TypeValue declarationReturnType,
        TypeSpecificationValue declarationOwner,
        string declarationName,
        int declarationArity,
        ImmutableArray<SignatureArgumentValue> declarationArguments,
        byte bodyCallingConvention,
        TypeValue bodyReturnType,
        TypeSpecificationValue bodyOwner,
        string bodyName,
        int bodyArity,
        ImmutableArray<SignatureArgumentValue> bodyArguments)
    {
        if (!PrepareClassMember())
        {
            return;
        }

        AddClassMethodOverrideCore(
            context,
            declarationCallingConvention,
            declarationReturnType,
            declarationOwner,
            declarationName,
            declarationArity,
            declarationArguments,
            bodyCallingConvention,
            bodyReturnType,
            bodyOwner,
            bodyName,
            bodyArity,
            bodyArguments);
    }

    private void AddClassMethodOverrideCore(
        CILParser.ClassDeclContext context,
        byte declarationCallingConvention,
        TypeValue declarationReturnType,
        TypeSpecificationValue declarationOwner,
        string declarationName,
        int declarationArity,
        ImmutableArray<SignatureArgumentValue> declarationArguments,
        byte bodyCallingConvention,
        TypeValue bodyReturnType,
        TypeSpecificationValue bodyOwner,
        string bodyName,
        int bodyArity,
        ImmutableArray<SignatureArgumentValue> bodyArguments)
    {
        if (_currentTypeDefinition.PeekOrDefault() is not { } currentType)
        {
            return;
        }

        BlobBuilder declarationSignature = BuildClassMethodOverrideSignature(
            declarationCallingConvention,
            declarationReturnType,
            declarationArguments,
            declarationArity);
        BlobBuilder bodySignature = BuildClassMethodOverrideSignature(
            bodyCallingConvention,
            bodyReturnType,
            bodyArguments,
            bodyArity);

        EntityRegistry.MemberReferenceEntity declaration =
            _entityRegistry.CreateLazilyRecordedMemberReference(
                ResolveTypeSpecification(declarationOwner),
                declarationName,
                declarationSignature);
        EntityRegistry.TypeEntity resolvedBodyOwner =
            ResolveTypeSpecification(bodyOwner);
        EntityRegistry.MemberReferenceEntity? referencedBody =
            ReferenceEquals(resolvedBodyOwner, currentType)
                ? null
                : _entityRegistry.CreateLazilyRecordedMemberReference(
                    resolvedBodyOwner,
                    bodyName,
                    bodySignature);

        if (!_pendingClassMethodOverrides.TryGetValue(
                currentType,
                out List<PendingClassMethodOverride>? pendingOverrides))
        {
            pendingOverrides = new();
            _pendingClassMethodOverrides.Add(currentType, pendingOverrides);
        }

        pendingOverrides.Add(
            new(
                declaration,
                referencedBody,
                bodyName,
                bodySignature,
                context.Start));
    }

    private void CompleteClassMethodOverrides(EntityRegistry.TypeDefinitionEntity type)
    {
        if (!_pendingClassMethodOverrides.Remove(
                type,
                out List<PendingClassMethodOverride>? pendingOverrides))
        {
            return;
        }

        foreach (PendingClassMethodOverride pending in pendingOverrides)
        {
            if (pending.ReferencedBody is { } referencedBody)
            {
                type.MethodImplementations.Add(
                    EntityRegistry.CreateUnrecordedMethodImplementation(
                        type,
                        referencedBody,
                        pending.Declaration));
                continue;
            }

            EntityRegistry.MethodDefinitionEntity? bodyMethod = null;
            bool isAmbiguous = false;
            foreach (EntityRegistry.MethodDefinitionEntity candidate in type.Methods)
            {
                if (candidate.Name != pending.BodyName ||
                    candidate.MethodSignature is null ||
                    !candidate.MethodSignature.ContentEquals(pending.BodySignature))
                {
                    continue;
                }

                if (bodyMethod is not null)
                {
                    isAmbiguous = true;
                    break;
                }

                bodyMethod = candidate;
            }

            if (bodyMethod is null || isAmbiguous)
            {
                ReportError(
                    DiagnosticIds.InvalidMetadataToken,
                    $"Override body method '{pending.BodyName}' could not be resolved uniquely",
                    pending.Location);
                continue;
            }

            type.MethodImplementations.Add(
                EntityRegistry.CreateUnrecordedMethodImplementation(
                    bodyMethod,
                    pending.Declaration));
        }
    }

    private BlobBuilder BuildClassMethodOverrideSignature(
        byte callingConvention,
        TypeValue returnType,
        ImmutableArray<SignatureArgumentValue> arguments,
        int genericArity)
    {
        BlobBuilder signature = new();
        byte header = callingConvention;
        if (genericArity > 0)
        {
            header |= (byte)SignatureAttributes.Generic;
        }
        signature.WriteByte(header);
        if (genericArity > 0)
        {
            signature.WriteCompressedInteger(genericArity);
        }

        ImmutableArray<SignatureArg> materializedArguments =
            MaterializeSignatureArguments(arguments);
        int parameterCount = 0;
        foreach (SignatureArg argument in materializedArguments)
        {
            if (!argument.IsSentinel)
            {
                parameterCount++;
            }
        }
        signature.WriteCompressedInteger(parameterCount);
        MaterializeType(returnType).WriteContentTo(signature);
        foreach (SignatureArg argument in materializedArguments)
        {
            argument.SignatureBlob.WriteContentTo(signature);
        }

        return signature;
    }

    internal CILParser.CustomAttributeOwnerValue BeginClassGenericParameterDirective(
        CILParser.ClassDeclContext context,
        IToken index)
    {
        if (!PrepareClassMember())
        {
            return BeginClassGenericDirective(null);
        }

        return BeginClassGenericDirective(
            FindClassGenericParameter(context, ParseInt32(index)));
    }

    internal CILParser.CustomAttributeOwnerValue BeginClassGenericParameterDirective(
        string name)
    {
        if (!PrepareClassMember())
        {
            return BeginClassGenericDirective(null);
        }

        return BeginClassGenericDirective(FindClassGenericParameter(name));
    }

    internal CILParser.CustomAttributeOwnerValue BeginClassGenericConstraintDirective(
        CILParser.ClassDeclContext context,
        IToken index,
        TypeSpecificationValue constraintType)
    {
        if (!PrepareClassMember())
        {
            return BeginClassGenericDirective(null);
        }

        return BeginClassGenericDirective(
            FindOrCreateClassGenericConstraint(
                FindClassGenericParameter(context, ParseInt32(index)),
                constraintType));
    }

    internal CILParser.CustomAttributeOwnerValue BeginClassGenericConstraintDirective(
        string name,
        TypeSpecificationValue constraintType)
    {
        if (!PrepareClassMember())
        {
            return BeginClassGenericDirective(null);
        }

        return BeginClassGenericDirective(
            FindOrCreateClassGenericConstraint(
                FindClassGenericParameter(name),
                constraintType));
    }

    private CILParser.CustomAttributeOwnerValue BeginClassGenericDirective(
        EntityRegistry.EntityBase? owner)
    {
        _pendingClassCustomAttributeOwner = owner;
        return new CILParser.CustomAttributeOwnerValue(owner);
    }

    internal void AddClassGenericDirectiveAttribute(
        CILParser.CustomAttributeOwnerValue ownerValue,
        CILParser.CustomAttrDeclContext attribute)
    {
        if (attribute.HasSyntaxError ||
            ownerValue.Owner is not { } owner)
        {
            return;
        }

        if (MaterializeCustomAttributeDeclaration(attribute) is { } customAttribute)
        {
            customAttribute.Owner ??= owner;
        }
    }

    private EntityRegistry.GenericParameterEntity? FindClassGenericParameter(
        CILParser.ClassDeclContext context,
        int index)
    {
        EntityRegistry.TypeDefinitionEntity? currentType = _currentTypeDefinition.PeekOrDefault();
        if (currentType is not null &&
            index >= 0 &&
            index < currentType.GenericParameters.Count)
        {
            return currentType.GenericParameters[index];
        }

        ReportError(
            DiagnosticIds.GenericParameterIndexOutOfRange,
            string.Format(DiagnosticMessageTemplates.GenericParameterIndexOutOfRange, index),
            context);
        return null;
    }

    private EntityRegistry.GenericParameterEntity? FindClassGenericParameter(string name)
    {
        EntityRegistry.TypeDefinitionEntity? currentType = _currentTypeDefinition.PeekOrDefault();
        if (currentType is null)
        {
            return null;
        }

        foreach (EntityRegistry.GenericParameterEntity parameter in currentType.GenericParameters)
        {
            if (parameter.Name == name)
            {
                return parameter;
            }
        }

        return null;
    }

    private EntityRegistry.GenericParameterConstraintEntity? FindOrCreateClassGenericConstraint(
        EntityRegistry.GenericParameterEntity? parameter,
        TypeSpecificationValue constraintType)
    {
        if (parameter is null ||
            _currentTypeDefinition.PeekOrDefault() is not { } currentType)
        {
            return null;
        }

        EntityRegistry.TypeEntity baseType =
            ResolveTypeSpecification(constraintType);
        foreach (EntityRegistry.GenericParameterConstraintEntity constraint in parameter.Constraints)
        {
            if (constraint.BaseType == baseType)
            {
                return constraint;
            }
        }

        EntityRegistry.GenericParameterConstraintEntity newConstraint =
            EntityRegistry.CreateGenericConstraint(baseType);
        newConstraint.Owner = parameter;
        parameter.Constraints.Add(newConstraint);
        currentType.GenericParameterConstraints.Add(newConstraint);
        return newConstraint;
    }

    internal void AddInterfaceImplementationAttribute(
        CILParser.ClassDeclContext context,
        TypeSpecificationValue interfaceType,
        CILParser.CustomDescrContext attribute)
    {
        if (!PrepareClassMember() ||
            attribute.HasSyntaxError ||
            _currentTypeDefinition.PeekOrDefault() is not { } currentType)
        {
            return;
        }

        EntityRegistry.TypeEntity resolvedInterface =
            ResolveTypeSpecification(interfaceType);
        EntityRegistry.InterfaceImplementationEntity? implementation = null;
        foreach (EntityRegistry.InterfaceImplementationEntity candidate in currentType.InterfaceImplementations)
        {
            if (candidate.InterfaceType == resolvedInterface)
            {
                implementation = candidate;
                break;
            }
        }

        if (implementation is null)
        {
            implementation =
                EntityRegistry.CreateUnrecordedInterfaceImplementation(currentType, resolvedInterface);
            currentType.InterfaceImplementations.Add(implementation);
        }

        MaterializeCustomAttributeDescriptor(attribute).Owner = implementation;
        _ = context;
    }

}