From 2992c6e40699344eb9d28fd600e7ed4cfc3a6ea3 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 11 Jun 2017 00:15:41 +0300 Subject: [PATCH] Wrap function by an object if returning value of native method is a JSFunctor interface. See #280 --- .../src/main/java/org/teavm/jso/impl/JS.java | 4 + .../org/teavm/jso/impl/JSClassProcessor.java | 98 ++++------------- .../org/teavm/jso/impl/JSNativeGenerator.java | 27 +++-- .../jso/impl/JSObjectClassTransformer.java | 9 +- .../org/teavm/jso/impl/JSValueMarshaller.java | 101 +++++++++++++++++- .../java/org/teavm/jso/test/FunctorTest.java | 40 ++++++- 6 files changed, 180 insertions(+), 99 deletions(-) diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java index 718bd8476..19c20b920 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java @@ -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); } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java index 32b2e19ef..29e0ee7a2 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java @@ -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 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) { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java index 25c603de3..41fe6346a 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java @@ -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(); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java index 5ab1de62a..89a55902b 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java @@ -120,7 +120,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { BasicBlock basicBlock = program.createBasicBlock(); List marshallInstructions = new ArrayList<>(); - JSValueMarshaller marshaller = new JSValueMarshaller(diagnostics, typeHelper, program, + JSValueMarshaller marshaller = new JSValueMarshaller(diagnostics, typeHelper, innerSource, program, marshallInstructions); List 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(); } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java index fb115ae6b..59ce8caaa 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java @@ -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 replacement; - JSValueMarshaller(Diagnostics diagnostics, JSTypeHelper typeHelper, Program program, - List replacement) { + JSValueMarshaller(Diagnostics diagnostics, JSTypeHelper typeHelper, ClassReaderSource classSource, + Program program, List 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; + } } diff --git a/tests/src/test/java/org/teavm/jso/test/FunctorTest.java b/tests/src/test/java/org/teavm/jso/test/FunctorTest.java index 5afc3f5ad..95d5b6e80 100644 --- a/tests/src/test/java/org/teavm/jso/test/FunctorTest.java +++ b/tests/src/test/java/org/teavm/jso/test/FunctorTest.java @@ -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);