Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions Jint.Tests/Runtime/MethodAmbiguityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,49 @@ public void ShouldMatchCorrectConstructors()
");
}

[Fact]
public void ShouldSelectTypeReferenceOverloadCorrectly()
{
var engine = new Engine(cfg => cfg.AllowClr());
engine.SetValue("Player", TypeReference.CreateTypeReference<Player>(engine));
engine.SetValue("DamageClass", TypeReference.CreateTypeReference<DamageClass>(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<DamageClass>(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);
Expand Down Expand Up @@ -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";
}
19 changes: 19 additions & 0 deletions Jint/Runtime/Interop/InteropHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down