File: ManualTypeMarshallingHelper.cs
Web Access
Project: src\src\libraries\System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\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;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
 
namespace Microsoft.Interop
{
    public readonly record struct CustomTypeMarshallerData(
        ManagedTypeInfo MarshallerType,
        ManagedTypeInfo NativeType,
        bool HasState,
        MarshallerShape Shape,
        bool IsStrictlyBlittable,
        ManagedTypeInfo? BufferElementType,
        ManagedTypeInfo? CollectionElementType,
        MarshallingInfo? CollectionElementMarshallingInfo);
 
    public readonly record struct CustomTypeMarshallers(
        ImmutableDictionary<MarshalMode, CustomTypeMarshallerData> Modes)
    {
        public bool Equals(CustomTypeMarshallers other)
        {
            // Check for equal count, then check if any KeyValuePairs exist in one 'Modes'
            // but not the other (i.e. set equality on the set of items in the dictionary)
            return Modes.Count == other.Modes.Count
                && !Modes.Except(other.Modes).Any();
        }
 
        public override int GetHashCode()
        {
            int hash = 0;
            foreach (KeyValuePair<MarshalMode, CustomTypeMarshallerData> mode in Modes)
            {
                hash = HashCode.Combine(hash, mode.Key, mode.Value);
            }
            return hash;
        }
 
        public CustomTypeMarshallerData GetModeOrDefault(MarshalMode mode)
        {
            CustomTypeMarshallerData data;
            if (Modes.TryGetValue(mode, out data))
                return data;
 
            if (Modes.TryGetValue(MarshalMode.Default, out data))
                return data;
 
            // TODO: Hard failure based on previous implementation
            throw new InvalidOperationException();
        }
 
        public bool TryGetModeOrDefault(MarshalMode mode, out CustomTypeMarshallerData data)
        {
            if (Modes.TryGetValue(mode, out data))
                return true;
 
            return Modes.TryGetValue(MarshalMode.Default, out data);
        }
 
        public bool IsDefinedOrDefault(MarshalMode mode)
        {
            return Modes.ContainsKey(mode) || Modes.ContainsKey(MarshalMode.Default);
        }
    }
 
    public static class ManualTypeMarshallingHelper
    {
        public static class MarshalUsingProperties
        {
            public const string ElementIndirectionDepth = nameof(ElementIndirectionDepth);
            public const string CountElementName = nameof(CountElementName);
            public const string ConstantElementCount = nameof(ConstantElementCount);
        }
 
        private static void IgnoreArityMismatch(INamedTypeSymbol _, INamedTypeSymbol __) { }
 
        public static bool IsLinearCollectionEntryPoint(INamedTypeSymbol entryPointType)
        {
            return entryPointType.IsGenericType
                && entryPointType.GetAttributes().Any(attr => attr.AttributeClass.ToDisplayString() == TypeNames.ContiguousCollectionMarshallerAttribute);
        }
 
        public static bool HasEntryPointMarshallerAttribute(ITypeSymbol entryPointType)
        {
            return entryPointType.GetAttributes().Any(attr => attr.AttributeClass.ToDisplayString() == TypeNames.CustomMarshallerAttribute);
        }
 
        public static bool TryGetValueMarshallersFromEntryType(
            INamedTypeSymbol entryPointType,
            ITypeSymbol managedType,
            Compilation compilation,
            out CustomTypeMarshallers? marshallers)
        {
            return TryGetMarshallersFromEntryType(entryPointType, managedType, isLinearCollectionMarshalling: false, compilation, getMarshallingInfoForElement: null, IgnoreArityMismatch, out marshallers);
        }
 
        public static bool TryGetValueMarshallersFromEntryType(
            INamedTypeSymbol entryPointType,
            ITypeSymbol managedType,
            Compilation compilation,
            Action<INamedTypeSymbol, INamedTypeSymbol> onArityMismatch,
            out CustomTypeMarshallers? marshallers)
        {
            return TryGetMarshallersFromEntryType(entryPointType, managedType, isLinearCollectionMarshalling: false, compilation, getMarshallingInfoForElement: null, onArityMismatch, out marshallers);
        }
 
