oracle / graaljs

GraalJS – A high-performance, ECMAScript compliant, and embeddable JavaScript runtime for Java
https://www.graalvm.org/javascript/
Universal Permissive License v1.0
1.81k stars 190 forks source link

Getter- and Setter- Function Mapping when using "Value#as" #181

Open madoar opened 5 years ago

madoar commented 5 years ago

I'm currently exploring the functionality of "casting" JavaScript objects to Java interfaces using Value#as. In general I'm quite successful, but I've discovered some limitations about which I'm not sure whether they are intentional or not.

More specifically I'm currently trying to cast JavaScript object with setter and getter methods into Java interfaces.

Let's assume I have the following Java interface:

public interface A {
   String getX();

   void setX(String x);
}

Then I can successfully cast a JS object of the following "type" to the previous Java interface:

class AImpl1 {
   constructor() {
      // do nothing
   }

   getX() {
      return "test";
   }

   setX(x) {
      // do nothing
   }
}

What doesn't seem to work is casting a JS object with a JS getter method to the previous Java interface:

class AImpl2 {
   constructor() {
      // do nothing
   }

   get x() {
      return "test";
   }

   setX(x) {
      // do nothing
   }
}

The issue seems to be that get x() is not assumed to be the same as getX in Java. I also expect that a JS object with a JS setter method can't be converted to a Java interface for the same reason, but I haven't tested it.

Is this behavior of Value#as intentional or is it a bug?

iamstolis commented 5 years ago

Is this behavior of Value#as intentional or is it a bug?

Yes, the current behavior is intentional. getX() and get x() are two different things in JavaScript. The current "casting" works with the former but not with the latter. The "casting" works with executable members. get x() is not an executable member (from our polyglot point of view). It is an implementation detail of member x. Namely, it is the side-effect that is triggered when member x is read.

Hence, I don't expect us to support the get x() use-case as it does not fit well into the bigger picture of our polyglot support of "casting". Moreover, such support would be ambiguous in the unlikely (but possible) case when both get x()/set x() and getX()/setX() exist on the same object.

madoar commented 5 years ago

The "casting" works with executable members. get x() is not an executable member (from our polyglot point of view). It is an implementation detail of member x. Namely, it is the side-effect that is triggered when member x is read.

I don't think that a JavaScript getter method is simply a side-effect. In my opinion it is a fully fledged method with a special syntax that allows accessing the method as if it were a field. A main difference between a normal field and a getter method is that the getter method does not need to be backed by a real field.

Moreover, such support would be ambiguous in the unlikely (but possible) case when both get x()/set x() and getX()/setX() exist on the same object.

That is true, but there are solutions for that. For example you could set a strict resolution priority, where a Java A getX() method is first mapped to a JavaScript method with the same name, and only if such a JavaScript method does not exist a JavaScript getter method (i.e. get x()) is searched. This behavior could also be made optional with a flag to ensure backwards compatibility.