Browse Source

Fix overloaded constructor selection under interop (#1543)

Marko Lahma 2 years ago
parent
commit
800f428947

+ 0 - 45
Jint.Tests/Runtime/ConstructorSignature.cs

@@ -1,45 +0,0 @@
-using Jint.Runtime.Interop;
-
-namespace Jint.Tests.Runtime
-{
-    public class ConstructorSignature
-    {
-        [Fact]
-        public void OptionalConstructorParameters()
-        {
-            var engine = new Engine();
-
-            engine.SetValue("A", TypeReference.CreateTypeReference(engine, typeof(A)));
-
-            // ParamArray tests
-            Assert.Equal("3", engine.Evaluate("new A(1, 2).Result").AsString());
-            Assert.Equal("3", engine.Evaluate("new A(1, 2, null).Result").AsString());
-            Assert.Equal("3", engine.Evaluate("new A(1, 2, undefined).Result").AsString());
-            Assert.Equal("5", engine.Evaluate("new A(1, 2, null, undefined).Result").AsString());
-            Assert.Equal("9", engine.Evaluate("new A(1, 2, ...'packed').Result").AsString());
-            Assert.Equal("3", engine.Evaluate("new A(1, 2, []).Result").AsString());
-            Assert.Equal("7", engine.Evaluate("new A(1, 2, [...'abcd']).Result").AsString());
-
-            // Optional parameter tests
-            Assert.Equal("3", engine.Evaluate("new A(1, 2).Result").AsString());
-            Assert.Equal("6", engine.Evaluate("new A(1).Result").AsString());
-            Assert.Equal("7", engine.Evaluate("new A(2, undefined).Result").AsString());
-            Assert.Equal("8", engine.Evaluate("new A(3, undefined).Result").AsString());
-            Assert.Equal("ab", engine.Evaluate("new A('a').Result").AsString());
-            Assert.Equal("ab", engine.Evaluate("new A('a', undefined).Result").AsString());
-            Assert.Equal("ac", engine.Evaluate("new A('a', 'c').Result").AsString());
-            Assert.Equal("adc", engine.Evaluate("new A('a', 'd', undefined).Result").AsString());
-            Assert.Equal("ade", engine.Evaluate("new A('a', 'd', 'e').Result").AsString());
-        }
-
-        public class A
-        {
-            public A(int param1, int param2 = 5) => Result = (param1 + param2).ToString();
-            public A(string param1, string param2 = "b") => Result = string.Concat(param1, param2);
-            public A(string param1, string param2 = "b", string param3 = "c") => Result = string.Concat(param1, param2, param3);
-            public A(int param1, int param2, params object[] param3) => Result = (param1 + param2 + param3?.Length).ToString();
-
-            public string Result { get; }
-        }
-    }
-}

+ 69 - 0
Jint.Tests/Runtime/ConstructorSignatureTests.cs

@@ -0,0 +1,69 @@
+using System.Globalization;
+using Jint.Runtime.Interop;
+
+namespace Jint.Tests.Runtime;
+
+public class ConstructorSignature
+{
+    [Fact]
+    public void OptionalConstructorParametersShouldBeSupported()
+    {
+        var engine = new Engine();
+
+        engine.SetValue("A", TypeReference.CreateTypeReference(engine, typeof(A)));
+
+        // ParamArray tests
+        Assert.Equal("3", engine.Evaluate("new A(1, 2).Result").AsString());
+        Assert.Equal("3", engine.Evaluate("new A(1, 2, null).Result").AsString());
+        Assert.Equal("3", engine.Evaluate("new A(1, 2, undefined).Result").AsString());
+        Assert.Equal("5", engine.Evaluate("new A(1, 2, null, undefined).Result").AsString());
+        Assert.Equal("9", engine.Evaluate("new A(1, 2, ...'packed').Result").AsString());
+        Assert.Equal("3", engine.Evaluate("new A(1, 2, []).Result").AsString());
+        Assert.Equal("7", engine.Evaluate("new A(1, 2, [...'abcd']).Result").AsString());
+
+        // Optional parameter tests
+        Assert.Equal("3", engine.Evaluate("new A(1, 2).Result").AsString());
+        Assert.Equal("6", engine.Evaluate("new A(1).Result").AsString());
+        Assert.Equal("7", engine.Evaluate("new A(2, undefined).Result").AsString());
+        Assert.Equal("8", engine.Evaluate("new A(3, undefined).Result").AsString());
+        Assert.Equal("ab", engine.Evaluate("new A('a').Result").AsString());
+        Assert.Equal("ab", engine.Evaluate("new A('a', undefined).Result").AsString());
+        Assert.Equal("ac", engine.Evaluate("new A('a', 'c').Result").AsString());
+        Assert.Equal("adc", engine.Evaluate("new A('a', 'd', undefined).Result").AsString());
+        Assert.Equal("ade", engine.Evaluate("new A('a', 'd', 'e').Result").AsString());
+    }
+
+    [Fact]
+    public void CorrectOverloadShouldBeSelected()
+    {
+        var engine = new Engine();
+        engine.SetValue("B", typeof(B));
+
+        Assert.Equal("A-30", engine.Evaluate("new B('A', 30).Result"));
+    }
+
+    private class A
+    {
+        public A(int param1, int param2 = 5) => Result = (param1 + param2).ToString();
+        public A(string param1, string param2 = "b") => Result = string.Concat(param1, param2);
+        public A(string param1, string param2 = "b", string param3 = "c") => Result = string.Concat(param1, param2, param3);
+        public A(int param1, int param2, params object[] param3) => Result = (param1 + param2 + param3?.Length).ToString();
+
+        public string Result { get; }
+    }
+
+    private class B
+    {
+        public B(string param1, float param2, string param3)
+        {
+            Result = string.Join("-", param1, param2.ToString(CultureInfo.InvariantCulture), param3);
+        }
+
+        public B(string param1, float param2)
+        {
+            Result = string.Join("-", param1, param2.ToString(CultureInfo.InvariantCulture));
+       }
+
+        public string Result { get;}
+    }
+}

+ 15 - 3
Jint/Runtime/Interop/TypeReference.cs

@@ -123,14 +123,26 @@ namespace Jint.Runtime.Interop
 
                             return newArguments;
                         }
+
                         // TODO: edge case, last parameter is ParamArray with optional parameter before?
-                        else if (isParamArray && arguments.Length < parameters.Length - 1)
+                        if (isParamArray && arguments.Length < parameters.Length - 1)
                         {
                             return arguments;
                         }
+
                         // optional parameters
-                        else if (parameters.Length >= arguments.Length)
+                        if (parameters.Length >= arguments.Length)
                         {
+                            // all missing ones must be optional
+                            foreach (var parameter in parameters.AsSpan(parameters.Length - arguments.Length + 1))
+                            {
+                                if (!parameter.IsOptional)
+                                {
+                                    // use original arguments
+                                    return arguments;
+                                }
+                            }
+
                             Array.Copy(arguments, 0, newArguments, 0, arguments.Length);
 
                             for (var i = parameters.Length - 1; i >= 0; i--)
@@ -146,7 +158,7 @@ namespace Jint.Runtime.Interop
 
                                     if (arguments.Length - 1 < i || arguments[i].IsUndefined())
                                     {
-                                        newArguments[i] = JsValue.FromObject(engine, currentParameter.DefaultValue);
+                                        newArguments[i] = FromObject(engine, currentParameter.DefaultValue);
                                     }
                                 }
                             }