        public static bool TryGetLinearCollectionMarshallersFromEntryType(
            INamedTypeSymbol entryPointType,
            ITypeSymbol managedType,
            Compilation compilation,
            Func<ITypeSymbol, MarshallingInfo> getMarshallingInfo,
            out CustomTypeMarshallers? marshallers)
        {
            return TryGetMarshallersFromEntryType(entryPointType, managedType, isLinearCollectionMarshalling: true, compilation, getMarshallingInfo, IgnoreArityMismatch, out marshallers);
        }
 
        public static bool TryGetLinearCollectionMarshallersFromEntryType(
            INamedTypeSymbol entryPointType,
            ITypeSymbol managedType,
            Compilation compilation,
            Func<ITypeSymbol, MarshallingInfo> getMarshallingInfo,
            Action<INamedTypeSymbol, INamedTypeSymbol> onArityMismatch,
            out CustomTypeMarshallers? marshallers)
        {
            return TryGetMarshallersFromEntryType(entryPointType, managedType, isLinearCollectionMarshalling: true, compilation, getMarshallingInfo, onArityMismatch, out marshallers);
        }
 
        public static bool TryGetMarshallersFromEntryTypeIgnoringElements(
            INamedTypeSymbol entryPointType,
            ITypeSymbol managedType,
            Compilation compilation,
            Action<INamedTypeSymbol, INamedTypeSymbol> onArityMismatch,
            out CustomTypeMarshallers? marshallers)
        {
            bool isLinearCollectionMarshalling = IsLinearCollectionEntryPoint(entryPointType);
            return TryGetMarshallersFromEntryType(entryPointType, managedType, isLinearCollectionMarshalling, compilation, _ => NoMarshallingInfo.Instance, onArityMismatch, out marshallers);
        }
 
