diff --git a/Jint.Tests/Runtime/ConstructorSignatureTests.cs b/Jint.Tests/Runtime/ConstructorSignatureTests.cs index e478ade103..25cdbf92ac 100644 --- a/Jint.Tests/Runtime/ConstructorSignatureTests.cs +++ b/Jint.Tests/Runtime/ConstructorSignatureTests.cs @@ -42,6 +42,18 @@ public void CorrectOverloadShouldBeSelected() Assert.Equal("A-30", engine.Evaluate("new B('A', 30).Result")); } + + [Fact] + public void CanConstructWithMixedFloatAndEnumConstructorParameters() + { + var engine = new Engine(); + engine.SetValue("Length", TypeReference.CreateTypeReference(engine)); + Assert.Equal(12.3, engine.Evaluate("new Length(12.3).Value").AsNumber(), precision: 2); + Assert.Equal(12.3, engine.Evaluate("new Length(12.3, 0).Value").AsNumber(), precision: 2); + Assert.Equal(0, engine.Evaluate("new Length(12.3, 0).UnitValue").AsInteger()); + Assert.Equal(LengthUnit.Pixel, (LengthUnit) engine.Evaluate("new Length(12.3, 42).UnitValue").AsInteger()); + } + private class A { public A(int param1, int param2 = 5) => Result = (param1 + param2).ToString(); @@ -62,8 +74,27 @@ public B(string param1, float param2, string param3) public B(string param1, float param2) { Result = string.Join("-", param1, param2.ToString(CultureInfo.InvariantCulture)); - } + } + + public string Result { get; } + } + + private enum LengthUnit { Pixel = 42 } + + private class Length + { + public Length(float value) + : this(value, LengthUnit.Pixel) + { + } + + public Length(float value, LengthUnit unit) + { + Value = value; + UnitValue = unit; + } - public string Result { get;} + public float Value { get; } + public LengthUnit UnitValue { get; } } } diff --git a/Jint/Runtime/Interop/MethodDescriptor.cs b/Jint/Runtime/Interop/MethodDescriptor.cs index 152c9de2e3..831cbe60ad 100644 --- a/Jint/Runtime/Interop/MethodDescriptor.cs +++ b/Jint/Runtime/Interop/MethodDescriptor.cs @@ -111,7 +111,8 @@ public JsValue Call(Engine engine, object? instance, JsValue[] arguments) { for (var i = 0; i < arguments.Length; i++) { - var parameterType = methodParameters[i].ParameterType; + var methodParameter = methodParameters[i]; + var parameterType = methodParameter.ParameterType; var value = arguments[i]; object? converted; @@ -119,6 +120,11 @@ public JsValue Call(Engine engine, object? instance, JsValue[] arguments) { converted = value; } + else if (value.IsUndefined() && methodParameter.IsOptional) + { + // undefined is considered missing, null is consider explicit value + converted = methodParameter.DefaultValue; + } else if (!ReflectionExtensions.TryConvertViaTypeCoercion(parameterType, valueCoercionType, value, out converted)) { converted = engine.ClrTypeConverter.Convert( diff --git a/Jint/Runtime/Interop/TypeReference.cs b/Jint/Runtime/Interop/TypeReference.cs index 31c40789a8..8d7bb98c42 100644 --- a/Jint/Runtime/Interop/TypeReference.cs +++ b/Jint/Runtime/Interop/TypeReference.cs @@ -131,10 +131,10 @@ static ObjectInstance ObjectCreator(Engine engine, Realm realm, ObjectCreateStat } // optional parameters - 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)) + foreach (var parameter in parameters.AsSpan(parameters.Length - arguments.Length)) { if (!parameter.IsOptional) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs index a7ff7a305e..96d470239d 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs @@ -1,6 +1,4 @@ using Esprima.Ast; -using Jint.Native; -using Jint.Native.Object; using Jint.Native.Promise; namespace Jint.Runtime.Interpreter.Expressions; diff --git a/Jint/Runtime/TypeConverter.cs b/Jint/Runtime/TypeConverter.cs index a79e5adae0..ece06bf412 100644 --- a/Jint/Runtime/TypeConverter.cs +++ b/Jint/Runtime/TypeConverter.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Numerics; +using System.Reflection; using System.Runtime.CompilerServices; using Esprima; using Esprima.Ast; @@ -1142,9 +1143,8 @@ private static int CalculateMethodScore(Engine engine, MethodDescriptor method, for (var i = 0; i < arguments.Length; i++) { var jsValue = arguments[i]; - var paramType = method.Parameters[i].ParameterType; - var parameterScore = CalculateMethodParameterScore(engine, jsValue, paramType); + var parameterScore = CalculateMethodParameterScore(engine, method.Parameters[i], jsValue); if (parameterScore < 0) { return parameterScore; @@ -1222,12 +1222,10 @@ internal static AssignableResult IsAssignableToGenericType(Type givenType, Type /// /// Determines how well parameter type matches target method's type. /// - private static int CalculateMethodParameterScore( - Engine engine, - JsValue jsValue, - Type paramType) + private static int CalculateMethodParameterScore(Engine engine, ParameterInfo parameter, JsValue parameterValue) { - var objectValue = jsValue.ToObject(); + var paramType = parameter.ParameterType; + var objectValue = parameterValue.ToObject(); var objectValueType = objectValue?.GetType(); if (objectValueType == paramType) @@ -1237,7 +1235,7 @@ private static int CalculateMethodParameterScore( if (objectValue is null) { - if (!TypeIsNullable(paramType)) + if (!parameter.IsOptional && !TypeIsNullable(paramType)) { // this is bad return -1; @@ -1258,18 +1256,18 @@ private static int CalculateMethodParameterScore( return 5; } - if (paramType == typeof(int) && jsValue.IsInteger()) + if (paramType == typeof(int) && parameterValue.IsInteger()) { return 0; } if (paramType == typeof(float) && objectValueType == typeof(double)) { - return jsValue.IsInteger() ? 1 : 2; + return parameterValue.IsInteger() ? 1 : 2; } if (paramType.IsEnum && - jsValue is JsNumber jsNumber + parameterValue is JsNumber jsNumber && jsNumber.IsInteger() && paramType.GetEnumUnderlyingType() == typeof(int) && Enum.IsDefined(paramType, jsNumber.AsInteger())) @@ -1284,7 +1282,7 @@ jsValue is JsNumber jsNumber return 1; } - if (jsValue.IsArray() && paramType.IsArray) + if (parameterValue.IsArray() && paramType.IsArray) { // we have potential, TODO if we'd know JS array's internal type we could have exact match return 2; @@ -1315,7 +1313,7 @@ jsValue is JsNumber jsNumber } } - if (ReflectionExtensions.TryConvertViaTypeCoercion(paramType, engine.Options.Interop.ValueCoercion, jsValue, out _)) + if (ReflectionExtensions.TryConvertViaTypeCoercion(paramType, engine.Options.Interop.ValueCoercion, parameterValue, out _)) { // gray JS zone where we start to do odd things return 10;