File: Actions\GrammarActions.Signatures.Types.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.Immutable;
using System.Linq;
using System.Reflection.Metadata;

namespace ILAssembler;

#pragma warning disable CA1822 // Parser actions are invoked through the per-parser GrammarActions instance.
internal sealed partial class GrammarActions
{
    private BlobBuilder MaterializeType(TypeValue type)
    {
        const int DefaultSignatureElementBlobSize = 10;

        BlobBuilder elementType = MaterializeElementType(type.ElementType);
        BlobBuilder prefix = new(DefaultSignatureElementBlobSize);
        BlobBuilder suffix = new(DefaultSignatureElementBlobSize);

        for (int i = type.Modifiers.Length - 1; i >= 0; i--)
        {
            switch (type.Modifiers[i])
            {
                case SimpleTypeModifierValue { Kind: SimpleTypeModifierKind.SzArray }:
                    prefix.WriteByte((byte)SignatureTypeCode.SZArray);
                    break;
                case ArrayTypeModifierValue:
                    prefix.WriteByte((byte)SignatureTypeCode.Array);
                    break;
                case SimpleTypeModifierValue { Kind: SimpleTypeModifierKind.ByReference }:
                    prefix.WriteByte((byte)SignatureTypeCode.ByReference);
                    break;
                case SimpleTypeModifierValue { Kind: SimpleTypeModifierKind.Pointer }:
                    prefix.WriteByte((byte)SignatureTypeCode.Pointer);
                    break;
                case SimpleTypeModifierValue { Kind: SimpleTypeModifierKind.Pinned }:
                    prefix.WriteByte((byte)SignatureTypeCode.Pinned);
                    break;
                case CustomTypeModifierValue customModifier:
                    prefix.WriteByte((byte)(customModifier.IsRequired
                        ? SignatureTypeCode.RequiredModifier
                        : SignatureTypeCode.OptionalModifier));
                    prefix.WriteTypeEntity(ResolveTypeSpecification(customModifier.Type));
                    break;
                case GenericArgumentsTypeModifierValue:
                    prefix.WriteByte((byte)SignatureTypeCode.GenericTypeInstance);
                    break;
            }
        }

        foreach (TypeModifierValue modifier in type.Modifiers)
        {
            switch (modifier)
            {
                case ArrayTypeModifierValue array:
                    WriteArrayShape(suffix, array.Bounds);
                    break;
                case GenericArgumentsTypeModifierValue genericArguments:
                    MaterializeTypeArguments(genericArguments.Arguments).WriteContentTo(suffix);
                    break;
            }
        }

        // Work around https://github.com/dotnet/runtime/issues/127243 by writing to a separate blob.
        BlobBuilder fullBlob = new(elementType.Count + prefix.Count + suffix.Count);
        prefix.WriteContentTo(fullBlob);
        elementType.WriteContentTo(fullBlob);
        suffix.WriteContentTo(fullBlob);
        return fullBlob;
    }

    private BlobBuilder MaterializeElementType(ElementTypeValue elementType)
    {
        BlobBuilder blob = new(5);
        switch (elementType)
        {
            case CILParser.ErrorElementTypeValue:
                blob.WriteByte((byte)SignatureTypeCode.Object);
                break;
            case PrimitiveElementTypeValue primitive:
                blob.WriteByte(primitive.TypeCode);
                break;
            case ClassElementTypeValue classType:
                EntityRegistry.TypeEntity typeEntity = ResolveClassName(classType.ClassName);
                if (TryGetPrimitiveTypeCode(typeEntity, classType.IsValueType) is { } primitiveTypeCode)
                {
                    blob.WriteByte((byte)primitiveTypeCode);
                }
                else
                {
                    blob.WriteByte((byte)(classType.IsValueType ? SignatureTypeKind.ValueType : SignatureTypeKind.Class));
                    blob.WriteTypeEntity(typeEntity);
                }
                break;
            case FunctionPointerElementTypeValue functionPointer:
                blob.WriteByte((byte)SignatureTypeCode.FunctionPointer);
                blob.WriteByte(functionPointer.CallingConvention);
                ImmutableArray<SignatureArg> arguments = MaterializeSignatureArguments(functionPointer.Arguments);
                blob.WriteCompressedInteger(arguments.Count(argument => !argument.IsSentinel));
                blob.LinkSuffix(MaterializeType(functionPointer.ReturnType));
                foreach (SignatureArg argument in arguments)
                {
                    blob.LinkSuffix(argument.SignatureBlob);
                }
                break;
            case IndexedGenericParameterElementTypeValue indexedGenericParameter:
                blob.WriteByte((byte)(indexedGenericParameter.IsMethodParameter
                    ? SignatureTypeCode.GenericMethodParameter
                    : SignatureTypeCode.GenericTypeParameter));
                // Always emit indexed generic parameters, including intentionally invalid IL.
                blob.WriteCompressedInteger(indexedGenericParameter.Index);
                break;
            case NamedGenericParameterElementTypeValue namedGenericParameter:
                WriteNamedGenericParameter(blob, namedGenericParameter);
                break;
            case TypedefElementTypeValue typedef:
                if (TryResolveTypedefAsTypeBlob(typedef.Alias) is { } resolved)
                {
                    resolved.WriteContentTo(blob);
                }
                else
                {
                    ReportError(
                        DiagnosticIds.TypedefNotFound,
                        string.Format(DiagnosticMessageTemplates.TypedefNotFound, typedef.Alias),
                        typedef.Token);
                }
                break;
            case SentinelElementTypeValue sentinel:
                blob.WriteByte((byte)SignatureTypeCode.Sentinel);
                blob.LinkSuffix(MaterializeType(sentinel.Type));
                break;
        }

        return blob;
    }