        private static bool TryGetMarshallersFromEntryType(
            INamedTypeSymbol entryPointType,
            ITypeSymbol managedType,
            bool isLinearCollectionMarshalling,
            Compilation compilation,
            Func<ITypeSymbol, MarshallingInfo> getMarshallingInfoForElement,
            Action<INamedTypeSymbol, INamedTypeSymbol> onArityMismatch,
            out CustomTypeMarshallers? marshallers)
        {
            marshallers = null;
            var attrs = entryPointType.GetAttributes().Where(attr => attr.AttributeClass.ToDisplayString() == TypeNames.CustomMarshallerAttribute).ToArray();
            if (attrs is null || attrs.Length == 0)
                return false;
 
            // We expect a callback for getting the element marshalling info when handling linear collection marshalling
            if (isLinearCollectionMarshalling && getMarshallingInfoForElement is null)
                return false;
 
            Dictionary<MarshalMode, CustomTypeMarshallerData> modes = new();
 
            foreach (AttributeData attr in attrs)
            {
                if (attr.AttributeConstructor is null)
                {
                    // If the attribute constructor couldn't be bound by the compiler, then we shouldn't try to extract the constructor arguments.
                    // Roslyn doesn't provide them if it can't bind the constructor.
                    // We don't report a diagnostic here since Roslyn will report a diagnostic anyway.
                    continue;
                }
 
                if (attr.ConstructorArguments.Length != 3)
                {
                    Debug.WriteLine($"{attr} has {attr.ConstructorArguments.Length} constructor arguments - expected 3");
                    continue;
                }
 
                // Verify the defined marshaller is for the managed type.
                ITypeSymbol? managedTypeOnAttr = attr.ConstructorArguments[0].Value as ITypeSymbol;
 
                // If there is no managed type on this attribute, then the attribute is invalid.
                // Skip the entry as it definitely won't match the provided managed type.
                if (managedTypeOnAttr is null)
                    continue;
 
                // Resolve the provided managed type to a specific generic instantiation based on the marshaller type
                // If we're unable to resolve the instantiation based on our rules, skip this attribute. It is invalid.
                if (!TryResolveManagedType(entryPointType, ReplaceGenericPlaceholderInType(managedTypeOnAttr, entryPointType, compilation), isLinearCollectionMarshalling, onArityMismatch, out ITypeSymbol managedTypeInst))
                {
                    continue;
                }
                if (managedTypeInst is null)
                    return false;
 
                // If this marshaller is for a different type, skip it.
                if (!SymbolEqualityComparer.Default.Equals(managedType, managedTypeInst))
                {
                    continue;
                }
 
                var marshalMode = (MarshalMode)attr.ConstructorArguments[1].Value!;
 
                ITypeSymbol? marshallerTypeOnAttr = attr.ConstructorArguments[2].Value as ITypeSymbol;
                if (marshallerTypeOnAttr is null)
                    continue;
 
                ITypeSymbol marshallerType = marshallerTypeOnAttr;
                if (!TryResolveMarshallerType(entryPointType, marshallerType, IgnoreArityMismatch, out marshallerType))
                {
                    continue;
                }
 
                // TODO: Report invalid shape for mode
                //       Skip checking for bidirectional support for Default mode - always take / store marshaller data
                CustomTypeMarshallerData? data = GetMarshallerDataForType(marshallerType, marshalMode, managedType, isLinearCollectionMarshalling, compilation, getMarshallingInfoForElement);
 
                // TODO: Should we fire a diagnostic for duplicated modes or just take the last one?
                if (data is null
                    || modes.ContainsKey(marshalMode))
                {
                    continue;
                }
 
                modes.Add(marshalMode, data.Value);
            }
 
            if (modes.Count == 0)
                return false;
 
            marshallers = new CustomTypeMarshallers()
            {
                Modes = modes.ToImmutableDictionary()
            };
 
            return true;
        }
 
        public static bool TryResolveEntryPointType(INamedTypeSymbol managedType, ITypeSymbol typeInAttribute, bool isLinearCollectionMarshalling, Action<INamedTypeSymbol, INamedTypeSymbol> onArityMismatch, [NotNullWhen(true)] out ITypeSymbol? entryPoint)
        {
            if (typeInAttribute is not INamedTypeSymbol entryPointType)
            {
                entryPoint = typeInAttribute;
                return true;
            }
 
            if (!entryPointType.IsUnboundGenericType)
            {
                entryPoint = typeInAttribute;
                return true;
            }
 
            INamedTypeSymbol instantiatedEntryType = entryPointType.ResolveUnboundConstructedTypeToConstructedType(managedType, out int numOriginalArgsSubstituted, out int extraArgumentsInTemplate);
 
            entryPoint = instantiatedEntryType;
            if (extraArgumentsInTemplate > 0)
            {
                onArityMismatch(managedType.OriginalDefinition, instantiatedEntryType.OriginalDefinition);
                return false;
            }
            if (!isLinearCollectionMarshalling && numOriginalArgsSubstituted != 0)
            {
                onArityMismatch(managedType.OriginalDefinition, instantiatedEntryType.OriginalDefinition);
                return false;
            }
            else if (isLinearCollectionMarshalling && numOriginalArgsSubstituted != 1)
            {
                onArityMismatch(managedType.OriginalDefinition, instantiatedEntryType.OriginalDefinition);
                return false;
            }
 
            return true;
        }
 
