Skip to content

Commit

Permalink
Support explicit interface and hidden member of super class (#1613)
Browse files Browse the repository at this point in the history
  • Loading branch information
viruscamp authored Aug 6, 2023
1 parent 2ebfedf commit 0ebf7cd
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 22 deletions.
186 changes: 186 additions & 0 deletions Jint.Tests/Runtime/InteropExplicitTypeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
using System.Reflection;

namespace Jint.Tests.Runtime
{
public class InteropExplicitTypeTests
{
public interface I1
{
string Name { get; }
}

public class Super
{
public string Name { get; } = "Super";
}

public class CI1 : Super, I1
{
public new string Name { get; } = "CI1";

string I1.Name { get; } = "CI1 as I1";
}

public class Indexer<T>
{
private readonly T t;
public Indexer(T t)
{
this.t = t;
}
public T this[int index]
{
get { return t; }
}
}

public class InterfaceHolder
{
public InterfaceHolder()
{
var ci1 = new CI1();
this.ci1 = ci1;
this.i1 = ci1;
this.super = ci1;

this.IndexerCI1 = new Indexer<CI1>(ci1);
this.IndexerI1 = new Indexer<I1>(ci1);
this.IndexerSuper = new Indexer<Super>(ci1);
}

public readonly CI1 ci1;
public readonly I1 i1;
public readonly Super super;

public CI1 CI1 { get => ci1; }
public I1 I1 { get => i1; }
public Super Super { get => super; }

public CI1 GetCI1() => ci1;
public I1 GetI1() => i1;
public Super GetSuper() => super;

public Indexer<CI1> IndexerCI1 { get; }
public Indexer<I1> IndexerI1 { get; }
public Indexer<Super> IndexerSuper { get; }

}

private readonly Engine _engine;
private readonly InterfaceHolder holder;

public InteropExplicitTypeTests()
{
holder = new InterfaceHolder();
_engine = new Engine(cfg => cfg.AllowClr(
typeof(Console).GetTypeInfo().Assembly,
typeof(File).GetTypeInfo().Assembly))
.SetValue("log", new Action<object>(Console.WriteLine))
.SetValue("assert", new Action<bool>(Assert.True))
.SetValue("equal", new Action<object, object>(Assert.Equal))
.SetValue("holder", holder)
;
}
[Fact]
public void EqualTest()
{
Assert.Equal(_engine.Evaluate("holder.I1"), _engine.Evaluate("holder.i1"));
Assert.NotEqual(_engine.Evaluate("holder.I1"), _engine.Evaluate("holder.ci1"));

Assert.Equal(_engine.Evaluate("holder.Super"), _engine.Evaluate("holder.super"));
Assert.NotEqual(_engine.Evaluate("holder.Super"), _engine.Evaluate("holder.ci1"));
}

[Fact]
public void ExplicitInterfaceFromField()
{
Assert.Equal(holder.i1.Name, _engine.Evaluate("holder.i1.Name"));
Assert.NotEqual(holder.i1.Name, _engine.Evaluate("holder.ci1.Name"));
}

[Fact]
public void ExplicitInterfaceFromProperty()
{
Assert.Equal(holder.I1.Name, _engine.Evaluate("holder.I1.Name"));
Assert.NotEqual(holder.I1.Name, _engine.Evaluate("holder.CI1.Name"));
}

[Fact]
public void ExplicitInterfaceFromMethod()
{
Assert.Equal(holder.GetI1().Name, _engine.Evaluate("holder.GetI1().Name"));
Assert.NotEqual(holder.GetI1().Name, _engine.Evaluate("holder.GetCI1().Name"));
}

[Fact]
public void ExplicitInterfaceFromIndexer()
{
Assert.Equal(holder.IndexerI1[0].Name, _engine.Evaluate("holder.IndexerI1[0].Name"));
}


[Fact]
public void SuperClassFromField()
{
Assert.Equal(holder.super.Name, _engine.Evaluate("holder.super.Name"));
Assert.NotEqual(holder.super.Name, _engine.Evaluate("holder.ci1.Name"));
}

[Fact]
public void SuperClassFromProperty()
{
Assert.Equal(holder.Super.Name, _engine.Evaluate("holder.Super.Name"));
Assert.NotEqual(holder.Super.Name, _engine.Evaluate("holder.CI1.Name"));
}

[Fact]
public void SuperClassFromMethod()
{
Assert.Equal(holder.GetSuper().Name, _engine.Evaluate("holder.GetSuper().Name"));
Assert.NotEqual(holder.GetSuper().Name, _engine.Evaluate("holder.GetCI1().Name"));
}

[Fact]
public void SuperClassFromIndexer()
{
Assert.Equal(holder.IndexerSuper[0].Name, _engine.Evaluate("holder.IndexerSuper[0].Name"));
}

public struct NullabeStruct: I1
{
public NullabeStruct()
{
}
public string name = "NullabeStruct";

public string Name { get => name; }

string I1.Name { get => "NullabeStruct as I1"; }
}

public class NullableHolder
{
public I1? I1 { get; set; }
public NullabeStruct? NullabeStruct { get; set; }
}

[Fact]
public void TestNullable()
{
var nullableHolder = new NullableHolder();
_engine.SetValue("nullableHolder", nullableHolder);
_engine.SetValue("nullabeStruct", new NullabeStruct());

Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct"), Native.JsValue.Null);
_engine.Evaluate("nullableHolder.NullabeStruct = nullabeStruct");
Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct.Name"), nullableHolder.NullabeStruct?.Name);
}

[Fact]
public void TestUnwrapClr()
{
Assert.NotEqual(holder.CI1.Name, _engine.Evaluate("holder.I1.Name"));
Assert.Equal(holder.CI1.Name, _engine.Evaluate("unwrapClr(holder.I1).Name"));
}
}
}
4 changes: 2 additions & 2 deletions Jint.Tests/Runtime/InteropTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ public void CanAddArrayPrototypeForArrayLikeClrObjects()
{
var e = new Engine(cfg => cfg
.AllowClr(typeof(Person).Assembly)
.SetWrapObjectHandler((engine, target) =>
.SetWrapObjectHandler((engine, target, type) =>
{
var instance = new ObjectWrapper(engine, target);
if (instance.IsArrayLike)
Expand Down Expand Up @@ -884,7 +884,7 @@ public void CanSetIsConcatSpreadableForArrays()
{
var engine = new Engine(opt =>
{
opt.SetWrapObjectHandler((eng, obj) =>
opt.SetWrapObjectHandler((eng, obj, type) =>
{
var wrapper = new ObjectWrapper(eng, obj);
if (wrapper.IsArrayLike)
Expand Down
10 changes: 9 additions & 1 deletion Jint/Native/JsValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ public Types Type
/// Creates a valid <see cref="JsValue"/> instance from any <see cref="Object"/> instance
/// </summary>
public static JsValue FromObject(Engine engine, object? value)
{
return FromObjectWithType(engine, value, null);
}

/// <summary>
/// Creates a valid <see cref="JsValue"/> instance from any <see cref="Object"/> instance, with a type
/// </summary>
public static JsValue FromObjectWithType(Engine engine, object? value, Type? type)
{
if (value is null)
{
Expand All @@ -132,7 +140,7 @@ public static JsValue FromObject(Engine engine, object? value)
}
}

if (DefaultObjectConverter.TryConvert(engine, value, out var defaultConversion))
if (DefaultObjectConverter.TryConvert(engine, value, type, out var defaultConversion))
{
return defaultConversion;
}
Expand Down
20 changes: 18 additions & 2 deletions Jint/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Jint
{
public delegate JsValue? MemberAccessorDelegate(Engine engine, object target, string member);

public delegate ObjectInstance? WrapObjectDelegate(Engine engine, object target);
public delegate ObjectInstance? WrapObjectDelegate(Engine engine, object target, Type? type);

public delegate bool ExceptionHandlerDelegate(Exception exception);

Expand Down Expand Up @@ -120,6 +120,22 @@ internal void Apply(Engine engine)
(thisObj, arguments) =>
new NamespaceReference(engine, TypeConverter.ToString(arguments.At(0)))),
PropertyFlag.AllForbidden));
engine.Realm.GlobalObject.SetProperty("unwrapClr", new PropertyDescriptor(new ClrFunctionInstance(
engine,
"unwrapClr",
(thisObj, arguments) =>
{
var arg = arguments.At(0);
if (arg is ObjectWrapper obj)
{
return new ObjectWrapper(engine, obj.Target);
}
else
{
return arg;
}
}),
PropertyFlag.AllForbidden));
}

if (Interop.ExtensionMethodTypes.Count > 0)
Expand Down Expand Up @@ -282,7 +298,7 @@ public class InteropOptions
/// ObjectInstance using class ObjectWrapper. This function can be used to
/// change the behavior.
/// </summary>
public WrapObjectDelegate WrapObjectHandler { get; set; } = static (engine, target) => new ObjectWrapper(engine, target);
public WrapObjectDelegate WrapObjectHandler { get; set; } = static (engine, target, type) => new ObjectWrapper(engine, target, type);

/// <summary>
///
Expand Down
3 changes: 2 additions & 1 deletion Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ protected internal override JsValue? CustomValue
get
{
var value = _reflectionAccessor.GetValue(_engine, _target);
return JsValue.FromObject(_engine, value);
var type = _reflectionAccessor.MemberType;
return JsValue.FromObjectWithType(_engine, value, type);
}
set
{
Expand Down
6 changes: 3 additions & 3 deletions Jint/Runtime/Interop/DefaultObjectConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ internal static class DefaultObjectConverter
}
};

public static bool TryConvert(Engine engine, object value, [NotNullWhen(true)] out JsValue? result)
public static bool TryConvert(Engine engine, object value, Type? type, [NotNullWhen(true)] out JsValue? result)
{
result = null;
var valueType = value.GetType();
Type valueType = ObjectWrapper.ClrType(value, type);

var typeMappers = _typeMappers;

Expand Down Expand Up @@ -109,7 +109,7 @@ public static bool TryConvert(Engine engine, object value, [NotNullWhen(true)] o
}
else
{
var wrapped = engine.Options.Interop.WrapObjectHandler.Invoke(engine, value);
var wrapped = engine.Options.Interop.WrapObjectHandler.Invoke(engine, value, type);
result = wrapped;

if (engine.Options.Interop.TrackObjectWrapperIdentity && wrapped is not null)
Expand Down
10 changes: 8 additions & 2 deletions Jint/Runtime/Interop/MethodInfoFunctionInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,17 +220,23 @@ JsValue[] ArgumentProvider(MethodDescriptor method)
continue;
}

Type? returnType = null;
if (method.Method is MethodInfo methodInfo)
{
returnType = methodInfo.ReturnType;
}

// todo: cache method info
try
{
if (method.Method.IsGenericMethodDefinition && method.Method is MethodInfo)
{
var genericMethodInfo = resolvedMethod;
var result = genericMethodInfo.Invoke(thisObj, parameters);
return FromObject(Engine, result);
return FromObjectWithType(Engine, result, returnType);
}

return FromObject(Engine, method.Method.Invoke(thisObj, parameters));
return FromObjectWithType(Engine, method.Method.Invoke(thisObj, parameters), returnType);
}
catch (TargetInvocationException exception)
{
Expand Down
Loading

0 comments on commit 0ebf7cd

Please sign in to comment.