Make JS function out of JS object when casting to JSFunctor interface

This commit is contained in:
Alexey Andreev 2018-10-24 14:30:26 +03:00
parent 5035c58533
commit 6551f3eb68
4 changed files with 59 additions and 16 deletions

View File

@ -61,6 +61,7 @@ import org.teavm.model.TextLocation;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
import org.teavm.model.Variable; import org.teavm.model.Variable;
import org.teavm.model.instructions.AssignInstruction; import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.InvokeInstruction;
@ -252,25 +253,55 @@ class JSClassProcessor {
for (int i = 0; i < program.basicBlockCount(); ++i) { for (int i = 0; i < program.basicBlockCount(); ++i) {
BasicBlock block = program.basicBlockAt(i); BasicBlock block = program.basicBlockAt(i);
for (Instruction insn : block) { for (Instruction insn : block) {
if (!(insn instanceof InvokeInstruction)) { if (insn instanceof CastInstruction) {
continue; replacement.clear();
} CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
InvokeInstruction invoke = (InvokeInstruction) insn; if (processCast((CastInstruction) insn, callLocation)) {
insn.insertNextAll(replacement);
insn.delete();
}
} else if (insn instanceof InvokeInstruction) {
InvokeInstruction invoke = (InvokeInstruction) insn;
MethodReader method = getMethod(invoke.getMethod()); MethodReader method = getMethod(invoke.getMethod());
if (method == null) { if (method == null) {
continue; continue;
} }
CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation()); CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
replacement.clear(); replacement.clear();
if (processInvocation(method, callLocation, invoke, methodToProcess)) { if (processInvocation(method, callLocation, invoke, methodToProcess)) {
insn.insertNextAll(replacement); insn.insertNextAll(replacement);
insn.delete(); insn.delete();
}
} }
} }
} }
} }
private boolean processCast(CastInstruction cast, CallLocation location) {
if (!(cast.getTargetType() instanceof ValueType.Object)) {
return false;
}
String targetClassName = ((ValueType.Object) cast.getTargetType()).getClassName();
if (!typeHelper.isJavaScriptClass(targetClassName)) {
return false;
}
ClassReader targetClass = classSource.get(targetClassName);
if (targetClass.getAnnotations().get(JSFunctor.class.getName()) == null) {
return false;
}
Variable result = marshaller.unwrapFunctor(location, cast.getValue(), targetClass);
AssignInstruction assign = new AssignInstruction();
assign.setLocation(location.getSourceLocation());
assign.setAssignee(result);
assign.setReceiver(cast.getReceiver());
replacement.add(assign);
return true;
}
private boolean processInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke, private boolean processInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke,
MethodHolder methodToProcess) { MethodHolder methodToProcess) {
if (method.getAnnotations().get(JSBody.class.getName()) != null) { if (method.getAnnotations().get(JSBody.class.getName()) != null) {

View File

@ -76,8 +76,8 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
String thisName = context.getParameterName(1); String thisName = context.getParameterName(1);
String methodName = context.getParameterName(2); String methodName = context.getParameterName(2);
writer.append("if").ws().append("(").append(thisName).ws().append("===").ws().append("null)").ws() writer.append("if").ws().append("(typeof ").append(thisName).ws().append("!==").ws().append("\"function\")")
.append("return null;").softNewLine(); .ws().append("return ").append(thisName).append(";").softNewLine();
writer.append("var result").ws().append("=").ws().append("{};").softNewLine(); writer.append("var result").ws().append("=").ws().append("{};").softNewLine();
writer.append("result[").append(methodName).append("]").ws().append("=").ws().append(thisName) writer.append("result[").append(methodName).append("]").ws().append("=").ws().append(thisName)
.append(";").softNewLine(); .append(";").softNewLine();

View File

@ -462,7 +462,7 @@ class JSValueMarshaller {
return result; return result;
} }
private Variable unwrapFunctor(CallLocation location, Variable var, ClassReader type) { Variable unwrapFunctor(CallLocation location, Variable var, ClassReader type) {
if (!isProperFunctor(type)) { if (!isProperFunctor(type)) {
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName()); diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
return var; return var;

View File

@ -83,6 +83,12 @@ public class FunctorTest {
assertEquals("q,w", function.foo("q", "w")); assertEquals("q,w", function.foo("q", "w"));
} }
@Test
public void castToFunctor() {
JSBiFunction f = getBiFunctionAsObject().cast();
assertEquals(23042, f.foo(23, 42));
}
@JSBody(params = { "f", "a", "b" }, script = "return '(' + f(a, b) + ')';") @JSBody(params = { "f", "a", "b" }, script = "return '(' + f(a, b) + ')';")
private static native String testMethod(JSBiFunction f, int a, int b); private static native String testMethod(JSBiFunction f, int a, int b);
@ -95,6 +101,12 @@ public class FunctorTest {
+ "};") + "};")
private static native JSBiFunction getBiFunction(); private static native JSBiFunction getBiFunction();
@JSBody(script = ""
+ "return function(a, b) {"
+ "return a * 1000 + b;"
+ "};")
private static native JSObject getBiFunctionAsObject();
@JSBody(script = "" @JSBody(script = ""
+ "return function(a, b) {" + "return function(a, b) {"
+ "return a + ',' + b;" + "return a + ',' + b;"