Browse Source

Support inheritance chain of CLR objects with instanceof operator (#1511)

Co-authored-by: Marko Lahma <[email protected]>
mainlyer 2 years ago
parent
commit
ef8976839c

+ 42 - 0
Jint.Tests/Runtime/InstanceOfTests.cs

@@ -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 { }
+}

+ 5 - 5
Jint/Native/JsValue.cs

@@ -200,21 +200,21 @@ namespace Jint.Native
         /// </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);

+ 32 - 2
Jint/Runtime/Interop/TypeReference.cs

@@ -3,6 +3,7 @@ using System.Reflection;
 using Jint.Collections;
 using Jint.Native;
 using Jint.Native.Object;
+using Jint.Native.Symbol;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop.Reflection;
 
@@ -141,13 +142,26 @@ namespace Jint.Runtime.Interop
         {
             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))
@@ -204,5 +218,21 @@ namespace Jint.Runtime.Interop
         }
 
         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));
+        }
     }
 }