I've been experimenting with Nashorn and got some strange results regarding the return types of eval()
.
// calculator.js
var add = function(a, b) {
return a + b;
}
And the following Java code:
// CalculatorTest.java
import java.io.InputStreamReader;
import javax.script.*
import org.junit.*
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
public class CalculatorTest {
ScriptEngine nashorn;
@Before
public void setup() throws ScriptException {
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
nashorn = factory.getScriptEngine("–optimistic-types=true");
nashorn.eval(new InputStreamReader(
getClass().getClassLoader().getResourceAsStream("calculator.js")));
}
@Test
public void addViaInvokeFuntion() throws Exception {
Object result = ((Invocable) nashorn).invokeFunction("add", 2, 3);
Assert.assertEquals(5.0, result);
}
@Test
public void addViaSimple() throws Exception {
Object result = nashorn.eval("2+3");
Assert.assertEquals(5, result);
}
@Test
public void addViaMoreComplexEval() throws Exception {
Object result = nashorn.eval(
"var anotherAdd = function(a, b) {\n" +
" return a + b;\n" +
"};\n" +
"anotherAdd(2, 3);");
Assert.assertEquals(5L, result);
}
}
Why do these tests succeed in exactly this way?
Is there any way to "predict" the Nashorn return types?
Shouldn't the optimistic typing do exactly that?
What you see here is because of the way Nashorn creates specialized versions of functions based on input type of their parameters (and not optimistic typing per se). When you invoke add
through Invocable
interface you end up invoking the specialization add(Object, Object)
and not add(int, int)
. If the parameter types are objects, then there's no optimism to leverage; you get the following bytecode:
public static add(Object;Object;Object;)Object;
aload 1
aload 2
invokestatic ScriptRuntime.ADD(Object;Object;)Object;
areturn
(You can inspect these yourself if you add --print-code
to the engine command line arguments; another helpful one is --log=recompile
which'll print the type specializations of functions that have been generated.)
So it has nothing to do with add
being defined in calculator.js
but rather the fact that your call site – Invocable.invokeFunction
is typed as taking all-object parameters.
ScriptRuntime.ADD(Object, Object)
doesn't try to be too smart and narrow the type of its result; it's the generic-case slow-path +
operator implementation that is used when there's no static information about parameter types, and it has to be prepared to deal with JavaScripts's weird cases such as "2" + []
etc. Its code could certainly contain special optimization cases such as "if the result of the operation fits in an Integer
return one", but we decided the additional complexity there ain't worth the hassle as you have lost static type information on input anyway. Hence, you'll get back a double for all numeric addition when the static type of the parameters is Object
.
If on the other hand you'd execute nashorn.eval("add(2, 3)")
then it would invoke the add(int, int)
specialization, which is optimistic and you indeed end up with return value being integer 5.
Hope that helps.