Browse Source

Support params arrays in delegate invocation (#1520)

Co-authored-by: Marko Lahma <[email protected]>
Miguel Fernández Corral 2 years ago
parent
commit
144591843c

+ 33 - 0
Jint.Tests/Runtime/DelegateWrapperTests.cs

@@ -0,0 +1,33 @@
+using Jint.Runtime.Interop;
+
+namespace Jint.Tests.Runtime;
+
+public class DelegateWrapperTests
+{
+    [Fact]
+    public void ShouldSpreadParameters()
+    {
+        var engine = new Engine();
+        engine.SetValue("registerCallback", new DelegateWrapper(engine, new RegisterCallbackDelegate(RegisterCallback)));
+
+        engine.Execute(@"
+                var argsConcat = '';
+                registerCallback((valTest, valOther, valNumber) => {
+                    argsConcat+=typeof valTest;
+                    argsConcat+=' ' + typeof valOther;
+                    argsConcat+=' ' + typeof valNumber;
+                }, 'test', 'other', 1337);
+            ");
+
+        Assert.True(engine.Evaluate("argsConcat == 'string string number'").AsBoolean());
+    }
+
+    private static void RegisterCallback(CallbackAction callback, params object[] arguments)
+    {
+        callback.Invoke(arguments);
+    }
+
+    private delegate void RegisterCallbackDelegate(CallbackAction callback, params object[] arguments);
+
+    private delegate void CallbackAction(params object[] arguments);
+}

+ 18 - 3
Jint/Runtime/Interop/DefaultTypeConverter.cs

@@ -256,18 +256,33 @@ namespace Jint.Runtime.Interop
                 parameters[i] = Expression.Parameter(arguments[i].ParameterType, arguments[i].Name);
             }
 
-            var initializers = new MethodCallExpression[parameters.Length];
+            var initializers = new List<MethodCallExpression>(parameters.Length);
+
             for (var i = 0; i < parameters.Length; i++)
             {
                 var param = parameters[i];
                 if (param.Type.IsValueType)
                 {
                     var boxing = Expression.Convert(param, objectType);
-                    initializers[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), boxing);
+                    initializers.Add(Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), boxing));
+                }
+                else if (param.Type.IsArray &&
+                         arguments[i].GetCustomAttribute<ParamArrayAttribute>() is not null &&
+                         function.Target is FunctionInstance instance)
+                {
+                    for (var j = 0; j < instance.Length; j++)
+                    {
+                        var returnLabel = Expression.Label(typeof(object));
+                        var checkIndex = Expression.GreaterThanOrEqual(Expression.Property(param, nameof(Array.Length)), Expression.Constant(j));
+                        var condition = Expression.IfThen(checkIndex, Expression.Return(returnLabel, Expression.ArrayAccess(param, Expression.Constant(j))));
+                        var block = Expression.Block(condition, Expression.Label(returnLabel, Expression.Constant(JsValue.Undefined)));
+
+                        initializers.Add(Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), block));
+                    }
                 }
                 else
                 {
-                    initializers[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), param);
+                    initializers.Add(Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), param));
                 }
             }
 

+ 2 - 2
Jint/Runtime/Interpreter/JintFunctionDefinition.cs

@@ -75,8 +75,8 @@ internal sealed class JintFunctionDefinition
                 _bodyStatementList ??= new JintStatementList(Function);
                 AsyncFunctionStart(context, promiseCapability, context =>
                 {
-                     context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
-                     return _bodyStatementList.Execute(context);
+                    context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
+                    return _bodyStatementList.Execute(context);
                 });
                 result = new Completion(CompletionType.Return, promiseCapability.PromiseInstance, Function.Body);
             }