        /// <summary>
        /// Resolve the (possibly unbound generic) type to a fully constructed type based on the entry point type's generic parameters.
        /// </summary>
        /// <param name="entryPointType">The entry point type</param>
        /// <param name="typeInAttribute">The managed type from the CustomMarshallerAttribute</param>
        /// <returns>A fully constructed marshaller type</returns>
        public static bool TryResolveManagedType(INamedTypeSymbol entryPointType, ITypeSymbol typeInAttribute, bool isLinearCollectionMarshalling, Action<INamedTypeSymbol, INamedTypeSymbol> onArityMismatch, [NotNullWhen(true)] out ITypeSymbol? managed)
        {
            if (typeInAttribute is not INamedTypeSymbol namedMarshallerType)
            {
                managed = typeInAttribute;
                return true;
            }
 
            if (!namedMarshallerType.IsUnboundGenericType)
            {
                managed = namedMarshallerType;
                return true;
            }
 
 
            INamedTypeSymbol instantiatedManagedType = namedMarshallerType.ResolveUnboundConstructedTypeToConstructedType(entryPointType, out int numOriginalArgsSubstituted, out int extraArgumentsInTemplate);
 
            managed = instantiatedManagedType;
            if (numOriginalArgsSubstituted > 0)
            {
                onArityMismatch(entryPointType.OriginalDefinition, instantiatedManagedType.OriginalDefinition);
                return false;
            }
            if (!isLinearCollectionMarshalling && extraArgumentsInTemplate != 0)
            {
                onArityMismatch(entryPointType.OriginalDefinition, instantiatedManagedType.OriginalDefinition);
                return false;
            }
            else if (isLinearCollectionMarshalling && extraArgumentsInTemplate != 1)
            {
                onArityMismatch(entryPointType.OriginalDefinition, instantiatedManagedType.OriginalDefinition);
                return false;
            }
 
            return true;
        }
 
        /// <summary>
        /// Resolve the (possibly unbound generic) type to a fully constructed type based on the entry point type's generic parameters.
        /// </summary>
        /// <param name="entryPointType">The entry point type</param>
        /// <param name="typeInAttribute">The marshaller type from the CustomMarshallerAttribute</param>
        /// <returns>A fully constructed marshaller type</returns>
        public static bool TryResolveMarshallerType(INamedTypeSymbol entryPointType, ITypeSymbol typeInAttribute, Action<INamedTypeSymbol, INamedTypeSymbol> onArityMismatch, [NotNullWhen(true)] out ITypeSymbol? marshallerType)
        {
            if (typeInAttribute is not INamedTypeSymbol namedMarshallerType)
            {
                marshallerType = typeInAttribute;
                return true;
            }
 
            INamedTypeSymbol instantiatedMarshallerType = namedMarshallerType.ResolveUnboundConstructedTypeToConstructedType(entryPointType, out int numOriginalArgsSubstituted, out int extraArgumentsInTemplate);
 
            marshallerType = instantiatedMarshallerType;
 
            if (numOriginalArgsSubstituted != 0 || extraArgumentsInTemplate != 0)
            {
                onArityMismatch(entryPointType.OriginalDefinition, instantiatedMarshallerType.OriginalDefinition);
                return false;
            }
 
            return true;
        }
 
