mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
JS: allow passing Object to JS methods
This commit is contained in:
parent
059281a25c
commit
a1ed797d73
|
@ -171,8 +171,7 @@ class JSClassProcessor {
|
||||||
callerMethod.getModifiers().add(ElementModifier.STATIC);
|
callerMethod.getModifiers().add(ElementModifier.STATIC);
|
||||||
Program program = ProgramUtils.copy(method.getProgram());
|
Program program = ProgramUtils.copy(method.getProgram());
|
||||||
program.createVariable();
|
program.createVariable();
|
||||||
InstructionVariableMapper variableMapper = new InstructionVariableMapper(var ->
|
var variableMapper = new InstructionVariableMapper(var -> program.variableAt(var.getIndex() + 1));
|
||||||
program.variableAt(var.getIndex() + 1));
|
|
||||||
for (int i = program.variableCount() - 1; i > 0; --i) {
|
for (int i = program.variableCount() - 1; i > 0; --i) {
|
||||||
program.variableAt(i).setDebugName(program.variableAt(i - 1).getDebugName());
|
program.variableAt(i).setDebugName(program.variableAt(i - 1).getDebugName());
|
||||||
program.variableAt(i).setLabel(program.variableAt(i - 1).getLabel());
|
program.variableAt(i).setLabel(program.variableAt(i - 1).getLabel());
|
||||||
|
@ -241,12 +240,12 @@ class JSClassProcessor {
|
||||||
processIsInstance((IsInstanceInstruction) insn);
|
processIsInstance((IsInstanceInstruction) insn);
|
||||||
} else if (insn instanceof InvokeInstruction) {
|
} else if (insn instanceof InvokeInstruction) {
|
||||||
var invoke = (InvokeInstruction) insn;
|
var invoke = (InvokeInstruction) insn;
|
||||||
processInvokeArgs(invoke);
|
|
||||||
|
|
||||||
var method = getMethod(invoke.getMethod().getClassName(), invoke.getMethod().getDescriptor());
|
var method = getMethod(invoke.getMethod().getClassName(), invoke.getMethod().getDescriptor());
|
||||||
if (method == null) {
|
if (method == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
processInvokeArgs(invoke, method);
|
||||||
var callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
|
var callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
|
||||||
replacement.clear();
|
replacement.clear();
|
||||||
if (processInvocation(method, callLocation, invoke, methodToProcess)) {
|
if (processInvocation(method, callLocation, invoke, methodToProcess)) {
|
||||||
|
@ -272,8 +271,9 @@ class JSClassProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processInvokeArgs(InvokeInstruction invoke) {
|
private void processInvokeArgs(InvokeInstruction invoke, MethodReader methodToInvoke) {
|
||||||
if (typeHelper.isJavaScriptClass(invoke.getMethod().getClassName())) {
|
if (typeHelper.isJavaScriptClass(invoke.getMethod().getClassName())
|
||||||
|
|| methodToInvoke.getAnnotations().get(JSBody.class.getName()) != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Variable[] newArgs = null;
|
Variable[] newArgs = null;
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.jso.impl;
|
package org.teavm.jso.impl;
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import org.teavm.dependency.AbstractDependencyListener;
|
import org.teavm.dependency.AbstractDependencyListener;
|
||||||
import org.teavm.dependency.DependencyAgent;
|
import org.teavm.dependency.DependencyAgent;
|
||||||
import org.teavm.dependency.MethodDependency;
|
import org.teavm.dependency.MethodDependency;
|
||||||
|
@ -35,7 +34,7 @@ class JSDependencyListener extends AbstractDependencyListener {
|
||||||
@Override
|
@Override
|
||||||
public void methodReached(DependencyAgent agent, MethodDependency method) {
|
public void methodReached(DependencyAgent agent, MethodDependency method) {
|
||||||
MethodReference ref = method.getReference();
|
MethodReference ref = method.getReference();
|
||||||
Set<MethodReference> callbackMethods = repository.callbackMethods.get(ref);
|
var callbackMethods = repository.callbackMethods.get(ref);
|
||||||
if (callbackMethods != null) {
|
if (callbackMethods != null) {
|
||||||
for (MethodReference callbackMethod : callbackMethods) {
|
for (MethodReference callbackMethod : callbackMethods) {
|
||||||
agent.linkMethod(callbackMethod).addLocation(new CallLocation(ref)).use();
|
agent.linkMethod(callbackMethod).addLocation(new CallLocation(ref)).use();
|
||||||
|
|
|
@ -55,14 +55,27 @@ public class JSOPlugin implements TeaVMPlugin {
|
||||||
var wrapperGenerator = new JSWrapperGenerator();
|
var wrapperGenerator = new JSWrapperGenerator();
|
||||||
jsHost.add(new MethodReference(JSWrapper.class, "directJavaToJs", Object.class, JSObject.class),
|
jsHost.add(new MethodReference(JSWrapper.class, "directJavaToJs", Object.class, JSObject.class),
|
||||||
wrapperGenerator);
|
wrapperGenerator);
|
||||||
|
jsHost.add(new MethodReference(JSWrapper.class, "directJsToJava", JSObject.class, Object.class),
|
||||||
|
wrapperGenerator);
|
||||||
|
jsHost.add(new MethodReference(JSWrapper.class, "dependencyJavaToJs", Object.class, JSObject.class),
|
||||||
|
wrapperGenerator);
|
||||||
|
jsHost.add(new MethodReference(JSWrapper.class, "dependencyJsToJava", JSObject.class, Object.class),
|
||||||
|
wrapperGenerator);
|
||||||
jsHost.add(new MethodReference(JSWrapper.class, "isJava", Object.class, boolean.class),
|
jsHost.add(new MethodReference(JSWrapper.class, "isJava", Object.class, boolean.class),
|
||||||
wrapperGenerator);
|
wrapperGenerator);
|
||||||
|
jsHost.add(new MethodReference(JSWrapper.class, "isJava", JSObject.class, boolean.class),
|
||||||
|
wrapperGenerator);
|
||||||
jsHost.add(new MethodReference(JSWrapper.class, "wrapperToJs", JSWrapper.class, JSObject.class),
|
jsHost.add(new MethodReference(JSWrapper.class, "wrapperToJs", JSWrapper.class, JSObject.class),
|
||||||
wrapperGenerator);
|
wrapperGenerator);
|
||||||
jsHost.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class),
|
jsHost.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class),
|
||||||
wrapperGenerator);
|
wrapperGenerator);
|
||||||
|
|
||||||
host.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class),
|
host.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class),
|
||||||
wrapperGenerator);
|
wrapperGenerator);
|
||||||
|
host.add(new MethodReference(JSWrapper.class, "dependencyJavaToJs", Object.class, JSObject.class),
|
||||||
|
wrapperGenerator);
|
||||||
|
host.add(new MethodReference(JSWrapper.class, "dependencyJsToJava", JSObject.class, Object.class),
|
||||||
|
wrapperGenerator);
|
||||||
|
|
||||||
TeaVMPluginUtil.handleNatives(host, JS.class);
|
TeaVMPluginUtil.handleNatives(host, JS.class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ class JSTypeHelper {
|
||||||
private Map<String, Boolean> knownJavaScriptClasses = new HashMap<>();
|
private Map<String, Boolean> knownJavaScriptClasses = new HashMap<>();
|
||||||
private Map<String, Boolean> knownJavaScriptImplementations = new HashMap<>();
|
private Map<String, Boolean> knownJavaScriptImplementations = new HashMap<>();
|
||||||
|
|
||||||
public JSTypeHelper(ClassReaderSource classSource) {
|
JSTypeHelper(ClassReaderSource classSource) {
|
||||||
this.classSource = classSource;
|
this.classSource = classSource;
|
||||||
knownJavaScriptClasses.put(JSObject.class.getName(), true);
|
knownJavaScriptClasses.put(JSObject.class.getName(), true);
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,8 @@ class JSTypeHelper {
|
||||||
return isSupportedType(((ValueType.Array) type).getItemType());
|
return isSupportedType(((ValueType.Array) type).getItemType());
|
||||||
} else if (type instanceof ValueType.Object) {
|
} else if (type instanceof ValueType.Object) {
|
||||||
String typeName = ((ValueType.Object) type).getClassName();
|
String typeName = ((ValueType.Object) type).getClassName();
|
||||||
return typeName.equals("java.lang.String") || isJavaScriptClass(typeName);
|
return typeName.equals("java.lang.String") || typeName.equals("java.lang.Object")
|
||||||
|
|| isJavaScriptClass(typeName);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,16 @@ class JSValueMarshaller {
|
||||||
|
|
||||||
if (type instanceof ValueType.Object) {
|
if (type instanceof ValueType.Object) {
|
||||||
String className = ((ValueType.Object) type).getClassName();
|
String className = ((ValueType.Object) type).getClassName();
|
||||||
|
if (className.equals("java.lang.Object")) {
|
||||||
|
var unwrapNative = new InvokeInstruction();
|
||||||
|
unwrapNative.setLocation(location);
|
||||||
|
unwrapNative.setType(InvocationType.SPECIAL);
|
||||||
|
unwrapNative.setMethod(new MethodReference(JSWrapper.class, "javaToJs", Object.class, JSObject.class));
|
||||||
|
unwrapNative.setArguments(var);
|
||||||
|
unwrapNative.setReceiver(program.createVariable());
|
||||||
|
replacement.add(unwrapNative);
|
||||||
|
return unwrapNative.getReceiver();
|
||||||
|
}
|
||||||
if (!className.equals("java.lang.String")) {
|
if (!className.equals("java.lang.String")) {
|
||||||
return var;
|
return var;
|
||||||
}
|
}
|
||||||
|
@ -282,7 +292,16 @@ class JSValueMarshaller {
|
||||||
}
|
}
|
||||||
} else if (type instanceof ValueType.Object) {
|
} else if (type instanceof ValueType.Object) {
|
||||||
String className = ((ValueType.Object) type).getClassName();
|
String className = ((ValueType.Object) type).getClassName();
|
||||||
if (className.equals(JSObject.class.getName())) {
|
if (className.equals(Object.class.getName())) {
|
||||||
|
var wrapNative = new InvokeInstruction();
|
||||||
|
wrapNative.setLocation(location.getSourceLocation());
|
||||||
|
wrapNative.setType(InvocationType.SPECIAL);
|
||||||
|
wrapNative.setMethod(new MethodReference(JSWrapper.class, "jsToJava", JSObject.class, Object.class));
|
||||||
|
wrapNative.setArguments(var);
|
||||||
|
wrapNative.setReceiver(program.createVariable());
|
||||||
|
replacement.add(wrapNative);
|
||||||
|
return wrapNative.getReceiver();
|
||||||
|
} else if (className.equals(JSObject.class.getName())) {
|
||||||
return var;
|
return var;
|
||||||
} else if (className.equals("java.lang.String")) {
|
} else if (className.equals("java.lang.String")) {
|
||||||
return unwrap(var, "unwrapString", JSMethods.JS_OBJECT, stringType, location.getSourceLocation());
|
return unwrap(var, "unwrapString", JSMethods.JS_OBJECT, stringType, location.getSourceLocation());
|
||||||
|
|
|
@ -109,6 +109,15 @@ public final class JSWrapper {
|
||||||
@NoSideEffects
|
@NoSideEffects
|
||||||
public static native JSObject directJavaToJs(Object obj);
|
public static native JSObject directJavaToJs(Object obj);
|
||||||
|
|
||||||
|
@NoSideEffects
|
||||||
|
public static native Object directJsToJava(JSObject obj);
|
||||||
|
|
||||||
|
@NoSideEffects
|
||||||
|
public static native JSObject dependencyJavaToJs(Object obj);
|
||||||
|
|
||||||
|
@NoSideEffects
|
||||||
|
public static native Object dependencyJsToJava(JSObject obj);
|
||||||
|
|
||||||
@NoSideEffects
|
@NoSideEffects
|
||||||
private static native JSObject wrapperToJs(JSWrapper obj);
|
private static native JSObject wrapperToJs(JSWrapper obj);
|
||||||
|
|
||||||
|
@ -118,6 +127,9 @@ public final class JSWrapper {
|
||||||
@NoSideEffects
|
@NoSideEffects
|
||||||
public static native boolean isJava(Object obj);
|
public static native boolean isJava(Object obj);
|
||||||
|
|
||||||
|
@NoSideEffects
|
||||||
|
public static native boolean isJava(JSObject obj);
|
||||||
|
|
||||||
public static JSObject unwrap(Object o) {
|
public static JSObject unwrap(Object o) {
|
||||||
if (o == null) {
|
if (o == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -132,6 +144,20 @@ public final class JSWrapper {
|
||||||
return isJava(o) ? unwrap(o) : directJavaToJs(o);
|
return isJava(o) ? unwrap(o) : directJavaToJs(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static JSObject javaToJs(Object o) {
|
||||||
|
if (o == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return isJava(o) && o instanceof JSWrapper ? unwrap(o) : dependencyJavaToJs(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object jsToJava(JSObject o) {
|
||||||
|
if (o == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return !isJava(o) ? wrap(directJsToJava(o)) : dependencyJsToJava(o);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isJs(Object o) {
|
public static boolean isJs(Object o) {
|
||||||
if (o == null) {
|
if (o == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -19,15 +19,21 @@ import java.io.IOException;
|
||||||
import org.teavm.backend.javascript.spi.Injector;
|
import org.teavm.backend.javascript.spi.Injector;
|
||||||
import org.teavm.backend.javascript.spi.InjectorContext;
|
import org.teavm.backend.javascript.spi.InjectorContext;
|
||||||
import org.teavm.dependency.DependencyAgent;
|
import org.teavm.dependency.DependencyAgent;
|
||||||
|
import org.teavm.dependency.DependencyNode;
|
||||||
import org.teavm.dependency.DependencyPlugin;
|
import org.teavm.dependency.DependencyPlugin;
|
||||||
import org.teavm.dependency.MethodDependency;
|
import org.teavm.dependency.MethodDependency;
|
||||||
import org.teavm.model.MethodReference;
|
import org.teavm.model.MethodReference;
|
||||||
|
|
||||||
public class JSWrapperGenerator implements Injector, DependencyPlugin {
|
public class JSWrapperGenerator implements Injector, DependencyPlugin {
|
||||||
|
private DependencyNode externalClassesNode;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
|
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
|
||||||
switch (methodRef.getName()) {
|
switch (methodRef.getName()) {
|
||||||
case "directJavaToJs":
|
case "directJavaToJs":
|
||||||
|
case "directJsToJava":
|
||||||
|
case "dependencyJavaToJs":
|
||||||
|
case "dependencyJsToJava":
|
||||||
case "wrapperToJs":
|
case "wrapperToJs":
|
||||||
case "jsToWrapper":
|
case "jsToWrapper":
|
||||||
context.writeExpr(context.getArgument(0));
|
context.writeExpr(context.getArgument(0));
|
||||||
|
@ -41,8 +47,23 @@ public class JSWrapperGenerator implements Injector, DependencyPlugin {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void methodReached(DependencyAgent agent, MethodDependency method) {
|
public void methodReached(DependencyAgent agent, MethodDependency method) {
|
||||||
if (method.getMethod().getName().equals("jsToWrapper")) {
|
switch (method.getMethod().getName()) {
|
||||||
method.getResult().propagate(agent.getType(JSWrapper.class.getName()));
|
case "jsToWrapper":
|
||||||
|
method.getResult().propagate(agent.getType(JSWrapper.class.getName()));
|
||||||
|
break;
|
||||||
|
case "dependencyJavaToJs":
|
||||||
|
method.getVariable(1).connect(getExternalClassesNode(agent));
|
||||||
|
break;
|
||||||
|
case "dependencyJsToJava":
|
||||||
|
getExternalClassesNode(agent).connect(method.getResult());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DependencyNode getExternalClassesNode(DependencyAgent agent) {
|
||||||
|
if (externalClassesNode == null) {
|
||||||
|
externalClassesNode = agent.createNode();
|
||||||
|
}
|
||||||
|
return externalClassesNode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,9 +229,39 @@ public class JSWrapperTest {
|
||||||
assertEquals("org.teavm.jso.impl.JSWrapper", field1.getClass().getName());
|
assertEquals("org.teavm.jso.impl.JSWrapper", field1.getClass().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void passJavaToJS() {
|
||||||
|
var a = processObject(new A(23));
|
||||||
|
assertTrue(a instanceof A);
|
||||||
|
assertEquals(23, ((A) a).getX());
|
||||||
|
|
||||||
|
a = processObject(JSString.valueOf("qwe"));
|
||||||
|
assertTrue(a instanceof JSString);
|
||||||
|
assertEquals("qwe", ((JSString) a).stringValue());
|
||||||
|
|
||||||
|
a = processObject(JSNumber.valueOf(23));
|
||||||
|
assertTrue(a instanceof JSString);
|
||||||
|
assertEquals("number", ((JSString) a).stringValue());
|
||||||
|
}
|
||||||
|
|
||||||
@JSBody(script = "return null;")
|
@JSBody(script = "return null;")
|
||||||
private static native JSObject jsNull();
|
private static native JSObject jsNull();
|
||||||
|
|
||||||
@JSBody(params = "o", script = "return o === null;")
|
@JSBody(params = "o", script = "return o === null;")
|
||||||
private static native boolean isNull(JSObject o);
|
private static native boolean isNull(JSObject o);
|
||||||
|
|
||||||
|
@JSBody(params = "o", script = "return typeof o === 'number' ? 'number' : o;")
|
||||||
|
private static native Object processObject(Object o);
|
||||||
|
|
||||||
|
static class A {
|
||||||
|
private int x;
|
||||||
|
|
||||||
|
A(int x) {
|
||||||
|
this.x = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,15 +41,15 @@ public class JSOTest {
|
||||||
assertNotNull(foundProblem);
|
assertNotNull(foundProblem);
|
||||||
Object[] params = foundProblem.getParams();
|
Object[] params = foundProblem.getParams();
|
||||||
assertThat(params[0], is(new MethodReference(JSOTest.class, "jsBodyWithWrongParameter",
|
assertThat(params[0], is(new MethodReference(JSOTest.class, "jsBodyWithWrongParameter",
|
||||||
Object.class, void.class)));
|
A.class, void.class)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void callJSBodyWithWrongParameter() {
|
private static void callJSBodyWithWrongParameter() {
|
||||||
jsBodyWithWrongParameter(23);
|
jsBodyWithWrongParameter(new A());
|
||||||
}
|
}
|
||||||
|
|
||||||
@JSBody(params = "param", script = "alert(param.toString());")
|
@JSBody(params = "param", script = "alert(param.toString());")
|
||||||
private static native void jsBodyWithWrongParameter(Object param);
|
private static native void jsBodyWithWrongParameter(A param);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void reportsAboutWrongNonStaticJSBody() {
|
public void reportsAboutWrongNonStaticJSBody() {
|
||||||
|
@ -69,7 +69,7 @@ public class JSOTest {
|
||||||
new JSOTest().wrongNonStaticJSBody();
|
new JSOTest().wrongNonStaticJSBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
@JSBody(params = {}, script = "alert(this.toString());")
|
@JSBody(script = "alert(this.toString());")
|
||||||
private native void wrongNonStaticJSBody();
|
private native void wrongNonStaticJSBody();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -83,7 +83,7 @@ public class JSOTest {
|
||||||
assertNotNull(foundProblem);
|
assertNotNull(foundProblem);
|
||||||
Object[] params = foundProblem.getParams();
|
Object[] params = foundProblem.getParams();
|
||||||
assertThat(params[0], is(new MethodReference(JSOTest.class, "jsBodyWithWrongReturningType", String.class,
|
assertThat(params[0], is(new MethodReference(JSOTest.class, "jsBodyWithWrongReturningType", String.class,
|
||||||
Object.class)));
|
A.class)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void callJSBodyWithWrongReturningType() {
|
private static void callJSBodyWithWrongReturningType() {
|
||||||
|
@ -91,7 +91,7 @@ public class JSOTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@JSBody(params = "value", script = "return value;")
|
@JSBody(params = "value", script = "return value;")
|
||||||
private static native Object jsBodyWithWrongReturningType(String value);
|
private static native A jsBodyWithWrongReturningType(String value);
|
||||||
|
|
||||||
private List<Problem> build(String methodName) {
|
private List<Problem> build(String methodName) {
|
||||||
TeaVM vm = new TeaVMBuilder(new JavaScriptTarget()).build();
|
TeaVM vm = new TeaVMBuilder(new JavaScriptTarget()).build();
|
||||||
|
@ -101,4 +101,7 @@ public class JSOTest {
|
||||||
vm.build(name -> new ByteArrayOutputStream(), "tmp");
|
vm.build(name -> new ByteArrayOutputStream(), "tmp");
|
||||||
return vm.getProblemProvider().getSevereProblems();
|
return vm.getProblemProvider().getSevereProblems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class A {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user