Add JSByRef annotation to specify which parameters to pass to JS by reference

This commit is contained in:
Alexey Andreev 2017-01-23 23:10:35 +03:00
parent 8967f423eb
commit c1cddc5a71
7 changed files with 175 additions and 76 deletions

View File

@ -127,7 +127,7 @@ public @interface JSBody {
/** /**
* <p>How method parameters are named inside JavaScript implementation.</p> * <p>How method parameters are named inside JavaScript implementation.</p>
*/ */
String[] params(); String[] params() default {};
/** /**
* <p>JavaScript code.</p> * <p>JavaScript code.</p>

View File

@ -0,0 +1,31 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.jso;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p>Marks parameters of JavaScript methods that should be passed by reference.
* This annotation is only applicable to parameters of array type. More specifically:
* to: byte[], short[], char[], int[], float[], double[] or T[], where T is JSObject.</p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface JSByRef {
}

View File

@ -32,6 +32,9 @@ final class JS {
private JS() { private JS() {
} }
@InjectedBy(JSNativeGenerator.class)
public static native JSObject arrayData(Object array);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(byte value); public static native JSObject wrap(byte value);

View File

@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import org.mozilla.javascript.CompilerEnvirons; import org.mozilla.javascript.CompilerEnvirons;
@ -36,6 +37,7 @@ import org.teavm.cache.NoCache;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.interop.Sync; import org.teavm.interop.Sync;
import org.teavm.jso.JSBody; import org.teavm.jso.JSBody;
import org.teavm.jso.JSByRef;
import org.teavm.jso.JSFunctor; import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSIndexer; import org.teavm.jso.JSIndexer;
import org.teavm.jso.JSMethod; import org.teavm.jso.JSMethod;
@ -44,6 +46,7 @@ import org.teavm.jso.JSProperty;
import org.teavm.jso.core.JSArray; import org.teavm.jso.core.JSArray;
import org.teavm.jso.core.JSArrayReader; import org.teavm.jso.core.JSArrayReader;
import org.teavm.model.AccessLevel; import org.teavm.model.AccessLevel;
import org.teavm.model.AnnotationContainerReader;
import org.teavm.model.AnnotationHolder; import org.teavm.model.AnnotationHolder;
import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationReader;
import org.teavm.model.AnnotationValue; import org.teavm.model.AnnotationValue;
@ -180,22 +183,22 @@ class JSClassProcessor {
} }
} }
private MethodReader getOverridenMethod(MethodReader finalMethod) { private MethodReader getOverriddenMethod(MethodReader finalMethod) {
MethodReference ref = finalMethod.getReference(); MethodReference ref = finalMethod.getReference();
if (!overridenMethodCache.containsKey(ref)) { if (!overridenMethodCache.containsKey(ref)) {
overridenMethodCache.put(ref, findOverridenMethod(finalMethod.getOwnerName(), finalMethod)); overridenMethodCache.put(ref, findOverriddenMethod(finalMethod.getOwnerName(), finalMethod));
} }
return overridenMethodCache.get(ref); return overridenMethodCache.get(ref);
} }
private MethodReader findOverridenMethod(String className, MethodReader finalMethod) { private MethodReader findOverriddenMethod(String className, MethodReader finalMethod) {
if (finalMethod.getName().equals("<init>")) { if (finalMethod.getName().equals("<init>")) {
return null; return null;
} }
return classSource.getAncestors(className) return classSource.getAncestors(className)
.skip(1) .skip(1)
.map(cls -> cls.getMethod(finalMethod.getDescriptor())) .map(cls -> cls.getMethod(finalMethod.getDescriptor()))
.filter(method -> method != null) .filter(Objects::nonNull)
.findFirst() .findFirst()
.orElse(null); .orElse(null);
} }
@ -302,7 +305,7 @@ class JSClassProcessor {
} }
if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) { if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
MethodReader overridden = getOverridenMethod(method); MethodReader overridden = getOverriddenMethod(method);
if (overridden != null) { if (overridden != null) {
diagnostics.error(callLocation, "JS final method {{m0}} overrides {{m1}}. " diagnostics.error(callLocation, "JS final method {{m0}} overrides {{m1}}. "
+ "Overriding final method of overlay types is prohibited.", + "Overriding final method of overlay types is prohibited.",
@ -329,16 +332,8 @@ class JSClassProcessor {
private boolean processJSBodyInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke, private boolean processJSBodyInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke,
MethodHolder methodToProcess) { MethodHolder methodToProcess) {
boolean valid = true; boolean[] byRefParams = new boolean[method.parameterCount()];
for (int i = 0; i < method.parameterCount(); ++i) { boolean valid = validateSignature(method, callLocation, byRefParams);
ValueType arg = method.parameterType(i);
if (!typeHelper.isSupportedType(arg)) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
+ " declaration. Its parameter #" + (i + 1) + " has invalid type {{t1}}", invoke.getMethod(),
arg);
valid = false;
}
}
if (invoke.getInstance() != null) { if (invoke.getInstance() != null) {
if (!typeHelper.isSupportedType(ValueType.object(method.getOwnerName()))) { if (!typeHelper.isSupportedType(ValueType.object(method.getOwnerName()))) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method " diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
@ -347,15 +342,6 @@ class JSClassProcessor {
valid = false; valid = false;
} }
} }
if (method.getResultType() != ValueType.VOID && !typeHelper.isSupportedType(method.getResultType())) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
+ " declaration, since it returns invalid type {{t1}}", invoke.getMethod(),
method.getResultType());
valid = false;
}
if (!valid) {
return false;
}
requireJSBody(diagnostics, method); requireJSBody(diagnostics, method);
MethodReference delegate = repository.methodMap.get(method.getReference()); MethodReference delegate = repository.methodMap.get(method.getReference());
@ -372,12 +358,13 @@ class JSClassProcessor {
newInvoke.setReceiver(result); newInvoke.setReceiver(result);
newInvoke.setLocation(invoke.getLocation()); newInvoke.setLocation(invoke.getLocation());
if (invoke.getInstance() != null) { if (invoke.getInstance() != null) {
Variable arg = wrapArgument(callLocation, invoke.getInstance(), ValueType.object(method.getOwnerName())); Variable arg = wrapArgument(callLocation, invoke.getInstance(),
ValueType.object(method.getOwnerName()), false);
newInvoke.getArguments().add(arg); newInvoke.getArguments().add(arg);
} }
for (int k = 0; k < invoke.getArguments().size(); ++k) { for (int i = 0; i < invoke.getArguments().size(); ++i) {
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(k), Variable arg = wrapArgument(callLocation, invoke.getArguments().get(i),
method.parameterType(k)); method.parameterType(i), byRefParams[i]);
newInvoke.getArguments().add(arg); newInvoke.getArguments().add(arg);
} }
replacement.add(newInvoke); replacement.add(newInvoke);
@ -420,7 +407,7 @@ class JSClassProcessor {
propertyName = cutPrefix(method.getName(), 3); propertyName = cutPrefix(method.getName(), 3);
} }
Variable wrapped = wrapArgument(callLocation, invoke.getArguments().get(0), Variable wrapped = wrapArgument(callLocation, invoke.getArguments().get(0),
method.parameterType(0)); method.parameterType(0), false);
addPropertySet(propertyName, invoke.getInstance(), wrapped, invoke.getLocation()); addPropertySet(propertyName, invoke.getInstance(), wrapped, invoke.getLocation());
return true; return true;
} }
@ -433,7 +420,7 @@ class JSClassProcessor {
if (isProperGetIndexer(method.getDescriptor())) { if (isProperGetIndexer(method.getDescriptor())) {
Variable result = invoke.getReceiver() != null ? program.createVariable() : null; Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
addIndexerGet(invoke.getInstance(), wrap(invoke.getArguments().get(0), addIndexerGet(invoke.getInstance(), wrap(invoke.getArguments().get(0),
method.parameterType(0), invoke.getLocation()), result, invoke.getLocation()); method.parameterType(0), invoke.getLocation(), false), result, invoke.getLocation());
if (result != null) { if (result != null) {
result = unwrap(callLocation, result, method.getResultType()); result = unwrap(callLocation, result, method.getResultType());
copyVar(result, invoke.getReceiver(), invoke.getLocation()); copyVar(result, invoke.getReceiver(), invoke.getLocation());
@ -442,9 +429,9 @@ class JSClassProcessor {
} }
if (isProperSetIndexer(method.getDescriptor())) { if (isProperSetIndexer(method.getDescriptor())) {
Variable index = wrap(invoke.getArguments().get(0), method.parameterType(0), Variable index = wrap(invoke.getArguments().get(0), method.parameterType(0),
invoke.getLocation()); invoke.getLocation(), false);
Variable value = wrap(invoke.getArguments().get(1), method.parameterType(1), Variable value = wrap(invoke.getArguments().get(1), method.parameterType(1),
invoke.getLocation()); invoke.getLocation(), false);
addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation()); addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation());
return true; return true;
} }
@ -453,6 +440,36 @@ class JSClassProcessor {
return false; return false;
} }
private boolean validateSignature(MethodReader method, CallLocation callLocation, boolean[] byRefParams) {
if (method.getResultType() != ValueType.VOID && !typeHelper.isSupportedType(method.getResultType())) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
+ "declaration", method.getReference());
return false;
}
ValueType[] parameterTypes = method.getParameterTypes();
AnnotationContainerReader[] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0; i < parameterTypes.length; i++) {
ValueType paramType = parameterTypes[i];
if (!typeHelper.isSupportedType(paramType)) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
+ "declaration: its " + (i + 1) + "th argument has wrong type", method.getReference());
return false;
}
if (parameterAnnotations[i].get(JSByRef.class.getName()) != null) {
if (!typeHelper.isSupportedByRefType(paramType)) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
+ "declaration: its " + (i + 1) + "th argument is declared as JSByRef, "
+ "which has incompatible type", method.getReference());
return false;
}
byRefParams[i] = true;
}
}
return true;
}
private boolean processMethod(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) { private boolean processMethod(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
String name = method.getName(); String name = method.getName();
@ -463,19 +480,11 @@ class JSClassProcessor {
name = redefinedMethodName.getString(); name = redefinedMethodName.getString();
} }
} }
if (method.getResultType() != ValueType.VOID && !typeHelper.isSupportedType(method.getResultType())) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
+ "declaration", invoke.getMethod());
return false;
}
for (ValueType arg : method.getParameterTypes()) { boolean[] byRefParams = new boolean[method.parameterCount() + 1];
if (!typeHelper.isSupportedType(arg)) { if (!validateSignature(method, callLocation, byRefParams)) {
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
+ " declaration", invoke.getMethod());
return false; return false;
} }
}
Variable result = invoke.getReceiver() != null ? program.createVariable() : null; Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
InvokeInstruction newInvoke = new InvokeInstruction(); InvokeInstruction newInvoke = new InvokeInstruction();
@ -488,9 +497,9 @@ class JSClassProcessor {
newInvoke.getArguments().add(addStringWrap(addString(name, invoke.getLocation()), newInvoke.getArguments().add(addStringWrap(addString(name, invoke.getLocation()),
invoke.getLocation())); invoke.getLocation()));
newInvoke.setLocation(invoke.getLocation()); newInvoke.setLocation(invoke.getLocation());
for (int k = 0; k < invoke.getArguments().size(); ++k) { for (int i = 0; i < invoke.getArguments().size(); ++i) {
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(k), Variable arg = wrapArgument(callLocation, invoke.getArguments().get(i),
method.parameterType(k)); method.parameterType(i), byRefParams[i]);
newInvoke.getArguments().add(arg); newInvoke.getArguments().add(arg);
} }
replacement.add(newInvoke); replacement.add(newInvoke);
@ -515,7 +524,8 @@ class JSClassProcessor {
// validate parameter names // validate parameter names
AnnotationReader bodyAnnot = methodToProcess.getAnnotations().get(JSBody.class.getName()); AnnotationReader bodyAnnot = methodToProcess.getAnnotations().get(JSBody.class.getName());
int jsParamCount = bodyAnnot.getValue("params").getList().size(); AnnotationValue paramsValue = bodyAnnot.getValue("params");
int jsParamCount = paramsValue != null ? paramsValue.getList().size() : 0;
if (methodToProcess.parameterCount() != jsParamCount) { if (methodToProcess.parameterCount() != jsParamCount) {
diagnostics.error(location, "JSBody method {{m0}} declares " + methodToProcess.parameterCount() diagnostics.error(location, "JSBody method {{m0}} declares " + methodToProcess.parameterCount()
+ " parameters, but annotation specifies " + jsParamCount, methodToProcess.getReference()); + " parameters, but annotation specifies " + jsParamCount, methodToProcess.getReference());
@ -553,9 +563,9 @@ class JSClassProcessor {
MethodReference proxyMethod = new MethodReference(methodToProcess.getOwnerName(), MethodReference proxyMethod = new MethodReference(methodToProcess.getOwnerName(),
methodToProcess.getName() + "$js_body$_" + methodIndexGenerator++, proxyParamTypes); methodToProcess.getName() + "$js_body$_" + methodIndexGenerator++, proxyParamTypes);
String script = bodyAnnot.getValue("script").getString(); String script = bodyAnnot.getValue("script").getString();
String[] parameterNames = bodyAnnot.getValue("params").getList().stream() String[] parameterNames = paramsValue != null ? paramsValue.getList().stream()
.map(AnnotationValue::getString) .map(AnnotationValue::getString)
.toArray(String[]::new); .toArray(String[]::new) : new String[0];
// Parse JS script // Parse JS script
TeaVMErrorReporter errorReporter = new TeaVMErrorReporter(diagnostics, TeaVMErrorReporter errorReporter = new TeaVMErrorReporter(diagnostics,
@ -657,7 +667,7 @@ class JSClassProcessor {
ExitInstruction exit = new ExitInstruction(); ExitInstruction exit = new ExitInstruction();
if (insn.getReceiver() != null) { if (insn.getReceiver() != null) {
replacement.clear(); replacement.clear();
exit.setValueToReturn(wrap(insn.getReceiver(), callee.getResultType(), null)); exit.setValueToReturn(wrap(insn.getReceiver(), callee.getResultType(), null, false));
block.addAll(replacement); block.addAll(replacement);
} }
block.add(exit); block.add(exit);
@ -725,7 +735,7 @@ class JSClassProcessor {
} }
private Variable addStringWrap(Variable var, TextLocation location) { private Variable addStringWrap(Variable var, TextLocation location) {
return wrap(var, ValueType.object("java.lang.String"), location); return wrap(var, ValueType.object("java.lang.String"), location, false);
} }
private Variable addString(String str, TextLocation location) { private Variable addString(String str, TextLocation location) {
@ -972,7 +982,7 @@ class JSClassProcessor {
return result; return result;
} }
private Variable wrapArgument(CallLocation location, Variable var, ValueType type) { private Variable wrapArgument(CallLocation location, Variable var, ValueType type, boolean byRef) {
if (type instanceof ValueType.Object) { if (type instanceof ValueType.Object) {
String className = ((ValueType.Object) type).getClassName(); String className = ((ValueType.Object) type).getClassName();
ClassReader cls = classSource.get(className); ClassReader cls = classSource.get(className);
@ -980,7 +990,7 @@ class JSClassProcessor {
return wrapFunctor(location, var, cls); return wrapFunctor(location, var, cls);
} }
} }
return wrap(var, type, location.getSourceLocation()); return wrap(var, type, location.getSourceLocation(), byRef);
} }
private boolean isProperFunctor(ClassReader type) { private boolean isProperFunctor(ClassReader type) {
@ -1006,7 +1016,17 @@ class JSClassProcessor {
return functor; return functor;
} }
private Variable wrap(Variable var, ValueType type, TextLocation location) { private 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));
insn.setReceiver(program.createVariable());
insn.setType(InvocationType.SPECIAL);
insn.getArguments().add(var);
replacement.add(insn);
return insn.getReceiver();
}
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.String")) { if (!className.equals("java.lang.String")) {

View File

@ -35,10 +35,6 @@ import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
/**
*
* @author Alexey Andreev
*/
public class JSNativeGenerator implements Injector, DependencyPlugin, Generator { public class JSNativeGenerator implements Injector, DependencyPlugin, Generator {
@Override @Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef)
@ -76,6 +72,10 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
public void generate(InjectorContext context, MethodReference methodRef) throws IOException { public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
SourceWriter writer = context.getWriter(); SourceWriter writer = context.getWriter();
switch (methodRef.getName()) { switch (methodRef.getName()) {
case "arrayData":
context.writeExpr(context.getArgument(0));
writer.append(".data");
break;
case "get": case "get":
context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS); context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS);
renderProperty(context.getArgument(1), context); renderProperty(context.getArgument(1), context);

View File

@ -23,10 +23,6 @@ import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
/**
*
* @author Alexey Andreev
*/
class JSTypeHelper { class JSTypeHelper {
private ClassReaderSource classSource; private ClassReaderSource classSource;
private Map<String, Boolean> knownJavaScriptClasses = new HashMap<>(); private Map<String, Boolean> knownJavaScriptClasses = new HashMap<>();
@ -38,21 +34,12 @@ class JSTypeHelper {
} }
public boolean isJavaScriptClass(String className) { public boolean isJavaScriptClass(String className) {
Boolean known = knownJavaScriptClasses.get(className); return knownJavaScriptClasses.computeIfAbsent(className, k -> examineIfJavaScriptClass(className));
if (known == null) {
known = examineIfJavaScriptClass(className);
knownJavaScriptClasses.put(className, known);
}
return known;
} }
public boolean isJavaScriptImplementation(String className) { public boolean isJavaScriptImplementation(String className) {
Boolean known = knownJavaScriptImplementations.get(className); return knownJavaScriptImplementations
if (known == null) { .computeIfAbsent(className, k -> examineIfJavaScriptImplementation(className));
known = examineIfJavaScriptImplementation(className);
knownJavaScriptImplementations.put(className, known);
}
return known;
} }
private boolean examineIfJavaScriptClass(String className) { private boolean examineIfJavaScriptClass(String className) {
@ -104,4 +91,28 @@ class JSTypeHelper {
return false; return false;
} }
} }
public boolean isSupportedByRefType(ValueType type) {
if (!(type instanceof ValueType.Array)) {
return false;
}
ValueType itemType = ((ValueType.Array) type).getItemType();
if (itemType instanceof ValueType.Primitive) {
switch (((ValueType.Primitive) itemType).getKind()) {
case BYTE:
case SHORT:
case CHARACTER:
case INTEGER:
case FLOAT:
case DOUBLE:
return true;
default:
return false;
}
} else if (itemType instanceof ValueType.Object) {
return isJavaScriptClass(((ValueType.Object) itemType).getClassName());
} else {
return false;
}
}
} }

View File

@ -19,6 +19,7 @@ import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.teavm.jso.JSBody; import org.teavm.jso.JSBody;
import org.teavm.jso.JSByRef;
import org.teavm.jso.JSObject; import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty; import org.teavm.jso.JSProperty;
import org.teavm.jso.core.JSString; import org.teavm.jso.core.JSString;
@ -142,13 +143,26 @@ public class ConversionTest {
assertEquals(23, array[0]); assertEquals(23, array[0]);
} }
@Test
public void passesArrayByRef() {
int[] array = { 23, 42 };
mutateByRef(array);
assertEquals(24, array[0]);
assertEquals(43, array[1]);
createByRefMutator().mutate(array);
assertEquals(25, array[0]);
assertEquals(44, array[1]);
}
@JSBody(params = { "a", "b", "c", "d", "e", "f", "g", "h" }, script = "" @JSBody(params = { "a", "b", "c", "d", "e", "f", "g", "h" }, script = ""
+ "return '' + a + ':' + b + ':' + c + ':' + d + ':' + e + ':' + f.toFixed(1) + ':'" + "return '' + a + ':' + b + ':' + c + ':' + d + ':' + e + ':' + f.toFixed(1) + ':'"
+ "+ g.toFixed(1) + ':' + h;") + "+ g.toFixed(1) + ':' + h;")
private static native String combinePrimitives(boolean a, byte b, short c, char d, int e, float f, double g, private static native String combinePrimitives(boolean a, byte b, short c, char d, int e, float f, double g,
String h); String h);
@JSBody(params = {}, script = "return { a : true, b : 2, c : 3, d : 64, e : 4, f : 5.5, g : 6.5, h : 'foo' };") @JSBody(script = "return { a : true, b : 2, c : 3, d : 64, e : 4, f : 5.5, g : 6.5, h : 'foo' };")
private static native Primitives getPrimitives(); private static native Primitives getPrimitives();
@JSBody(params = { "a", "b", "c", "d", "e", "f", "g", "h" }, script = "" @JSBody(params = { "a", "b", "c", "d", "e", "f", "g", "h" }, script = ""
@ -300,4 +314,24 @@ public class ConversionTest {
@JSBody(params = "array", script = "array[0]++; return array[0];") @JSBody(params = "array", script = "array[0]++; return array[0];")
private static native int mutate(int[] array); private static native int mutate(int[] array);
@JSBody(params = "array", script = ""
+ "for (var i = 0; i < array.length; ++i) {"
+ "array[i]++;"
+ "}")
private static native void mutateByRef(@JSByRef int[] array);
private interface ByRefMutator extends JSObject {
void mutate(@JSByRef int[] array);
}
@JSBody(script = ""
+ "return {"
+ "mutate : function(array) {"
+ "for (var i = 0; i < array.length; ++i) {"
+ "array[i]++;"
+ "}"
+ "}"
+ "};")
private static native ByRefMutator createByRefMutator();
} }