File: Marshalling\MarshallingGeneratorExtensions.cs
Web Access
Project: Microsoft.Interop.SourceGeneration.csproj (Microsoft.Interop.SourceGeneration)
// 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;

namespace Microsoft.Interop
{
    public static class MarshallingGeneratorExtensions
    {
        private const string ParameterIdentifierSuffix = "param";

        /// <summary>
        /// Gets the return type for the unmanaged signature.
        /// </summary>
        public static string AsReturnType(this IBoundMarshallingGenerator generator)
        {
            return generator.NativeSignatureBehavior switch
            {
                SignatureBehavior.ManagedTypeAndAttributes => generator.TypeInfo.ManagedType.FullTypeName,
                SignatureBehavior.NativeType => generator.NativeType.FullTypeName,
                SignatureBehavior.PointerToNativeType => $"{generator.NativeType.FullTypeName}*",
                _ => throw new InvalidOperationException()
            };
        }

        /// <summary>
        /// Gets the attribute bodies for the unmanaged return type, without brackets or a return target.
        /// </summary>
        public static string? GenerateAttributesForReturnType(this IBoundMarshallingGenerator generator)
        {
            if (generator.NativeSignatureBehavior == SignatureBehavior.ManagedTypeAndAttributes
                && generator.TypeInfo.MarshallingAttributeInfo is IForwardedMarshallingInfo forwarded
                && forwarded.TryCreateAttribute(out string? attribute))
            {
                return attribute;
            }

            return null;
        }

        /// <summary>
        /// Gets a parameter for the unmanaged signature.
        /// </summary>
        public static GeneratedParameter AsParameter(this IBoundMarshallingGenerator generator, StubIdentifierContext context)
        {
            SignatureBehavior behavior = generator.NativeSignatureBehavior;
            if (behavior == SignatureBehavior.ManagedTypeAndAttributes)
            {
                return GenerateForwardingParameter(generator.TypeInfo, context.GetIdentifiers(generator.TypeInfo).managed);
            }

            string identifierName;
            if (generator.CodeContext.Direction == MarshalDirection.ManagedToUnmanaged)
            {
                // This name doesn't get introduced into the stub's scope, so we can make it pretty
                // and reuse the native identifier
                identifierName = context.GetIdentifiers(generator.TypeInfo).native;
            }
            else if (generator.CodeContext.Direction == MarshalDirection.UnmanagedToManaged)
            {
                // This name is introduced into the stub's scope.
                // When we are passing the managed identifier as-is, we can just use that name everywhere.
                // When we're passing the native identifier as-is or casting the value to the native type in managed->unmanaged cases,
                // we can use the native identifier.
                // When we're passing the address of the native identifier, we need to introduce a new name to hold this value
                // before we assign it to the managed value.
                (string managed, string native) = context.GetIdentifiers(generator.TypeInfo);
                string param = context.GetAdditionalIdentifier(generator.TypeInfo, ParameterIdentifierSuffix);
                identifierName = generator.ValueBoundaryBehavior switch
                {
                    ValueBoundaryBehavior.ManagedIdentifier => generator.TypeInfo.IsByRef ? param : managed,
                    ValueBoundaryBehavior.NativeIdentifier or ValueBoundaryBehavior.CastNativeIdentifier => native,
                    ValueBoundaryBehavior.AddressOfNativeIdentifier => param,
                    _ => throw new UnreachableException()
                };
            }
            else
            {
                throw new ArgumentException("Context direction must be ManagedToUnmanaged or UnmanagedToManaged");
            }

            string type = behavior switch
            {
                SignatureBehavior.NativeType => generator.NativeType.FullTypeName,
                SignatureBehavior.PointerToNativeType => $"{generator.NativeType.FullTypeName}*",
                _ => throw new InvalidOperationException()
            };
            return new GeneratedParameter(type, identifierName);
        }

        private static GeneratedParameter GenerateForwardingParameter(TypePositionInfo info, string identifier)
        {
            List<string> attributes = [];
            if (info.MarshallingAttributeInfo is IForwardedMarshallingInfo forwarded
                && forwarded.TryCreateAttribute(out string? attribute))
            {
                attributes.Add(attribute);
            }
            if (info.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.In))
            {
                attributes.Add(TypeNames.GlobalAlias + TypeNames.System_Runtime_InteropServices_InAttribute);
            }
            if (info.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out))
            {
                attributes.Add(TypeNames.GlobalAlias + TypeNames.System_Runtime_InteropServices_OutAttribute);
            }

            return new GeneratedParameter(
                info.ManagedType.FullTypeName,
                identifier,
                MarshallerHelpers.GetManagedParameterModifiers(info),
                attributes.Count == 0 ? null : string.Join(", ", attributes));
        }

        /// <summary>
        /// Gets an argument for passing a value across the managed/native boundary.
        /// </summary>
        public static string AsArgument(this IBoundMarshallingGenerator generator, StubIdentifierContext context)
        {
            TypePositionInfo info = generator.TypeInfo;
            (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info);
            return generator.ValueBoundaryBehavior switch
            {
                ValueBoundaryBehavior.ManagedIdentifier when !info.IsByRef => managedIdentifier,
                ValueBoundaryBehavior.ManagedIdentifier => $"{MarshallerHelpers.GetManagedArgumentRefKindKeyword(info)} {managedIdentifier}",
                ValueBoundaryBehavior.NativeIdentifier => nativeIdentifier,
                ValueBoundaryBehavior.AddressOfNativeIdentifier => $"&{nativeIdentifier}",
                ValueBoundaryBehavior.CastNativeIdentifier => $"({generator.AsParameter(context).Type}){nativeIdentifier}",
                _ => throw new InvalidOperationException()
            };
        }

        public static string AsManagedArgument(this IBoundMarshallingGenerator generator, StubIdentifierContext context)
        {
            TypePositionInfo info = generator.TypeInfo;
            string managedIdentifier = context.GetIdentifiers(info).managed;
            return info.IsByRef
                ? $"{MarshallerHelpers.GetManagedArgumentRefKindKeyword(info)} {managedIdentifier}"
                : managedIdentifier;
        }

        public static string GenerateNativeByRefInitialization(this IBoundMarshallingGenerator generator, StubIdentifierContext context)
        {
            string paramIdentifier = context.GetAdditionalIdentifier(generator.TypeInfo, ParameterIdentifierSuffix);
            return $"ref *{paramIdentifier}";
        }

        public static bool IsForwarder(this IBoundMarshallingGenerator generator) => generator is BoundMarshallingGenerator { IsForwarder: true };

        public static bool IsBlittable(this IBoundMarshallingGenerator generator) => generator is BoundMarshallingGenerator { IsBlittable: true };
    }
}