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

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

namespace ILAssembler;

#pragma warning disable CA1822 // Parser actions are invoked through the per-parser GrammarActions instance.
internal sealed partial class GrammarActions
{
    internal void SetExportedTypeAttribute(CILParser.ExptAttrContext context)
    {
        string attribute = context.Start.Text == "nested"
            ? $"nested{context.Stop?.Text}"
            : context.Start.Text;
        (TypeAttributes value, TypeAttributes mask) = attribute switch
        {
            "private" => (TypeAttributes.NotPublic, TypeAttributes.VisibilityMask),
            "public" => (TypeAttributes.Public, TypeAttributes.VisibilityMask),
            "forwarder" => (TypeAttributes.Forwarder, (TypeAttributes)0),
            "nestedpublic" => (TypeAttributes.NestedPublic, TypeAttributes.VisibilityMask),
            "nestedprivate" => (TypeAttributes.NestedPrivate, TypeAttributes.VisibilityMask),
            "nestedfamily" => (TypeAttributes.NestedFamily, TypeAttributes.VisibilityMask),
            "nestedassembly" => (TypeAttributes.NestedAssembly, TypeAttributes.VisibilityMask),
            "nestedfamandassem" => (TypeAttributes.NestedFamANDAssem, TypeAttributes.VisibilityMask),
            "nestedfamorassem" => (TypeAttributes.NestedFamORAssem, TypeAttributes.VisibilityMask),
            _ => ((TypeAttributes)0, (TypeAttributes)0)
        };
        context.Value = value;
        context.Mask = mask;
    }

    internal TypeAttributes AddExportedTypeAttribute(
        TypeAttributes attributes,
        TypeAttributes value,
        TypeAttributes mask)
        => mask == 0 ? attributes | value : (attributes & ~mask) | value;

    internal ExportedTypeHeaderValue CreateExportedTypeHeader(
        TypeAttributes attributes,
        string name,
        IToken location)
        => new ExportedTypeHeaderValue(true, attributes, name, location);

    internal ExportedTypeValue CreateExportedType(
        ExportedTypeHeaderValue header,
        ImmutableArray<ExportedTypeDeclarationValue> declarations)
        => new ExportedTypeValue(
            header,
            declarations);

    internal ExportedTypeDeclarationValue CreateExportedTypeFileDeclaration(
        string name,
        IToken location)
        => new ExportedTypeFileDirectiveValue(name, location);

    internal ExportedTypeDeclarationValue CreateNestedExportedTypeDeclaration(
        TypeName name,
        IToken location)
        => new NestedExportedTypeDirectiveValue(name, location);

    internal ExportedTypeDeclarationValue CreateExportedTypeAssemblyDeclaration(
        string name,
        IToken location)
        => new ExportedTypeAssemblyDirectiveValue(name, location);

    internal ExportedTypeDeclarationValue CreateExportedTypeMetadataTokenDeclaration(
        int token,
        IToken location)
        => new ExportedTypeMetadataTokenDirectiveValue(token, location);

    internal ExportedTypeDeclarationValue CreateExportedTypeDefinitionIdDeclaration(
        IToken value)
        => new ExportedTypeDefinitionIdDirectiveValue(ParseInt32(value));

    internal ExportedTypeDeclarationValue CreateExportedTypeCustomAttributeDeclaration(
        CustomAttributeDeclarationValue? value,
        IToken location)
        => new ExportedTypeCustomAttributeDirectiveValue(value, location);

    private void MaterializeExportedType(ExportedTypeValue value)
    {
        ExportedTypeHeaderValue header = value.Header;
        if (!header.IsValid ||
            header.Location is not IToken location)
        {
            return;
        }

        (string typeNamespace, string name) =
            NameHelpers.SplitDottedNameToNamespaceAndName(header.Name);
        (
            EntityRegistry.EntityBase? implementation,
            int typeDefinitionId,
            ImmutableArray<EntityRegistry.CustomAttributeEntity> customAttributes
        ) = MaterializeExportedTypeDeclarations(value.Declarations);

        if (implementation is null)
        {
            ReportWarning(
                DiagnosticIds.MissingExportedTypeImplementation,
                string.Format(
                    DiagnosticMessageTemplates.MissingExportedTypeImplementation,
                    header.Name),
                location);
            return;
        }

        EntityRegistry.ExportedTypeEntity exportedType =
            _entityRegistry.GetOrCreateExportedType(
                implementation,
                typeNamespace,
                name,
                entity =>
                {
                    entity.Attributes = header.Attributes;
                    entity.TypeDefinitionId = typeDefinitionId;
                });
        foreach (EntityRegistry.CustomAttributeEntity attribute in customAttributes)
        {
            attribute.Owner ??= exportedType;
        }
    }

