graaljs icon indicating copy to clipboard operation
graaljs copied to clipboard

Support for instanceof of ProxyInstantiable

Open provegard opened this issue 6 years ago • 6 comments

I'm exposing a "type object" that implements ProxyInstantiable. I wan't to be able to check if the instance returned from newInstance is actually an instance of the type object.

Here's my code:

public class MainType {
    public static void main(String[] args) {
        Context context = Context.create("js");

        context.getBindings("js").putMember("MyType", new TypeLike());

        String src = "var instance = new MyType();\n" +
                "print(instance instanceof MyType);";
        context.eval("js", src);
    }

    public static class TypeLike implements ProxyInstantiable {
        @Override
        public Object newInstance(Value... arguments) {
            return new InstanceLike();
        }
    }

    public static class InstanceLike implements ProxyObject {

        @Override
        public Object getMember(String key) {
            return null;
        }

        @Override
        public Object getMemberKeys() {
            return new String[0];
        }

        @Override
        public boolean hasMember(String key) {
            return false;
        }

        @Override
        public void putMember(String key, Value value) {

        }
    }
}

It prints:

Exception in thread "main" TypeError: Right-hand-side of instanceof is not an object
	at <js> :program(Unnamed:2:35-60)
	at org.graalvm.polyglot.Context.eval(Context.java:336)
	at com.programmaticallyspeaking.gho.MainType.main(MainType.java:16)

I also tried to implement ProxyObject on the host type class, but I get the same result.

provegard avatar Aug 15 '18 20:08 provegard

That is an interesting feature indeed. And we already discussed this internally. It requires to extend our interop protocol. So will take a bit to land.

chumer avatar Aug 16 '18 11:08 chumer

Sounds good!

Since it works in Nashorn, I'd also say that it's important from a migration perspective.

provegard avatar Aug 16 '18 12:08 provegard

Please note that this should already work for Java host classes. The limitation only applies to ProxyInstantiable.

chumer avatar Aug 16 '18 12:08 chumer

It appears that foo instanceof SomeProxyInstantiable doesn't work even when SomeProxyInstantiable also implements ProxyObject with a prototype member that is in the __proto__ chain of foo.

I realize that __proto__ is non-standard. Unfortunately, Object.setPrototypeOf(someProxyObject, someProxyObjectPrototype) doesn't work either. No exception is thrown, but Object.getPrototypeOf(someProxyObject) always returns null when called on a ProxyObject.

I've also tried to use the com.oracle.truffle.js APIs directly—which I would prefer anyway over the lowest common denominator polyglot APIs—but I keep running into private access roadblocks.

Is there any way at all to get instanceof to work for bridged objects? I'm open to any approach that I can do procedurally from Java. We're trying to procedurally map a large Java API to JavaScript, but with a number of transformations and context-sensitive security restrictions that make host access a no-go. Is there a proper way to get my hands on a JSContext (JavaScriptLanguage.getContext(Context) seems to throw under all circumstances) so that I can create Truffle DynamicObjects that are compatible with JSObject? I'd appreciate any pointers for where to look to do this. Thanks!

c9r avatar Jul 10 '19 00:07 c9r

I don't know if this would be as satisfactory solution for your use case, but you can test instanceof against a JS object that has a Symbol.hasInstance method that you can use to implement your own check, like so:

function MyType() {
    return new TypeLike(); // constructs the ProxyObject
}
Object.defineProperty(MyType, Symbol.hasInstance, {
  value: function myinstanceof(obj) {
    return isInstanceLike(obj);
  }
});
...
var instance = new MyType();
print(instance instanceof MyType);

Now the problem is how to actually check if the object is an instance of your ProxyObject. You can't do that from JS, but you could use another ProxyExecutable for that:

public static class IsInstanceLike implements ProxyExecutable {
    @Override
    public Object execute(Value... arguments) {
        Value obj = arguments[0];
        return obj.isProxyObject() && obj.asProxyObject() instanceof InstanceLike;
    }
}
context.getBindings("js").putMember("isInstanceLike", new IsInstanceLike());

woess avatar Jul 19 '19 14:07 woess

@woess thank you, that worked well! An addition to ProxyInstantiable would be cleaner, though.

provegard avatar Aug 22 '20 07:08 provegard