        /// <summary>
        /// Resolve a non-<see cref="INamedTypeSymbol"/> <paramref name="managedType"/> to the correct
        /// managed type if <paramref name="entryType"/> is generic and <paramref name="managedType"/>
        /// is using any placeholder types.
        /// </summary>
        /// <param name="managedType">The non-named managed type.</param>
        /// <param name="entryType">The marshaller type.</param>
        /// <param name="compilation">The compilation to use to make new type symbols.</param>
        /// <returns>The resolved managed type, or <paramref name="managedType"/> if the provided type did not have any placeholders.</returns>
        public static ITypeSymbol ReplaceGenericPlaceholderInType(ITypeSymbol managedType, INamedTypeSymbol entryType, Compilation compilation)
        {
            if (!entryType.IsGenericType)
            {
                return managedType;
            }
            Stack<ITypeSymbol> typeStack = new();
            ITypeSymbol innerType = managedType;
            while (innerType.TypeKind is TypeKind.Array or TypeKind.Pointer)
            {
                if (innerType is IArrayTypeSymbol array)
                {
                    typeStack.Push(innerType);
                    innerType = array.ElementType;
                }
                else if (innerType is IPointerTypeSymbol pointerType)
                {
                    typeStack.Push(innerType);
                    innerType = pointerType.PointedAtType;
                }
            }
 
            if (innerType.ToDisplayString() != TypeNames.CustomMarshallerAttributeGenericPlaceholder)
            {
                return managedType;
            }
 
            ITypeSymbol resultType = entryType.TypeArguments[0];
 
            while (typeStack.Count > 0)
            {
                ITypeSymbol wrapperType = typeStack.Pop();
                if (wrapperType.TypeKind == TypeKind.Pointer)
                {
                    resultType = compilation.CreatePointerTypeSymbol(resultType);
                }
                else if (wrapperType.TypeKind == TypeKind.Array)
                {
                    IArrayTypeSymbol arrayType = (IArrayTypeSymbol)wrapperType;
                    if (arrayType.IsSZArray)
                    {
                        resultType = compilation.CreateArrayTypeSymbol(resultType, arrayType.Rank);
                    }
                }
            }
            return resultType;
        }
 
        private static CustomTypeMarshallerData? GetMarshallerDataForType(
            ITypeSymbol marshallerType,
            MarshalMode mode,
            ITypeSymbol managedType,
            bool isLinearCollectionMarshaller,
            Compilation compilation,
            Func<ITypeSymbol, MarshallingInfo> getMarshallingInfo)
        {
            if (marshallerType is { IsStatic: true, TypeKind: TypeKind.Class })
            {
                return GetStatelessMarshallerDataForType(marshallerType, mode, managedType, isLinearCollectionMarshaller, compilation, getMarshallingInfo);
            }
            if (marshallerType.IsValueType)
            {
                return GetStatefulMarshallerDataForType(marshallerType, mode, managedType, isLinearCollectionMarshaller, compilation, getMarshallingInfo);
            }
            return null;
        }
 
        public static bool ModeUsesManagedToUnmanagedShape(MarshalMode mode)
            => mode is MarshalMode.Default
                or MarshalMode.ManagedToUnmanagedIn
                or MarshalMode.ElementIn
                or MarshalMode.UnmanagedToManagedOut
                or MarshalMode.ElementOut
                or MarshalMode.ManagedToUnmanagedRef
                or MarshalMode.UnmanagedToManagedRef
                or MarshalMode.ElementRef;
 
        public static bool ModeUsesUnmanagedToManagedShape(MarshalMode mode)
            => mode is MarshalMode.Default
                or MarshalMode.ManagedToUnmanagedOut
                or MarshalMode.ElementOut
                or MarshalMode.UnmanagedToManagedIn
                or MarshalMode.ElementIn
                or MarshalMode.ManagedToUnmanagedRef
                or MarshalMode.UnmanagedToManagedRef
                or MarshalMode.ElementRef;
 
        // These modes can be used with either shape,
        // but we don't want to require that all marshallers implement both shapes.
        // The CustomMarshallerAttributeAnalyzer will provide any enforcement we would like
        // on these marshaller shapes.
        private static bool ModeOptionallyMatchesShape(MarshalMode mode)
            => mode is MarshalMode.Default
                or MarshalMode.ElementIn
                or MarshalMode.ElementRef
                or MarshalMode.ElementOut;
 
