Skip to content

Commit

Permalink
Support inheritance chain of CLR objects with instanceof operator (#1511
Browse files Browse the repository at this point in the history
)

Co-authored-by: Marko Lahma <[email protected]>
  • Loading branch information
mainlyer and lahma authored Mar 26, 2023
1 parent 8960e80 commit ef89768
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 7 deletions.
42 changes: 42 additions & 0 deletions Jint.Tests/Runtime/InstanceOfTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Jint.Runtime.Interop;

namespace Jint.Tests.Runtime;

public class InstanceOfTests
{
[Fact]
public void ShouldSupportInheritanceChainUnderInterop()
{
var a = new A();
var b = new B();
var c = new C();

var engine = new Engine();

engine.SetValue("A", TypeReference.CreateTypeReference(engine, typeof(A)));
engine.SetValue("B", TypeReference.CreateTypeReference(engine, typeof(B)));
engine.SetValue("C", TypeReference.CreateTypeReference(engine, typeof(C)));

engine.SetValue("a", a);
engine.SetValue("b", b);
engine.SetValue("c", c);

Assert.True(engine.Evaluate("a instanceof A").AsBoolean());
Assert.False(engine.Evaluate("a instanceof B").AsBoolean());
Assert.False(engine.Evaluate("a instanceof C").AsBoolean());

Assert.True(engine.Evaluate("b instanceof A").AsBoolean());
Assert.True(engine.Evaluate("b instanceof B").AsBoolean());
Assert.False(engine.Evaluate("b instanceof C").AsBoolean());

Assert.True(engine.Evaluate("c instanceof A").AsBoolean());
Assert.True(engine.Evaluate("c instanceof B").AsBoolean());
Assert.True(engine.Evaluate("c instanceof C").AsBoolean());
}

public class A { }

public class B : A { }

public class C : B { }
}
10 changes: 5 additions & 5 deletions Jint/Native/JsValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,21 +200,21 @@ public virtual bool Set(JsValue property, JsValue value, JsValue receiver)
/// </summary>
internal bool InstanceofOperator(JsValue target)
{
var oi = target as ObjectInstance;
if (oi is null)
if (target is not ObjectInstance oi)
{
ExceptionHelper.ThrowTypeErrorNoEngine("not an object");
ExceptionHelper.ThrowTypeErrorNoEngine("Right-hand side of 'instanceof' is not an object");
return false;
}

var instOfHandler = oi.GetMethod(GlobalSymbolRegistry.HasInstance);
if (instOfHandler is not null)
{
return TypeConverter.ToBoolean(instOfHandler.Call(target, new[] {this}));
return TypeConverter.ToBoolean(instOfHandler.Call(target, new[] { this }));
}

if (!target.IsCallable)
{
ExceptionHelper.ThrowTypeErrorNoEngine("not callable");
ExceptionHelper.ThrowTypeErrorNoEngine("Right-hand side of 'instanceof' is not callable");
}

return target.OrdinaryHasInstance(this);
Expand Down
34 changes: 32 additions & 2 deletions Jint/Runtime/Interop/TypeReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Jint.Collections;
using Jint.Native;
using Jint.Native.Object;
using Jint.Native.Symbol;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop.Reflection;

Expand Down Expand Up @@ -141,13 +142,26 @@ public override PropertyDescriptor GetOwnProperty(JsValue property)
{
if (property is not JsString jsString)
{
if (property == GlobalSymbolRegistry.HasInstance)
{
var hasInstanceFunction = new ClrFunctionInstance(
Engine,
"[Symbol.hasInstance]",
HasInstance,
1,
PropertyFlag.Configurable);

var hasInstanceProperty = new PropertyDescriptor(hasInstanceFunction, PropertyFlag.AllForbidden);
SetProperty(GlobalSymbolRegistry.HasInstance, hasInstanceProperty);
return hasInstanceProperty;
}

return PropertyDescriptor.Undefined;
}

var key = jsString._value;
var descriptor = PropertyDescriptor.Undefined;

if (_properties?.TryGetValue(key, out descriptor) != true)
if (_properties?.TryGetValue(key, out var descriptor) != true)
{
descriptor = CreatePropertyDescriptor(key);
if (!ReferenceEquals(descriptor, PropertyDescriptor.Undefined))
Expand Down Expand Up @@ -204,5 +218,21 @@ private static ReflectionAccessor ResolveMemberAccessor(Engine engine, Type type
}

public object Target => ReferenceType;

private static JsValue HasInstance(JsValue thisObject, JsValue[] arguments)
{
var typeReference = thisObject as TypeReference;
var objectWrapper = arguments.At(0) as ObjectWrapper;

if (typeReference is null || objectWrapper is null)
{
return JsBoolean.False;
}

var derivedType = objectWrapper.Target?.GetType();
var baseType = typeReference.ReferenceType;

return derivedType != null && baseType != null && (derivedType == baseType || derivedType.IsSubclassOf(baseType));
}
}
}

0 comments on commit ef89768

Please sign in to comment.