Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nashorn bug when calling overloaded method with varargs parameter

Assume the following API:

package nashorn.test;

public class API {

    public static void test(String string) {
        throw new RuntimeException("Don't call this");
    }

    public static void test(Integer... args) {
        System.out.println("OK");
    }
}

The following Nashorn JavaScript snippet will fail:

var API = Java.type("nashorn.test.API");
API.test(1);

The first method will be called instead of the second. Is this a bug in the Nashorn engine?

For the record, this issue was previously reported on the jOOQ User Group, where method overloading and varargs are used heavily, and where this issue may cause a lot of trouble.

About boxing

There might be a suspicion that this could have to do with boxing. It doesn't. The problem also appears when I do

public class API {

    public static void test(String string) {
        throw new RuntimeException("Don't call this");
    }

    public static void test(Integer... args) {
        System.out.println("OK");
    }

    public static void test(MyType... args) {
        System.out.println("OK");
    }
}

And:

public class MyType {
}

And then:

var API = Java.type("nashorn.test.API");
var MyType = Java.type("nashorn.test.MyType");

API.test(new MyType());
like image 966
Lukas Eder Avatar asked Sep 01 '14 09:09

Lukas Eder


3 Answers

As the guy who wrote the overload resolution mechanism for Nashorn, I'm always fascinated with corner cases that people run into. For better or worse, here's how this ends up being invoked:

Nashorn's overload method resolution mimics Java Language Specification (JLS) as much as possible, but allows for JavaScript-specific conversions too. JLS says that when selecting a method to invoke for an overloaded name, variable arity methods can be considered for invocation only when there is no applicable fixed arity method. Normally, when invoking from Java test(String) would not be an applicable to an invocation with an int, so the test(Integer...) method would get invoked. However, since JavaScript actually allows number-to-string implicit conversion, it is applicable, and considered before any variable arity methods. Hence the observed behavior. Arity trumps non-conversion. If you added a test(int) method, it'd be invoked before the String method, as it's fixed arity and more specific than the String one.

You could argue that we should alter the algorithm for choosing the method. A lot of thought has been given to this since even before the Nashorn project (even back when I was developing Dynalink independently). Current code (as embodied in the Dynalink library, which Nashorn actually builds upon) follows JLS to the letter and in absence of language-specific type conversions will choose the same methods as Java would. However, as soon as you start relaxing your type system, things start to subtly change, and the more you relax it, the more they'll change (and JavaScript relaxes a lot), and any change to the choice algorithm will have some other weird behavior that someone else will run into… it just comes with the relaxed type system, I'm afraid. For example:

  • If we allowed varargs to be considered together with fixargs, we'd need to invent a "more specific than" relation among differing arity methods, something that doesn't exist in JLS and thus isn't compatible with it, and would cause varargs to sometimes be invoked when otherwise JLS would prescribe fixargs invocation.
  • If we disallowed JS-allowed conversions (thus forcing test(String) to not be considered applicable to an int parameter), some JS developers would feel encumbered by needing to contort their program into invoking the String method (e.g. doing test(String(x)) to ensure x is a string, etc.

As you can see, no matter what we do, something else would suffer; overloaded method selection is in a tight spot between Java and JS type systems and very sensitive to even small changes in the logic.

Finally, when you manually select among overloads, you can also stick to unqualified type names, as long as there's no ambiguity in potential methods signatures for the package name in the argument position, that is

API["test(Integer[])"](1);

should work too, no need for the java.lang. prefix. That might ease the syntactic noise a bit, unless you can rework the API.

HTH, Attila.

like image 95
Attila Szegedi Avatar answered Oct 21 '22 02:10

Attila Szegedi


These are valid workarounds:

Explicitly calling the test(Integer[]) method using an array argument:

var API = Java.type("nashorn.test.API");
API.test([1]);

Removing the overload:

public class AlternativeAPI1 {
    public static void test(Integer... args) {
        System.out.println("OK");
    }
}

Removing the varargs:

public class AlternativeAPI3 {
    public static void test(String string) {
        throw new RuntimeException("Don't call this");
    }

    public static void test(Integer args) {
        System.out.println("OK");
    }
}

Replacing String by CharSequence (or any other "similar type"):

public class AlternativeAPI2 {
    public static void test(CharSequence string) {
        throw new RuntimeException("Don't call this");
    }

    public static void test(Integer args) {
        System.out.println("OK");
    }
}
like image 7
Lukas Eder Avatar answered Oct 21 '22 04:10

Lukas Eder


This is an ambiguous situation. The second case it is looking for either an array of integers or more than one integer to discern from the first case. You can use method selection to tell Nashorn which case you mean.

API["test(java.lang.Integer[])"](1);
like image 4
wickund Avatar answered Oct 21 '22 04:10

wickund