        private static CustomTypeMarshallerData? GetStatelessMarshallerDataForType(ITypeSymbol marshallerType, MarshalMode mode, ITypeSymbol managedType, bool isLinearCollectionMarshaller, Compilation compilation, Func<ITypeSymbol, MarshallingInfo>? getMarshallingInfo)
        {
            (MarshallerShape shape, StatelessMarshallerShapeHelper.MarshallerMethods methods) = StatelessMarshallerShapeHelper.GetShapeForType(marshallerType, managedType, isLinearCollectionMarshaller, compilation);
 
            ITypeSymbol? collectionElementType = null;
            ITypeSymbol? nativeType = null;
            if (ModeUsesManagedToUnmanagedShape(mode))
            {
                if (!ModeOptionallyMatchesShape(mode) && !shape.HasFlag(MarshallerShape.CallerAllocatedBuffer) && !shape.HasFlag(MarshallerShape.ToUnmanaged))
                    return null;
 
                if (isLinearCollectionMarshaller && methods.ManagedValuesSource is not null)
                {
                    // Element type is the type parameter of the ReadOnlySpan returned by GetManagedValuesSource
                    collectionElementType = ((INamedTypeSymbol)methods.ManagedValuesSource.ReturnType).TypeArguments[0];
                }
 
                // Native type is the return type of ConvertToUnmanaged / AllocateContainerForUnmanagedElement
                if (methods.ToUnmanagedWithBuffer is not null)
                {
                    nativeType = methods.ToUnmanagedWithBuffer.ReturnType;
                }
                else if (methods.ToUnmanaged is not null)
                {
                    nativeType = methods.ToUnmanaged.ReturnType;
                }
            }
 
            if (ModeUsesUnmanagedToManagedShape(mode))
            {
                // Unmanaged to managed requires ToManaged either with or without guaranteed unmarshal
                if (!ModeOptionallyMatchesShape(mode) && !shape.HasFlag(MarshallerShape.GuaranteedUnmarshal) && !shape.HasFlag(MarshallerShape.ToManaged))
                    return null;
 
                if (isLinearCollectionMarshaller)
                {
                    if (nativeType is null && methods.UnmanagedValuesSource is not null)
                    {
                        // Native type is the first parameter of GetUnmanagedValuesSource
                        nativeType = methods.UnmanagedValuesSource.Parameters[0].Type;
                    }
 
                    if (collectionElementType is null && methods.ManagedValuesDestination is not null)
                    {
                        // Element type is the type parameter of the Span returned by GetManagedValuesDestination
                        collectionElementType = ((INamedTypeSymbol)methods.ManagedValuesDestination.ReturnType).TypeArguments[0];
                    }
                }
                else if (nativeType is null)
                {
                    // Native type is the first parameter of ConvertToManaged or ConvertToManagedFinally
                    if (methods.ToManagedFinally is not null)
                    {
                        nativeType = methods.ToManagedFinally.Parameters[0].Type;
                    }
                    else if (methods.ToManaged is not null)
                    {
                        nativeType = methods.ToManaged.Parameters[0].Type;
                    }
                }
            }
 
            // Bidirectional requires ToUnmanaged without the caller-allocated buffer
            if (!ModeOptionallyMatchesShape(mode) && ModeUsesManagedToUnmanagedShape(mode) && ModeUsesUnmanagedToManagedShape(mode) && !shape.HasFlag(MarshallerShape.ToUnmanaged))
                return null;
 
            if (nativeType is null)
                return null;
 
            ManagedTypeInfo bufferElementType = null;
            if (methods.ToUnmanagedWithBuffer is not null)
            {
                bufferElementType = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(((INamedTypeSymbol)methods.ToUnmanagedWithBuffer.Parameters[1].Type).TypeArguments[0]);
            }
 
            ManagedTypeInfo? collectionElementTypeInfo = null;
            MarshallingInfo? collectionElementMarshallingInfo = null;
            if (collectionElementType is not null)
            {
                collectionElementTypeInfo = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(collectionElementType);
                collectionElementMarshallingInfo = getMarshallingInfo(collectionElementType);
            }
 
            return new CustomTypeMarshallerData(
                ManagedTypeInfo.CreateTypeInfoForTypeSymbol(marshallerType),
                ManagedTypeInfo.CreateTypeInfoForTypeSymbol(nativeType),
                HasState: false,
                shape,
                nativeType.IsStrictlyBlittableInContext(compilation),
                bufferElementType,
                collectionElementTypeInfo,
                collectionElementMarshallingInfo);
        }
 
