Browse Source

Support CLR object inheritance chain with prototypes (#1518)

Co-authored-by: Marko Lahma <[email protected]>
Miguel Fernández Corral 2 years ago
parent
commit
9984761e7d

+ 28 - 4
Jint.Tests/Runtime/InstanceOfTests.cs

@@ -7,16 +7,28 @@ 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("AToo", TypeReference.CreateTypeReference(engine, typeof(A)));
         engine.SetValue("B", TypeReference.CreateTypeReference(engine, typeof(B)));
         engine.SetValue("C", TypeReference.CreateTypeReference(engine, typeof(C)));
 
+        Assert.True(engine.Evaluate("A == A").AsBoolean());
+        Assert.True(engine.Evaluate("A === A").AsBoolean());
+        Assert.True(engine.Evaluate("A == AToo").AsBoolean());
+        Assert.True(engine.Evaluate("A === AToo").AsBoolean());
+
+        Assert.True(engine.Evaluate("A.prototype instanceof A").AsBoolean());
+        Assert.True(engine.Evaluate("B.prototype instanceof A").AsBoolean());
+        Assert.False(engine.Evaluate("A.prototype instanceof B").AsBoolean());
+        Assert.True(engine.Evaluate("C.prototype instanceof A").AsBoolean());
+        Assert.True(engine.Evaluate("C.prototype instanceof B").AsBoolean());
+
+        var a = new A();
+        var b = new B();
+        var c = new C();
+
         engine.SetValue("a", a);
         engine.SetValue("b", b);
         engine.SetValue("c", c);
@@ -32,6 +44,18 @@ public class InstanceOfTests
         Assert.True(engine.Evaluate("c instanceof A").AsBoolean());
         Assert.True(engine.Evaluate("c instanceof B").AsBoolean());
         Assert.True(engine.Evaluate("c instanceof C").AsBoolean());
+
+        Assert.True(engine.Evaluate("new A() instanceof A").AsBoolean());
+        Assert.False(engine.Evaluate("new A() instanceof B").AsBoolean());
+        Assert.False(engine.Evaluate("new A() instanceof C").AsBoolean());
+
+        Assert.True(engine.Evaluate("new B() instanceof A").AsBoolean());
+        Assert.True(engine.Evaluate("new B() instanceof B").AsBoolean());
+        Assert.False(engine.Evaluate("new B() instanceof C").AsBoolean());
+
+        Assert.True(engine.Evaluate("new C() instanceof A").AsBoolean());
+        Assert.True(engine.Evaluate("new C() instanceof B").AsBoolean());
+        Assert.True(engine.Evaluate("new C() instanceof C").AsBoolean());
     }
 
     public class A { }

+ 22 - 7
Jint/Runtime/Interop/TypeReference.cs

@@ -25,7 +25,7 @@ namespace Jint.Runtime.Interop
             _prototype = engine.Realm.Intrinsics.Function.PrototypeObject;
             _length = PropertyDescriptor.AllForbiddenDescriptor.NumberZero;
 
-            var proto = new JsObject(engine);
+            var proto = new TypeReferencePrototype(engine, this);
             _prototypeDescriptor = new PropertyDescriptor(proto, PropertyFlag.AllForbidden);
 
             PreventExtensions();
@@ -184,12 +184,21 @@ namespace Jint.Runtime.Interop
                 ObjectCreator,
                 new ObjectCreateState(this, arguments));
 
-
             return thisArgument;
         }
 
         private readonly record struct ObjectCreateState(TypeReference TypeReference, JsValue[] Arguments);
 
+        public override bool Equals(JsValue? obj)
+        {
+            if (obj is TypeReference typeReference)
+            {
+                return this.ReferenceType == typeReference.ReferenceType;
+            }
+
+            return base.Equals(obj);
+        }
+
         internal override bool OrdinaryHasInstance(JsValue v)
         {
             if (v is IObjectWrapper wrapper)
@@ -295,8 +304,8 @@ namespace Jint.Runtime.Interop
                 return ConstantValueAccessor.NullAccessor;
             }
 
-            const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
-            return typeResolver.TryFindMemberAccessor(engine, type, name, bindingFlags, indexerToTry: null, out var accessor)
+            const BindingFlags BindingFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
+            return typeResolver.TryFindMemberAccessor(engine, type, name, BindingFlags, indexerToTry: null, out var accessor)
                 ? accessor
                 : ConstantValueAccessor.NullAccessor;
         }
@@ -306,16 +315,22 @@ namespace Jint.Runtime.Interop
         private static JsValue HasInstance(JsValue thisObject, JsValue[] arguments)
         {
             var typeReference = thisObject as TypeReference;
-            var objectWrapper = arguments.At(0) as ObjectWrapper;
+            var other = arguments.At(0);
 
-            if (typeReference is null || objectWrapper is null)
+            if (typeReference is null)
             {
                 return JsBoolean.False;
             }
 
-            var derivedType = objectWrapper.Target?.GetType();
             var baseType = typeReference.ReferenceType;
 
+            var derivedType = other switch
+            {
+                ObjectWrapper wrapper => wrapper.Target.GetType(),
+                TypeReferencePrototype otherTypeReference => otherTypeReference.TypeReference.ReferenceType,
+                _ => null
+            };
+
             return derivedType != null && baseType != null && (derivedType == baseType || derivedType.IsSubclassOf(baseType));
         }
     }

+ 11 - 37
Jint/Runtime/Interop/TypeReferencePrototype.cs

@@ -1,40 +1,14 @@
-//using Jint.Native;
-//using Jint.Native.Object;
+using Jint.Native.Object;
 
-//namespace Jint.Runtime.Interop
-//{
-//    public sealed class TypeReferencePrototype : ObjectInstance
-//    {
-//        private TypeReferencePrototype(Engine engine)
-//            : base(engine)
-//        {
-//        }
+namespace Jint.Runtime.Interop;
 
-//        public static TypeReferencePrototype CreatePrototypeObject(Engine engine, TypeReference typeReferenceConstructor)
-//        {
-//            var obj = new TypeReferencePrototype(engine);
-//            obj.Prototype = engine.Object.PrototypeObject;
-//            obj.Extensible = false;
+internal sealed class TypeReferencePrototype : ObjectInstance
+{
+    public TypeReferencePrototype(Engine engine, TypeReference typeReference) : base(engine)
+    {
+        TypeReference = typeReference;
+        _prototype = engine.Realm.Intrinsics.Object.PrototypeObject;
+    }
 
-//            obj.FastAddProperty("constructor", typeReferenceConstructor, true, false, true);
-
-//            return obj;
-//        }
-
-//        public void Configure()
-//        {
-//            FastAddProperty("toString", new ClrFunctionInstance(Engine, ToTypeReferenceString), true, false, true);
-//        }
-
-//        private JsValue ToTypeReferenceString(JsValue thisObj, JsValue[] arguments)
-//        {
-//            var typeReference = thisObj.As<TypeReference>();
-//            if (typeReference == null)
-//            {
-//                ExceptionHelper.ThrowTypeError(Engine);
-//            }
-
-//            return typeReference.Type.FullName;
-//        }
-//    }
-//}
+    public TypeReference TypeReference { get; }
+}