From ffd8a0f84987050186ee72aa98b8c4335d2a4905 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 7 Jan 2026 13:44:14 +0200 Subject: [PATCH] Improve TypeReference method parameter scoring --- Jint.Tests/Runtime/MethodAmbiguityTests.cs | 66 ++++++++++++++++++++++ Jint/Runtime/Interop/InteropHelper.cs | 19 +++++++ 2 files changed, 85 insertions(+) diff --git a/Jint.Tests/Runtime/MethodAmbiguityTests.cs b/Jint.Tests/Runtime/MethodAmbiguityTests.cs index 2f1fd484d6..1020d7b919 100644 --- a/Jint.Tests/Runtime/MethodAmbiguityTests.cs +++ b/Jint.Tests/Runtime/MethodAmbiguityTests.cs @@ -116,6 +116,49 @@ public void ShouldMatchCorrectConstructors() "); } + [Fact] + public void ShouldSelectTypeReferenceOverloadCorrectly() + { + var engine = new Engine(cfg => cfg.AllowClr()); + engine.SetValue("Player", TypeReference.CreateTypeReference(engine)); + engine.SetValue("DamageClass", TypeReference.CreateTypeReference(engine)); + + var damageClassInstance = new DamageClass(); + engine.SetValue("damageClassInstance", damageClassInstance); + + // When passing a TypeReference, should select the TypeReference overload + var typeRefResult = engine.Evaluate("Player.GetArmorPenetration(DamageClass);"); + Assert.Equal("TypeReference", typeRefResult.AsString()); + + // When passing a domain object instance, should select the domain class overload + var instanceResult = engine.Evaluate("Player.GetArmorPenetration(damageClassInstance);"); + Assert.Equal("DamageClass", instanceResult.AsString()); + } + + [Fact] + public void ShouldSelectCorrectExtensionMethodOverloadForTypeReference() + { + var engine = new Engine(cfg => cfg + .AllowClr() + .AddExtensionMethods(typeof(PlayerExtensions))); + + engine.SetValue("DamageClass", TypeReference.CreateTypeReference(engine)); + + var player = new Player(); + engine.SetValue("player", player); + + var damageClassInstance = new DamageClass(); + engine.SetValue("damageClassInstance", damageClassInstance); + + // When calling extension method with TypeReference, should select TypeReference overload + var typeRefResult = engine.Evaluate("player.GetPenetration(DamageClass);"); + Assert.Equal("TypeReference:10", typeRefResult.AsString()); + + // When calling extension method with instance, should select DamageClass overload + var instanceResult = engine.Evaluate("player.GetPenetration(damageClassInstance);"); + Assert.Equal("DamageClass:20", instanceResult.AsString()); + } + private struct Class1 { public static string Print(ExpandoObject eo) => nameof(Class1) + "." + nameof(ExpandoObject); @@ -143,4 +186,27 @@ public Class3() { } public void Print(object a) => Console.WriteLine("Print(object a): " + a); } +} + +public class Player +{ + // Static methods for testing overload resolution + public static string GetArmorPenetration(TypeReference damageClass) => "TypeReference"; + public static string GetArmorPenetration(Type damageClass) => "Type"; + public static string GetArmorPenetration(DamageClass damageClass) => "DamageClass"; + + // Variant that only takes Type (for testing explicit Type parameter) + public static string GetArmorPenetrationForType(Type damageClass) => "Type"; +} + +public class DamageClass +{ + public string Name { get; set; } = "Default"; +} + +public static class PlayerExtensions +{ + public static string GetPenetration(this Player player, TypeReference damageClass) => "TypeReference:10"; + public static string GetPenetration(this Player player, Type damageClass) => "Type:15"; + public static string GetPenetration(this Player player, DamageClass damageClass) => "DamageClass:20"; } \ No newline at end of file diff --git a/Jint/Runtime/Interop/InteropHelper.cs b/Jint/Runtime/Interop/InteropHelper.cs index b5eb60d8c5..cada0caec4 100644 --- a/Jint/Runtime/Interop/InteropHelper.cs +++ b/Jint/Runtime/Interop/InteropHelper.cs @@ -89,6 +89,25 @@ internal static AssignableResult IsAssignableToGenericType( private static int CalculateMethodParameterScore(Engine engine, ParameterInfo parameter, JsValue parameterValue) { var paramType = parameter.ParameterType; + + // Special case: if parameter expects a JsValue-derived type (e.g., TypeReference), + // check if the argument is of that exact type before calling ToObject(). + // This is important because ToObject() unwraps TypeReference to System.Type, + // losing the original wrapper type information needed for overload resolution. + if (typeof(JsValue).IsAssignableFrom(paramType)) + { + var jsValueType = parameterValue.GetType(); + if (jsValueType == paramType) + { + return 0; // Exact match + } + + if (paramType.IsAssignableFrom(jsValueType)) + { + return 1; // Is-a relationship + } + } + var objectValue = parameterValue.ToObject(); var objectValueType = objectValue?.GetType();