        private static CustomTypeMarshallerData? GetStatefulMarshallerDataForType(
            ITypeSymbol marshallerType,
            MarshalMode mode,
            ITypeSymbol managedType,
            bool isLinearCollectionMarshaller,
            Compilation compilation,
            Func<ITypeSymbol, MarshallingInfo>? getMarshallingInfo)
        {
            (MarshallerShape shape, StatefulMarshallerShapeHelper.MarshallerMethods methods) = StatefulMarshallerShapeHelper.GetShapeForType(marshallerType, managedType, isLinearCollectionMarshaller, compilation);
 
            ITypeSymbol? nativeType = null;
            ITypeSymbol? collectionElementType = null;
            if (ModeUsesManagedToUnmanagedShape(mode))
            {
                // Managed to unmanaged requires ToUnmanaged either with or without the caller-allocated buffer
                if (mode != MarshalMode.Default && !shape.HasFlag(MarshallerShape.CallerAllocatedBuffer) && !shape.HasFlag(MarshallerShape.ToUnmanaged))
                    return null;
 
                if (methods.ToUnmanaged is not null)
                {
                    nativeType = methods.ToUnmanaged.ReturnType;
                }
 
                if (isLinearCollectionMarshaller && methods.ManagedValuesSource is not null)
                {
                    // Element type is the type parameter of the ReadOnlySpan returned by GetManagedValuesSource
                    collectionElementType = ((INamedTypeSymbol)methods.ManagedValuesSource.ReturnType).TypeArguments[0];
                }
            }
 
            if (ModeUsesUnmanagedToManagedShape(mode))
            {
                // Unmanaged to managed requires ToManaged either with or without guaranteed unmarshal
                if (mode != MarshalMode.Default && !shape.HasFlag(MarshallerShape.GuaranteedUnmarshal) && !shape.HasFlag(MarshallerShape.ToManaged))
                    return null;
 
                if (methods.FromUnmanaged is not null && nativeType is null)
                {
                    nativeType = methods.FromUnmanaged.Parameters[0].Type;
                }
 
                if (isLinearCollectionMarshaller && collectionElementType is null && methods.ManagedValuesDestination is not null)
                {
                    // Element type is the type parameter of the Span returned by GetManagedValuesDestination
                    collectionElementType = ((INamedTypeSymbol)methods.ManagedValuesDestination.ReturnType).TypeArguments[0];
                }
            }
 
            // Bidirectional requires ToUnmanaged without the caller-allocated buffer
            if (mode != MarshalMode.Default && ModeUsesManagedToUnmanagedShape(mode) && ModeUsesUnmanagedToManagedShape(mode) && !shape.HasFlag(MarshallerShape.ToUnmanaged))
                return null;
 
            if (nativeType is null)
                return null;
 
            ManagedTypeInfo bufferElementType = null;
            if (methods.FromManagedWithBuffer is not null)
            {
                bufferElementType = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(((INamedTypeSymbol)methods.FromManagedWithBuffer.Parameters[1].Type).TypeArguments[0]);
            }
 
            ManagedTypeInfo? collectionElementTypeInfo = null;
            MarshallingInfo? collectionElementMarshallingInfo = null;
            if (collectionElementType is not null)
            {
                collectionElementTypeInfo = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(collectionElementType);
                collectionElementMarshallingInfo = getMarshallingInfo(collectionElementType);
            }
 
            return new CustomTypeMarshallerData(
                ManagedTypeInfo.CreateTypeInfoForTypeSymbol(marshallerType),
                ManagedTypeInfo.CreateTypeInfoForTypeSymbol(nativeType),
                HasState: true,
                shape,
                nativeType.IsStrictlyBlittableInContext(compilation),
                bufferElementType,
                CollectionElementType: collectionElementTypeInfo,
                CollectionElementMarshallingInfo: collectionElementMarshallingInfo);
        }
    }
}