Wrap function by an object if returning value of native method is

a JSFunctor interface.

See #280
This commit is contained in:
Alexey Andreev 2017-06-11 00:15:41 +03:00
parent 45ba247265
commit 2992c6e406
6 changed files with 180 additions and 99 deletions

View File

@ -405,4 +405,8 @@ final class JS {
@GeneratedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject function(JSObject instance, JSObject property);
@GeneratedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject functionAsObject(JSObject instance, JSObject property);
}

View File

@ -66,7 +66,6 @@ import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.StringConstantInstruction;
import org.teavm.model.util.InstructionVariableMapper;
import org.teavm.model.util.ModelUtils;
import org.teavm.model.util.ProgramUtils;
@ -114,7 +113,7 @@ class JSClassProcessor {
private void getFunctorMethods(String className, Map<MethodDescriptor, MethodReference> methods) {
classSource.getAncestors(className).forEach(cls -> {
if (cls.getAnnotations().get(JSFunctor.class.getName()) != null && isProperFunctor(cls)) {
if (cls.getAnnotations().get(JSFunctor.class.getName()) != null && marshaller.isProperFunctor(cls)) {
MethodReference method = cls.getMethods().iterator().next().getReference();
if (!methods.containsKey(method.getDescriptor())) {
methods.put(method.getDescriptor(), method);
@ -241,7 +240,7 @@ class JSClassProcessor {
private void setCurrentProgram(Program program) {
this.program = program;
marshaller = new JSValueMarshaller(diagnostics, typeHelper, program, replacement);
marshaller = new JSValueMarshaller(diagnostics, typeHelper, classSource, program, replacement);
}
void processProgram(MethodHolder methodToProcess) {
@ -333,18 +332,18 @@ class JSClassProcessor {
newInvoke.setReceiver(result);
newInvoke.setLocation(invoke.getLocation());
if (invoke.getInstance() != null) {
Variable arg = wrapArgument(callLocation, invoke.getInstance(),
Variable arg = marshaller.wrapArgument(callLocation, invoke.getInstance(),
ValueType.object(method.getOwnerName()), false);
newInvoke.getArguments().add(arg);
}
for (int i = 0; i < invoke.getArguments().size(); ++i) {
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(i),
Variable arg = marshaller.wrapArgument(callLocation, invoke.getArguments().get(i),
method.parameterType(i), byRefParams[i]);
newInvoke.getArguments().add(arg);
}
replacement.add(newInvoke);
if (result != null) {
result = marshaller.unwrap(callLocation, result, method.getResultType());
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation());
}
@ -365,7 +364,7 @@ class JSClassProcessor {
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation());
if (result != null) {
result = marshaller.unwrap(callLocation, result, method.getResultType());
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation());
}
return true;
@ -375,7 +374,7 @@ class JSClassProcessor {
if (propertyName == null) {
propertyName = cutPrefix(method.getName(), 3);
}
Variable wrapped = wrapArgument(callLocation, invoke.getArguments().get(0),
Variable wrapped = marshaller.wrapArgument(callLocation, invoke.getArguments().get(0),
method.parameterType(0), false);
addPropertySet(propertyName, invoke.getInstance(), wrapped, invoke.getLocation());
return true;
@ -394,19 +393,19 @@ class JSClassProcessor {
private boolean processIndexer(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
if (isProperGetIndexer(method.getDescriptor())) {
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
addIndexerGet(invoke.getInstance(), marshaller.wrap(invoke.getArguments().get(0),
method.parameterType(0), invoke.getLocation(), false), result, invoke.getLocation());
addIndexerGet(invoke.getInstance(), marshaller.wrapArgument(callLocation, invoke.getArguments().get(0),
method.parameterType(0), false), result, invoke.getLocation());
if (result != null) {
result = marshaller.unwrap(callLocation, result, method.getResultType());
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation());
}
return true;
}
if (isProperSetIndexer(method.getDescriptor())) {
Variable index = marshaller.wrap(invoke.getArguments().get(0), method.parameterType(0),
invoke.getLocation(), false);
Variable value = marshaller.wrap(invoke.getArguments().get(1), method.parameterType(1),
invoke.getLocation(), false);
Variable index = marshaller.wrapArgument(callLocation, invoke.getArguments().get(0),
method.parameterType(0), false);
Variable value = marshaller.wrapArgument(callLocation, invoke.getArguments().get(1),
method.parameterType(1), false);
addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation());
return true;
}
@ -469,17 +468,17 @@ class JSClassProcessor {
newInvoke.setType(InvocationType.SPECIAL);
newInvoke.setReceiver(result);
newInvoke.getArguments().add(invoke.getInstance());
newInvoke.getArguments().add(addStringWrap(addString(name, invoke.getLocation()),
newInvoke.getArguments().add(marshaller.addStringWrap(marshaller.addString(name, invoke.getLocation()),
invoke.getLocation()));
newInvoke.setLocation(invoke.getLocation());
for (int i = 0; i < invoke.getArguments().size(); ++i) {
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(i),
Variable arg = marshaller.wrapArgument(callLocation, invoke.getArguments().get(i),
method.parameterType(i), byRefParams[i]);
newInvoke.getArguments().add(arg);
}
replacement.add(newInvoke);
if (result != null) {
result = marshaller.unwrap(callLocation, result, method.getResultType());
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation());
}
@ -627,11 +626,11 @@ class JSClassProcessor {
insn.setMethod(calleeRef);
replacement.clear();
if (!callee.hasModifier(ElementModifier.STATIC)) {
insn.setInstance(marshaller.unwrap(location, program.variableAt(paramIndex++),
insn.setInstance(marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++),
ValueType.object(calleeRef.getClassName())));
}
for (int i = 0; i < callee.parameterCount(); ++i) {
insn.getArguments().add(marshaller.unwrap(location, program.variableAt(paramIndex++),
insn.getArguments().add(marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++),
callee.parameterType(i)));
}
if (callee.getResultType() != ValueType.VOID) {
@ -655,7 +654,7 @@ class JSClassProcessor {
private void addPropertyGet(String propertyName, Variable instance, Variable receiver,
TextLocation location) {
Variable nameVar = addStringWrap(addString(propertyName, location), location);
Variable nameVar = marshaller.addStringWrap(marshaller.addString(propertyName, location), location);
InvokeInstruction insn = new InvokeInstruction();
insn.setType(InvocationType.SPECIAL);
insn.setMethod(new MethodReference(JS.class, "get", JSObject.class, JSObject.class, JSObject.class));
@ -667,7 +666,7 @@ class JSClassProcessor {
}
private void addPropertySet(String propertyName, Variable instance, Variable value, TextLocation location) {
Variable nameVar = addStringWrap(addString(propertyName, location), location);
Variable nameVar = marshaller.addStringWrap(marshaller.addString(propertyName, location), location);
InvokeInstruction insn = new InvokeInstruction();
insn.setType(InvocationType.SPECIAL);
insn.setMethod(new MethodReference(JS.class, "set", JSObject.class, JSObject.class,
@ -710,61 +709,6 @@ class JSClassProcessor {
replacement.add(insn);
}
private Variable addStringWrap(Variable var, TextLocation location) {
return marshaller.wrap(var, ValueType.object("java.lang.String"), location, false);
}
private Variable addString(String str, TextLocation location) {
Variable var = program.createVariable();
StringConstantInstruction nameInsn = new StringConstantInstruction();
nameInsn.setReceiver(var);
nameInsn.setConstant(str);
nameInsn.setLocation(location);
replacement.add(nameInsn);
return var;
}
private Variable wrapArgument(CallLocation location, Variable var, ValueType type, boolean byRef) {
if (type instanceof ValueType.Object) {
String className = ((ValueType.Object) type).getClassName();
ClassReader cls = classSource.get(className);
if (cls.getAnnotations().get(JSFunctor.class.getName()) != null) {
return wrapFunctor(location, var, cls);
}
}
return marshaller.wrap(var, type, location.getSourceLocation(), byRef);
}
private boolean isProperFunctor(ClassReader type) {
if (!type.hasModifier(ElementModifier.INTERFACE)) {
return false;
}
return type.getMethods().stream()
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
.count() == 1;
}
private Variable wrapFunctor(CallLocation location, Variable var, ClassReader type) {
if (!isProperFunctor(type)) {
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
return var;
}
String name = type.getMethods().stream()
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
.findFirst().get().getName();
Variable functor = program.createVariable();
Variable nameVar = addStringWrap(addString(name, location.getSourceLocation()), location.getSourceLocation());
InvokeInstruction insn = new InvokeInstruction();
insn.setType(InvocationType.SPECIAL);
insn.setMethod(new MethodReference(JS.class, "function", JSObject.class, JSObject.class, JSObject.class));
insn.setReceiver(functor);
insn.getArguments().add(var);
insn.getArguments().add(nameVar);
insn.setLocation(location.getSourceLocation());
replacement.add(insn);
return functor;
}
private MethodReader getMethod(MethodReference ref) {
ClassReader cls = classSource.get(ref.getClassName());
if (cls == null) {

View File

@ -44,6 +44,9 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
case "function":
writeFunction(context, writer);
break;
case "functionAsObject":
writeFunctionAsObject(context, writer);
break;
}
}
@ -69,6 +72,16 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
writer.append("return ").append(thisName).append("[name]();").softNewLine();
}
private void writeFunctionAsObject(GeneratorContext context, SourceWriter writer) throws IOException {
String thisName = context.getParameterName(1);
String methodName = context.getParameterName(2);
writer.append("var result").ws().append("=").ws().append("{};").softNewLine();
writer.append("result[").append(methodName).append("]").ws().append("=").ws().append(thisName)
.append(";").softNewLine();
writer.append("return result;").softNewLine();
}
@Override
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
SourceWriter writer = context.getWriter();
@ -143,9 +156,6 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
context.writeExpr(context.getArgument(0), context.getPrecedence());
}
break;
case "function":
generateFunction(context);
break;
case "unwrapString":
writer.append("$rt_str(");
context.writeExpr(context.getArgument(0), Precedence.min());
@ -200,17 +210,6 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
}
}
private void generateFunction(InjectorContext context) throws IOException {
SourceWriter writer = context.getWriter();
writer.append("(function($instance,").ws().append("$property)").ws().append("{").ws()
.append("return function()").ws().append("{").indent().softNewLine();
writer.append("return $instance[$property].apply($instance,").ws().append("arguments);").softNewLine();
writer.outdent().append("};})(");
context.writeExpr(context.getArgument(0));
writer.append(",").ws();
context.writeExpr(context.getArgument(1));
writer.append(")");
}
private void renderProperty(Expr property, InjectorContext context) throws IOException {
SourceWriter writer = context.getWriter();

View File

@ -120,7 +120,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
BasicBlock basicBlock = program.createBasicBlock();
List<Instruction> marshallInstructions = new ArrayList<>();
JSValueMarshaller marshaller = new JSValueMarshaller(diagnostics, typeHelper, program,
JSValueMarshaller marshaller = new JSValueMarshaller(diagnostics, typeHelper, innerSource, program,
marshallInstructions);
List<Variable> variablesToPass = new ArrayList<>();
@ -129,7 +129,8 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
}
for (int i = 0; i < method.parameterCount(); ++i) {
Variable var = marshaller.unwrap(callLocation, variablesToPass.get(i), method.parameterType(i));
Variable var = marshaller.unwrapReturnValue(callLocation, variablesToPass.get(i),
method.parameterType(i));
variablesToPass.set(i, var);
}
@ -146,8 +147,8 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
ExitInstruction exit = new ExitInstruction();
if (method.getResultType() != ValueType.VOID) {
invocation.setReceiver(program.createVariable());
exit.setValueToReturn(marshaller.wrap(invocation.getReceiver(), method.getResultType(),
null, false));
exit.setValueToReturn(marshaller.wrapArgument(callLocation, invocation.getReceiver(),
method.getResultType(), false));
basicBlock.addAll(marshallInstructions);
marshallInstructions.clear();
}

View File

@ -18,10 +18,14 @@ package org.teavm.jso.impl;
import java.util.List;
import java.util.function.Function;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.core.JSArray;
import org.teavm.jso.core.JSArrayReader;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.Instruction;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
@ -32,22 +36,66 @@ import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.StringConstantInstruction;
class JSValueMarshaller {
private Diagnostics diagnostics;
private JSTypeHelper typeHelper;
private ClassReaderSource classSource;
private Program program;
private List<Instruction> replacement;
JSValueMarshaller(Diagnostics diagnostics, JSTypeHelper typeHelper, Program program,
List<Instruction> replacement) {
JSValueMarshaller(Diagnostics diagnostics, JSTypeHelper typeHelper, ClassReaderSource classSource,
Program program, List<Instruction> replacement) {
this.diagnostics = diagnostics;
this.typeHelper = typeHelper;
this.classSource = classSource;
this.program = program;
this.replacement = replacement;
}
public Variable wrap(Variable var, ValueType type, TextLocation location, boolean byRef) {
Variable wrapArgument(CallLocation location, Variable var, ValueType type, boolean byRef) {
if (type instanceof ValueType.Object) {
String className = ((ValueType.Object) type).getClassName();
ClassReader cls = classSource.get(className);
if (cls.getAnnotations().get(JSFunctor.class.getName()) != null) {
return wrapFunctor(location, var, cls);
}
}
return wrap(var, type, location.getSourceLocation(), byRef);
}
boolean isProperFunctor(ClassReader type) {
if (!type.hasModifier(ElementModifier.INTERFACE)) {
return false;
}
return type.getMethods().stream()
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
.count() == 1;
}
private Variable wrapFunctor(CallLocation location, Variable var, ClassReader type) {
if (!isProperFunctor(type)) {
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
return var;
}
String name = type.getMethods().stream()
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
.findFirst().get().getName();
Variable functor = program.createVariable();
Variable nameVar = addStringWrap(addString(name, location.getSourceLocation()), location.getSourceLocation());
InvokeInstruction insn = new InvokeInstruction();
insn.setType(InvocationType.SPECIAL);
insn.setMethod(new MethodReference(JS.class, "function", JSObject.class, JSObject.class, JSObject.class));
insn.setReceiver(functor);
insn.getArguments().add(var);
insn.getArguments().add(nameVar);
insn.setLocation(location.getSourceLocation());
replacement.add(insn);
return functor;
}
Variable wrap(Variable var, ValueType type, TextLocation location, boolean byRef) {
if (byRef) {
InvokeInstruction insn = new InvokeInstruction();
insn.setMethod(new MethodReference(JS.class, "arrayData", Object.class, JSObject.class));
@ -169,6 +217,17 @@ class JSValueMarshaller {
return new MethodReference(JS.class, "arrayWrapper", Function.class);
}
Variable unwrapReturnValue(CallLocation location, Variable var, ValueType type) {
if (type instanceof ValueType.Object) {
String className = ((ValueType.Object) type).getClassName();
ClassReader cls = classSource.get(className);
if (cls.getAnnotations().get(JSFunctor.class.getName()) != null) {
return unwrapFunctor(location, var, cls);
}
}
return unwrap(location, var, type);
}
Variable unwrap(CallLocation location, Variable var, ValueType type) {
if (type instanceof ValueType.Primitive) {
switch (((ValueType.Primitive) type).getKind()) {
@ -402,4 +461,40 @@ class JSValueMarshaller {
replacement.add(insn);
return result;
}
private Variable unwrapFunctor(CallLocation location, Variable var, ClassReader type) {
if (!isProperFunctor(type)) {
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
return var;
}
String name = type.getMethods().stream()
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
.findFirst().get().getName();
Variable functor = program.createVariable();
Variable nameVar = addStringWrap(addString(name, location.getSourceLocation()), location.getSourceLocation());
InvokeInstruction insn = new InvokeInstruction();
insn.setType(InvocationType.SPECIAL);
insn.setMethod(new MethodReference(JS.class, "functionAsObject", JSObject.class, JSObject.class,
JSObject.class));
insn.setReceiver(functor);
insn.getArguments().add(var);
insn.getArguments().add(nameVar);
insn.setLocation(location.getSourceLocation());
replacement.add(insn);
return functor;
}
Variable addStringWrap(Variable var, TextLocation location) {
return wrap(var, ValueType.object("java.lang.String"), location, false);
}
Variable addString(String str, TextLocation location) {
Variable var = program.createVariable();
StringConstantInstruction nameInsn = new StringConstantInstruction();
nameInsn.setReceiver(var);
nameInsn.setConstant(str);
nameInsn.setLocation(location);
replacement.add(nameInsn);
return var;
}
}

View File

@ -15,7 +15,8 @@
*/
package org.teavm.jso.test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.jso.JSBody;
@ -33,6 +34,11 @@ public class FunctorTest {
assertEquals("(5)", testMethod((a, b) -> a + b, 2, 3));
}
@Test
public void functorParamsMarshaled() {
assertEquals("(q,w)", testMethod((a, b) -> a + "," + b, "q", "w"));
}
@Test
public void functorIdentityPreserved() {
JSBiFunction javaFunction = (a, b) -> a + b;
@ -65,9 +71,36 @@ public class FunctorTest {
assertEquals("baz_ok", wp.propbaz());
}
@Test
public void functorPassedBack() {
JSBiFunction function = getBiFunction();
assertEquals(23042, function.foo(23, 42));
}
@Test
public void functorParamsMarshaledBack() {
JSStringBiFunction function = getStringBiFunction();
assertEquals("q,w", function.foo("q", "w"));
}
@JSBody(params = { "f", "a", "b" }, script = "return '(' + f(a, b) + ')';")
private static native String testMethod(JSBiFunction f, int a, int b);
@JSBody(params = { "f", "a", "b" }, script = "return '(' + f(a, b) + ')';")
private static native String testMethod(JSStringBiFunction f, String a, String b);
@JSBody(script = ""
+ "return function(a, b) {"
+ "return a * 1000 + b;"
+ "};")
private static native JSBiFunction getBiFunction();
@JSBody(script = ""
+ "return function(a, b) {"
+ "return a + ',' + b;"
+ "};")
private static native JSStringBiFunction getStringBiFunction();
@JSBody(params = "f", script = "return f;")
private static native JSObject getFunction(JSBiFunction f);
@ -79,6 +112,11 @@ public class FunctorTest {
int foo(int a, int b);
}
@JSFunctor
interface JSStringBiFunction extends JSObject {
String foo(String a, String b);
}
@JSFunctor
interface JSFunctionWithDefaultMethod extends JSObject {
int foo(int a);