File: Marshaling\FuncJSGenerator.cs
Web Access
Project: src\src\libraries\System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj (Microsoft.Interop.JavaScript.JSImportGenerator)
// 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.Linq;
using System.Runtime.InteropServices.JavaScript;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
 
namespace Microsoft.Interop.JavaScript
{
    internal sealed class FuncJSGenerator : BaseJSGenerator
    {
        private readonly bool _isAction;
        private readonly MarshalerType[] _argumentMarshalerTypes;
 
        public FuncJSGenerator(TypePositionInfo info, StubCodeContext context, bool isAction, MarshalerType[] argumentMarshalerTypes)
            : base(isAction ? MarshalerType.Action : MarshalerType.Function, new Forwarder().Bind(info, context))
        {
            _isAction = isAction;
            _argumentMarshalerTypes = argumentMarshalerTypes;
        }
 
        public override IEnumerable<ExpressionSyntax> GenerateBind()
        {
            var args = _argumentMarshalerTypes.Select(x => Argument(MarshalerTypeName(x))).ToList();
            yield return InvocationExpression(MarshalerTypeName(Type), ArgumentList(SeparatedList(args)));
        }
 
        public override IEnumerable<StatementSyntax> Generate(StubIdentifierContext context)
        {
            string argName = context.GetAdditionalIdentifier(TypeInfo, "js_arg");
            var target = TypeInfo.IsManagedReturnPosition
                ? Constants.ArgumentReturn
                : argName;
 
            var source = TypeInfo.IsManagedReturnPosition
                ? Argument(IdentifierName(context.GetIdentifiers(TypeInfo).native))
                : _inner.AsArgument(context);
 
            var jsty = (JSFunctionTypeInfo)((JSMarshallingInfo)TypeInfo.MarshallingAttributeInfo).TypeInfo;
            var sourceTypes = jsty.ArgsTypeInfo
                .Select(a => a.Syntax)
                .ToArray();
 
            if (context.CurrentStage == StubIdentifierContext.Stage.UnmarshalCapture && CodeContext.Direction == MarshalDirection.ManagedToUnmanaged && TypeInfo.IsManagedReturnPosition)
            {
                yield return ToManagedMethod(target, source, jsty);
            }
 
            if (context.CurrentStage == StubIdentifierContext.Stage.Marshal && CodeContext.Direction == MarshalDirection.UnmanagedToManaged && TypeInfo.IsManagedReturnPosition)
            {
                yield return ToJSMethod(target, source, jsty);
            }
 
            foreach (var x in base.Generate(context))
            {
                yield return x;
            }
 
            if (context.CurrentStage == StubIdentifierContext.Stage.PinnedMarshal && CodeContext.Direction == MarshalDirection.ManagedToUnmanaged && !TypeInfo.IsManagedReturnPosition)
            {
                yield return ToJSMethod(target, source, jsty);
            }
 
            if (context.CurrentStage == StubIdentifierContext.Stage.Unmarshal && CodeContext.Direction == MarshalDirection.UnmanagedToManaged && !TypeInfo.IsManagedReturnPosition)
            {
                yield return ToManagedMethod(target, source, jsty);
            }
        }
 
        private ExpressionStatementSyntax ToManagedMethod(string target, ArgumentSyntax source, JSFunctionTypeInfo info)
        {
            List<ArgumentSyntax> arguments = [source.WithRefOrOutKeyword(Token(SyntaxKind.OutKeyword))];
            for (int i = 0; i < info.ArgsTypeInfo.Length; i++)
            {
                var sourceType = info.ArgsTypeInfo[i];
                if (!_isAction && i + 1 == info.ArgsTypeInfo.Length)
                {
                    arguments.Add(ArgToManaged(i, sourceType.Syntax, _argumentMarshalerTypes[i]));
                }
                else
                {
                    arguments.Add(ArgToJS(i, sourceType.Syntax, _argumentMarshalerTypes[i]));
                }
            }
 
            return ExpressionStatement(InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
                    IdentifierName(target), GetToManagedMethod(Type)))
                .WithArgumentList(ArgumentList(SeparatedList(arguments))));
        }
 
        private ExpressionStatementSyntax ToJSMethod(string target, ArgumentSyntax source, JSFunctionTypeInfo info)
        {
            List<ArgumentSyntax> arguments = [source];
            for (int i = 0; i < info.ArgsTypeInfo.Length; i++)
            {
                var sourceType = info.ArgsTypeInfo[i];
                if (!_isAction && i + 1 == info.ArgsTypeInfo.Length)
                {
                    arguments.Add(ArgToJS(i, sourceType.Syntax, _argumentMarshalerTypes[i]));
                }
                else
                {
                    arguments.Add(ArgToManaged(i, sourceType.Syntax, _argumentMarshalerTypes[i]));
                }
            }
 
            return ExpressionStatement(InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
                    IdentifierName(target), GetToJSMethod(Type)))
                .WithArgumentList(ArgumentList(SeparatedList(arguments))));
        }
 
        private static ArgumentSyntax ArgToJS(int i, TypeSyntax sourceType, MarshalerType marshalerType) => Argument(ParenthesizedLambdaExpression()
                            .WithModifiers(TokenList(Token(SyntaxKind.StaticKeyword)))
                            .WithParameterList(ParameterList(SeparatedList(new[]{
                        Parameter(Identifier("__delegate_arg_arg"+(i+1)))
                        .WithModifiers(TokenList(Token(SyntaxKind.RefKeyword)))
                        .WithType(IdentifierName(Constants.JSMarshalerArgumentGlobal)),
                        Parameter(Identifier("__delegate_arg"+(i+1)))
                        .WithType(sourceType)})))
                            .WithBlock(Block(SingletonList<StatementSyntax>(ExpressionStatement(
                                InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
                                IdentifierName("__delegate_arg_arg" + (i + 1)), GetToJSMethod(marshalerType)))
                                .WithArgumentList(ArgumentList(SeparatedList(new[]{
                            Argument(IdentifierName("__delegate_arg"+(i+1))),
                                }))))))));
 
        private static ArgumentSyntax ArgToManaged(int i, TypeSyntax sourceType, MarshalerType marshalerType) => Argument(ParenthesizedLambdaExpression()
                            .WithModifiers(TokenList(Token(SyntaxKind.StaticKeyword)))
                            .WithParameterList(ParameterList(SeparatedList(new[]{
                        Parameter(Identifier("__delegate_arg_arg"+(i+1)))
                        .WithModifiers(TokenList(Token(SyntaxKind.RefKeyword)))
                        .WithType(IdentifierName(Constants.JSMarshalerArgumentGlobal)),
                        Parameter(Identifier("__delegate_arg"+(i+1)))
                        .WithModifiers(TokenList(Token(SyntaxKind.OutKeyword)))
                        .WithType(sourceType)})))
                            .WithBlock(Block(SingletonList<StatementSyntax>(ExpressionStatement(
                                InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
                                IdentifierName("__delegate_arg_arg" + (i + 1)), GetToManagedMethod(marshalerType)))
                                .WithArgumentList(ArgumentList(SeparatedList(new[]{
                            Argument(IdentifierName("__delegate_arg"+(i+1))).WithRefOrOutKeyword(Token(SyntaxKind.OutKeyword)),
                                }))))))));
    }
}