    private (
        EntityRegistry.EntityBase? Implementation,
        int TypeDefinitionId,
        ImmutableArray<EntityRegistry.CustomAttributeEntity> CustomAttributes
    ) MaterializeExportedTypeDeclarations(
        ImmutableArray<ExportedTypeDeclarationValue> declarations)
    {
        EntityRegistry.EntityBase? implementation = null;
        int typeDefinitionId = 0;
        ImmutableArray<EntityRegistry.CustomAttributeEntity>.Builder customAttributes =
            ImmutableArray.CreateBuilder<EntityRegistry.CustomAttributeEntity>();

        foreach (ExportedTypeDeclarationValue declaration in declarations)
        {
            switch (declaration)
            {
                case ExportedTypeCustomAttributeDirectiveValue customAttribute:
                    if (MaterializeCustomAttributeDeclaration(
                            customAttribute.Value,
                            customAttribute.Location) is { } attribute)
                    {
                        customAttributes.Add(attribute);
                    }
                    break;
                case ExportedTypeMetadataTokenDirectiveValue metadataToken:
                    EntityRegistry.EntityBase entity = ResolveMetadataToken(metadataToken.Token);
                    if (entity is EntityRegistry.FakeTypeEntity)
                    {
                        ReportError(
                            DiagnosticIds.InvalidMetadataToken,
                            DiagnosticMessageTemplates.InvalidMetadataToken,
                            metadataToken.Location);
                    }
                    implementation = ResolveBetterExportedTypeImplementation(
                        implementation,
                        entity);
                    break;
                case ExportedTypeFileDirectiveValue file:
                    implementation = _entityRegistry.FindFile(file.Name);
                    if (implementation is null)
                    {
                        ReportError(
                            DiagnosticIds.FileNotFound,
                            string.Format(DiagnosticMessageTemplates.FileNotFound, file.Name),
                            file.Location);
                    }
                    break;
                case ExportedTypeAssemblyDirectiveValue assembly:
                    implementation = _entityRegistry.FindAssemblyReference(assembly.Name);
                    if (implementation is null)
                    {
                        ReportError(
                            DiagnosticIds.AssemblyNotFound,
                            string.Format(
                                DiagnosticMessageTemplates.AssemblyNotFound,
                                assembly.Name),
                            assembly.Location);
                    }
                    break;
                case NestedExportedTypeDirectiveValue nested:
                    EntityRegistry.ExportedTypeEntity? containingType =
                        ResolveExportedType(nested.Name, nested.Location);
                    if (containingType is null)
                    {
                        ReportError(
                            DiagnosticIds.ExportedTypeNotFound,
                            string.Format(
                                DiagnosticMessageTemplates.ExportedTypeNotFound,
                                GetExportedTypeDisplayName(nested.Name)),
                            nested.Location);
                    }
                    else
                    {
                        implementation = ResolveBetterExportedTypeImplementation(
                            implementation,
                            containingType);
                    }
                    break;
                case ExportedTypeDefinitionIdDirectiveValue definitionId:
                    typeDefinitionId = definitionId.Value;
                    break;
            }
        }

        return (implementation, typeDefinitionId, customAttributes.ToImmutable());
    }

    private static EntityRegistry.EntityBase? ResolveBetterExportedTypeImplementation(
        EntityRegistry.EntityBase? current,
        EntityRegistry.EntityBase? candidate)
    {
        if (candidate is null)
        {
            return current;
        }

        if (current is null)
        {
            return candidate;
        }

        return GetImplementationPriority(candidate) >= GetImplementationPriority(current)
            ? candidate
            : current;

        static int GetImplementationPriority(EntityRegistry.EntityBase entity)
            => entity switch
            {
                EntityRegistry.FileEntity => 4,
                EntityRegistry.AssemblyReferenceEntity => 3,
                EntityRegistry.ExportedTypeEntity => 2,
                _ => 1
            };
    }

    private EntityRegistry.ExportedTypeEntity? ResolveExportedType(
        TypeName typeName,
        IToken location)
    {
        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 typeNamespace, string name) =
                NameHelpers.SplitDottedNameToNamespaceAndName(containingType.DottedName);
            exportedType = _entityRegistry.FindExportedType(
                exportedType,
                typeNamespace,
                name);
            if (exportedType is null)
            {
                ReportError(
                    DiagnosticIds.ExportedTypeNotFound,
                    string.Format(
                        DiagnosticMessageTemplates.ExportedTypeNotFound,
                        containingType.DottedName),
                    location);
                return null;
            }
        }

        return exportedType;
    }

    private static string GetExportedTypeDisplayName(TypeName typeName)
    {
        Stack<string> names = new();
        for (TypeName? current = typeName;
             current is not null;
             current = current.ContainingTypeName)
        {
            names.Push(current.DottedName);
        }

        return string.Join("/", names);
    }

    internal void MaterializeExportedType(CILParser.ExptypeBlockContext context)
    {
        if (context.Value is ExportedTypeValue value)
        {
            MaterializeExportedType(value);
        }
    }

}