    private void WriteNamedGenericParameter(BlobBuilder blob, NamedGenericParameterElementTypeValue genericParameter)
    {
        blob.WriteByte((byte)(genericParameter.IsMethodParameter
            ? SignatureTypeCode.GenericMethodParameter
            : SignatureTypeCode.GenericTypeParameter));

        string name = genericParameter.Name;
        if (genericParameter.IsMethodParameter)
        {
            if (_currentMethod is null)
            {
                ReportError(
                    DiagnosticIds.MethodTypeParameterOutsideMethod,
                    string.Format(DiagnosticMessageTemplates.MethodTypeParameterOutsideMethod, name),
                    genericParameter.Token);
                blob.WriteCompressedInteger(0);
                return;
            }

            for (int i = 0; i < _currentMethod.Definition.GenericParameters.Count; i++)
            {
                if (_currentMethod.Definition.GenericParameters[i].Name == name)
                {
                    blob.WriteCompressedInteger(i);
                    return;
                }
            }
        }
        else
        {
            if (_currentTypeDefinition.Count == 0)
            {
                ReportError(
                    DiagnosticIds.TypeParameterOutsideType,
                    string.Format(DiagnosticMessageTemplates.TypeParameterOutsideType, name),
                    genericParameter.Token);
                blob.WriteCompressedInteger(0);
                return;
            }

            for (int i = 0; i < _currentTypeDefinition.Peek().GenericParameters.Count; i++)
            {
                if (_currentTypeDefinition.Peek().GenericParameters[i].Name == name)
                {
                    blob.WriteCompressedInteger(i);
                    return;
                }
            }
        }

        ReportError(
            DiagnosticIds.GenericParameterNotFound,
            string.Format(DiagnosticMessageTemplates.GenericParameterNotFound, name),
            genericParameter.Token);
        blob.WriteCompressedInteger(0);
    }

    private ImmutableArray<SignatureArg> MaterializeSignatureArguments(ImmutableArray<SignatureArgumentValue> arguments)
    {
        ImmutableArray<SignatureArg>.Builder builder = ImmutableArray.CreateBuilder<SignatureArg>(arguments.Length);
        foreach (SignatureArgumentValue argument in arguments)
        {
            builder.Add(MaterializeSignatureArgument(argument));
        }
        return builder.MoveToImmutable();
    }

    private SignatureArg MaterializeSignatureArgument(SignatureArgumentValue argument)
    {
        if (argument.IsSentinel)
        {
            return SignatureArg.CreateSentinelArgument();
        }

        return new SignatureArg(
            (System.Reflection.ParameterAttributes)argument.Attributes,
            MaterializeType(argument.Type ?? TypeValue.Error),
            MaterializeMarshallingDescriptor(argument.Marshalling),
            argument.Name);
    }

    private BlobBuilder MaterializeTypeArguments(ImmutableArray<TypeValue> arguments)
    {
        BlobBuilder blob = new(4);
        blob.WriteCompressedInteger(arguments.Length);
        foreach (TypeValue argument in arguments)
        {
            blob.LinkSuffix(MaterializeType(argument));
        }
        return blob;
    }

    private static void WriteArrayShape(BlobBuilder suffix, ImmutableArray<ArrayBoundValue> bounds)
    {
        suffix.WriteCompressedInteger(bounds.Length);

        int sizeCount = 0;
        while (sizeCount < bounds.Length && bounds[sizeCount].Upper is not null)
        {
            sizeCount++;
        }

        suffix.WriteCompressedInteger(sizeCount);
        for (int i = 0; i < sizeCount; i++)
        {
            suffix.WriteCompressedInteger(bounds[i].Upper.GetValueOrDefault());
        }

        int lowerBoundCount = 0;
        while (lowerBoundCount < bounds.Length && bounds[lowerBoundCount].Lower is not null)
        {
            lowerBoundCount++;
        }

        suffix.WriteCompressedInteger(lowerBoundCount);
        for (int i = 0; i < lowerBoundCount; i++)
        {
            suffix.WriteCompressedSignedInteger(bounds[i].Lower.GetValueOrDefault());
        }
    }

    private static SignatureTypeCode? TryGetPrimitiveTypeCode(EntityRegistry.TypeEntity typeEntity, bool isValueType)
    {
        if (typeEntity is not EntityRegistry.TypeReferenceEntity typeReference || typeReference.Namespace != "System")
        {
            return null;
        }

        if (isValueType)
        {
            return typeReference.Name switch
            {
                "Boolean" => SignatureTypeCode.Boolean,
                "Char" => SignatureTypeCode.Char,
                "SByte" => SignatureTypeCode.SByte,
                "Byte" => SignatureTypeCode.Byte,
                "Int16" => SignatureTypeCode.Int16,
                "UInt16" => SignatureTypeCode.UInt16,
                "Int32" => SignatureTypeCode.Int32,
                "UInt32" => SignatureTypeCode.UInt32,
                "Int64" => SignatureTypeCode.Int64,
                "UInt64" => SignatureTypeCode.UInt64,
                "Single" => SignatureTypeCode.Single,
                "Double" => SignatureTypeCode.Double,
                "IntPtr" => SignatureTypeCode.IntPtr,
                "UIntPtr" => SignatureTypeCode.UIntPtr,
                "TypedReference" => SignatureTypeCode.TypedReference,
                _ => null
            };
        }

        return typeReference.Name switch
        {
            "String" => SignatureTypeCode.String,
            "Object" => SignatureTypeCode.Object,
            _ => null
        };
    }
}