mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
jso: support varargs in exported classes and modules
This commit is contained in:
parent
abb1ea0070
commit
931f0f1f4a
|
@ -15,15 +15,15 @@
|
|||
*/
|
||||
package org.teavm.classlib.impl;
|
||||
|
||||
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
|
||||
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
|
||||
import org.teavm.backend.javascript.spi.MethodContributor;
|
||||
import org.teavm.backend.javascript.spi.MethodContributorContext;
|
||||
import org.teavm.model.ClassReader;
|
||||
import org.teavm.model.MethodReference;
|
||||
import org.teavm.platform.PlatformAnnotationProvider;
|
||||
|
||||
public class AnnotationVirtualMethods implements VirtualMethodContributor {
|
||||
public class AnnotationVirtualMethods implements MethodContributor {
|
||||
@Override
|
||||
public boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef) {
|
||||
public boolean isContributing(MethodContributorContext context, MethodReference methodRef) {
|
||||
ClassReader cls = context.getClassSource().get(methodRef.getClassName());
|
||||
if (cls == null || !cls.getInterfaces().contains(PlatformAnnotationProvider.class.getName())) {
|
||||
return false;
|
||||
|
|
|
@ -60,8 +60,8 @@ import org.teavm.backend.javascript.spi.GeneratedBy;
|
|||
import org.teavm.backend.javascript.spi.Generator;
|
||||
import org.teavm.backend.javascript.spi.InjectedBy;
|
||||
import org.teavm.backend.javascript.spi.Injector;
|
||||
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
|
||||
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
|
||||
import org.teavm.backend.javascript.spi.MethodContributor;
|
||||
import org.teavm.backend.javascript.spi.MethodContributorContext;
|
||||
import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory;
|
||||
import org.teavm.cache.EmptyMethodNodeCache;
|
||||
import org.teavm.cache.MethodNodeCache;
|
||||
|
@ -122,7 +122,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
|
|||
private DebugInformationEmitter debugEmitter;
|
||||
private MethodNodeCache astCache = EmptyMethodNodeCache.INSTANCE;
|
||||
private final Set<MethodReference> asyncMethods = new HashSet<>();
|
||||
private List<VirtualMethodContributor> customVirtualMethods = new ArrayList<>();
|
||||
private List<MethodContributor> customVirtualMethods = new ArrayList<>();
|
||||
private List<MethodContributor> forcedFunctionMethods = new ArrayList<>();
|
||||
private boolean strict;
|
||||
private BoundCheckInsertion boundCheckInsertion = new BoundCheckInsertion();
|
||||
private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY);
|
||||
|
@ -366,11 +367,13 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
|
|||
debugEmitterToUse = new DummyDebugInformationEmitter();
|
||||
}
|
||||
|
||||
var virtualMethodContributorContext = new VirtualMethodContributorContextImpl(classes);
|
||||
var methodContributorContext = new MethodContributorContextImpl(classes);
|
||||
RenderingContext renderingContext = new RenderingContext(debugEmitterToUse,
|
||||
controller.getUnprocessedClassSource(), classes,
|
||||
controller.getClassLoader(), controller.getServices(), controller.getProperties(), naming,
|
||||
controller.getDependencyInfo(), m -> isVirtual(virtualMethodContributorContext, m),
|
||||
controller.getDependencyInfo(),
|
||||
m -> isVirtual(methodContributorContext, m),
|
||||
m -> isForcedFunction(methodContributorContext, m),
|
||||
controller.getClassInitializerInfo(), strict
|
||||
) {
|
||||
@Override
|
||||
|
@ -804,31 +807,45 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void addVirtualMethods(VirtualMethodContributor virtualMethods) {
|
||||
public void addVirtualMethods(MethodContributor virtualMethods) {
|
||||
customVirtualMethods.add(virtualMethods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addForcedFunctionMethods(MethodContributor forcedFunctionMethods) {
|
||||
this.forcedFunctionMethods.add(forcedFunctionMethods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isVirtual(VirtualMethodContributorContext context, MethodReference method) {
|
||||
private boolean isVirtual(MethodContributorContext context, MethodReference method) {
|
||||
if (controller.isVirtual(method)) {
|
||||
return true;
|
||||
}
|
||||
for (VirtualMethodContributor predicate : customVirtualMethods) {
|
||||
if (predicate.isVirtual(context, method)) {
|
||||
for (MethodContributor predicate : customVirtualMethods) {
|
||||
if (predicate.isContributing(context, method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static class VirtualMethodContributorContextImpl implements VirtualMethodContributorContext {
|
||||
private boolean isForcedFunction(MethodContributorContext context, MethodReference method) {
|
||||
for (var predicate : forcedFunctionMethods) {
|
||||
if (predicate.isContributing(context, method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class MethodContributorContextImpl implements MethodContributorContext {
|
||||
private ClassReaderSource classSource;
|
||||
|
||||
VirtualMethodContributorContextImpl(ClassReaderSource classSource) {
|
||||
MethodContributorContextImpl(ClassReaderSource classSource) {
|
||||
this.classSource = classSource;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ package org.teavm.backend.javascript;
|
|||
import java.util.function.Function;
|
||||
import org.teavm.backend.javascript.spi.Generator;
|
||||
import org.teavm.backend.javascript.spi.Injector;
|
||||
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
|
||||
import org.teavm.backend.javascript.spi.MethodContributor;
|
||||
import org.teavm.model.MethodReference;
|
||||
import org.teavm.vm.spi.RendererListener;
|
||||
import org.teavm.vm.spi.TeaVMHostExtension;
|
||||
|
@ -34,5 +34,7 @@ public interface TeaVMJavaScriptHost extends TeaVMHostExtension {
|
|||
|
||||
void add(RendererListener listener);
|
||||
|
||||
void addVirtualMethods(VirtualMethodContributor virtualMethods);
|
||||
void addVirtualMethods(MethodContributor virtualMethods);
|
||||
|
||||
void addForcedFunctionMethods(MethodContributor forcedFunctionMethods);
|
||||
}
|
||||
|
|
|
@ -93,12 +93,16 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
|
|||
}
|
||||
|
||||
public void renderParameters(MethodReference reference, Set<ElementModifier> modifiers) {
|
||||
renderParameters(reference, modifiers, false);
|
||||
}
|
||||
|
||||
public void renderParameters(MethodReference reference, Set<ElementModifier> modifiers, boolean forceParentheses) {
|
||||
int startParam = 0;
|
||||
if (modifiers.contains(ElementModifier.STATIC)) {
|
||||
startParam = 1;
|
||||
}
|
||||
var count = reference.parameterCount() - startParam + 1;
|
||||
if (count != 1) {
|
||||
if (count != 1 || forceParentheses) {
|
||||
writer.append("(");
|
||||
}
|
||||
for (int i = startParam; i <= reference.parameterCount(); ++i) {
|
||||
|
@ -107,7 +111,7 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
|
|||
}
|
||||
writer.append(statementRenderer.variableName(i));
|
||||
}
|
||||
if (count != 1) {
|
||||
if (count != 1 || forceParentheses) {
|
||||
writer.append(")");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -372,8 +372,13 @@ public class Renderer implements RenderingManager {
|
|||
if (!filterMethod(method)) {
|
||||
continue;
|
||||
}
|
||||
writer.startVariableDeclaration();
|
||||
renderBody(method, decompiler);
|
||||
var isFunction = context.isForcedFunction(method.getReference());
|
||||
if (isFunction) {
|
||||
writer.startFunctionDeclaration();
|
||||
} else {
|
||||
writer.startVariableDeclaration();
|
||||
}
|
||||
renderBody(method, decompiler, isFunction);
|
||||
writer.endDeclaration();
|
||||
if (needsInitializers && !method.hasModifier(ElementModifier.STATIC)
|
||||
&& method.getName().equals("<init>")) {
|
||||
|
@ -381,7 +386,6 @@ public class Renderer implements RenderingManager {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
writer.emitClass(null);
|
||||
}
|
||||
|
||||
|
@ -781,7 +785,12 @@ public class Renderer implements RenderingManager {
|
|||
}
|
||||
|
||||
private void emitVirtualFunctionWrapper(MethodReference method) {
|
||||
if (method.parameterCount() <= 4) {
|
||||
var forced = context.isForcedFunction(method);
|
||||
if (forced) {
|
||||
writer.appendFunction("$rt_wrapFunctionVararg").append("(").appendMethod(method).append(")");
|
||||
return;
|
||||
}
|
||||
if (method.parameterCount() <= 4 && !forced) {
|
||||
writer.appendFunction("$rt_wrapFunction" + method.parameterCount());
|
||||
writer.append("(").appendMethod(method).append(")");
|
||||
return;
|
||||
|
@ -810,22 +819,22 @@ public class Renderer implements RenderingManager {
|
|||
writer.append(");").ws().append("}");
|
||||
}
|
||||
|
||||
private void renderBody(MethodHolder method, Decompiler decompiler) {
|
||||
private void renderBody(MethodHolder method, Decompiler decompiler, boolean isFunction) {
|
||||
MethodReference ref = method.getReference();
|
||||
writer.emitMethod(ref.getDescriptor());
|
||||
|
||||
writer.appendMethod(ref);
|
||||
if (method.hasModifier(ElementModifier.NATIVE)) {
|
||||
renderNativeBody(method, classSource);
|
||||
renderNativeBody(method, classSource, isFunction);
|
||||
} else {
|
||||
renderRegularBody(method, decompiler);
|
||||
renderRegularBody(method, decompiler, isFunction);
|
||||
}
|
||||
|
||||
writer.outdent().append("}");
|
||||
writer.emitMethod(null);
|
||||
}
|
||||
|
||||
private void renderNativeBody(MethodHolder method, ClassReaderSource classes) {
|
||||
private void renderNativeBody(MethodHolder method, ClassReaderSource classes, boolean isFunction) {
|
||||
var reference = method.getReference();
|
||||
var generator = generators.get(reference);
|
||||
if (generator == null) {
|
||||
|
@ -841,7 +850,7 @@ public class Renderer implements RenderingManager {
|
|||
}
|
||||
|
||||
var async = asyncMethods.contains(reference);
|
||||
renderMethodPrologue(reference, method.getModifiers());
|
||||
renderMethodPrologue(reference, method.getModifiers(), isFunction);
|
||||
methodBodyRenderer.renderNative(generator, async, reference);
|
||||
threadLibraryUsed |= methodBodyRenderer.isThreadLibraryUsed();
|
||||
}
|
||||
|
@ -895,7 +904,7 @@ public class Renderer implements RenderingManager {
|
|||
}
|
||||
}
|
||||
|
||||
private void renderRegularBody(MethodHolder method, Decompiler decompiler) {
|
||||
private void renderRegularBody(MethodHolder method, Decompiler decompiler, boolean isFunction) {
|
||||
MethodReference reference = method.getReference();
|
||||
MethodNode node;
|
||||
var async = asyncMethods.contains(reference);
|
||||
|
@ -907,14 +916,17 @@ public class Renderer implements RenderingManager {
|
|||
}
|
||||
|
||||
methodBodyRenderer.setCurrentMethod(node);
|
||||
renderMethodPrologue(method.getReference(), method.getModifiers());
|
||||
renderMethodPrologue(method.getReference(), method.getModifiers(), isFunction);
|
||||
methodBodyRenderer.render(node, async);
|
||||
threadLibraryUsed |= methodBodyRenderer.isThreadLibraryUsed();
|
||||
}
|
||||
|
||||
private void renderMethodPrologue(MethodReference reference, Set<ElementModifier> modifier) {
|
||||
methodBodyRenderer.renderParameters(reference, modifier);
|
||||
writer.sameLineWs().append("=>").ws().append("{").indent().softNewLine();
|
||||
private void renderMethodPrologue(MethodReference reference, Set<ElementModifier> modifier, boolean isFunction) {
|
||||
methodBodyRenderer.renderParameters(reference, modifier, isFunction);
|
||||
if (!isFunction) {
|
||||
writer.sameLineWs().append("=>");
|
||||
}
|
||||
writer.ws().append("{").indent().softNewLine();
|
||||
}
|
||||
|
||||
private AstCacheEntry decompileRegular(Decompiler decompiler, MethodHolder method) {
|
||||
|
|
|
@ -51,6 +51,7 @@ public abstract class RenderingContext {
|
|||
private NamingStrategy naming;
|
||||
private DependencyInfo dependencyInfo;
|
||||
private Predicate<MethodReference> virtualPredicate;
|
||||
private Predicate<MethodReference> forcedFunctionPredicate;
|
||||
private final Map<String, Integer> stringPoolMap = new HashMap<>();
|
||||
private final List<String> stringPool = new ArrayList<>();
|
||||
private final List<String> readonlyStringPool = Collections.unmodifiableList(stringPool);
|
||||
|
@ -63,7 +64,9 @@ public abstract class RenderingContext {
|
|||
ClassReaderSource initialClassSource, ListableClassReaderSource classSource,
|
||||
ClassLoader classLoader, ServiceRepository services, Properties properties,
|
||||
NamingStrategy naming, DependencyInfo dependencyInfo,
|
||||
Predicate<MethodReference> virtualPredicate, ClassInitializerInfo classInitializerInfo,
|
||||
Predicate<MethodReference> virtualPredicate,
|
||||
Predicate<MethodReference> forcedFunctionPredicate,
|
||||
ClassInitializerInfo classInitializerInfo,
|
||||
boolean strict) {
|
||||
this.debugEmitter = debugEmitter;
|
||||
this.initialClassSource = initialClassSource;
|
||||
|
@ -74,6 +77,7 @@ public abstract class RenderingContext {
|
|||
this.naming = naming;
|
||||
this.dependencyInfo = dependencyInfo;
|
||||
this.virtualPredicate = virtualPredicate;
|
||||
this.forcedFunctionPredicate = forcedFunctionPredicate;
|
||||
this.classInitializerInfo = classInitializerInfo;
|
||||
this.strict = strict;
|
||||
}
|
||||
|
@ -118,6 +122,10 @@ public abstract class RenderingContext {
|
|||
return virtualPredicate.test(method);
|
||||
}
|
||||
|
||||
public boolean isForcedFunction(MethodReference method) {
|
||||
return forcedFunctionPredicate.test(method);
|
||||
}
|
||||
|
||||
public boolean isDynamicInitializer(String className) {
|
||||
return classInitializerInfo.isDynamicInitializer(className);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,6 @@ package org.teavm.backend.javascript.spi;
|
|||
|
||||
import org.teavm.model.MethodReference;
|
||||
|
||||
public interface VirtualMethodContributor {
|
||||
boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef);
|
||||
public interface MethodContributor {
|
||||
boolean isContributing(MethodContributorContext context, MethodReference methodRef);
|
||||
}
|
|
@ -17,6 +17,6 @@ package org.teavm.backend.javascript.spi;
|
|||
|
||||
import org.teavm.model.ClassReaderSource;
|
||||
|
||||
public interface VirtualMethodContributorContext {
|
||||
public interface MethodContributorContext {
|
||||
ClassReaderSource getClassSource();
|
||||
}
|
|
@ -119,4 +119,12 @@ public interface ClassReaderSource {
|
|||
default Optional<Boolean> isSuperType(String superType, String subType) {
|
||||
return ClassReaderSourceHelper.isSuperType(this, superType, subType);
|
||||
}
|
||||
|
||||
default MethodReader getMethod(MethodReference ref) {
|
||||
var cls = get(ref.getClassName());
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
return cls.getMethod(ref.getDescriptor());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,12 @@ let $rt_wrapFunction3 = f => function(p1, p2, p3) {
|
|||
let $rt_wrapFunction4 = f => function(p1, p2, p3, p4) {
|
||||
return f(this, p1, p2, p3, p4);
|
||||
}
|
||||
let $rt_wrapFunctionVararg = f => function() {
|
||||
let array = new teavm_globals.Array();
|
||||
array.push(this);
|
||||
Array.prototype.push.apply(array, arguments);
|
||||
return f.apply(null, array);
|
||||
}
|
||||
let $rt_threadStarter = f => function() {
|
||||
let args = teavm_globals.Array.prototype.slice.apply(arguments);
|
||||
$rt_startThread(function() {
|
||||
|
@ -111,3 +117,5 @@ let $rt_setThread = t => {
|
|||
let $rt_apply = (instance, method, args) => instance[method].apply(instance, args);
|
||||
|
||||
let $rt_apply_topLevel = (method, args) => method.apply(null, args);
|
||||
|
||||
let $rt_skip = (array, count) => count === 0 ? array : Array.prototype.slice.call(array, count);
|
|
@ -716,4 +716,8 @@ final class JS {
|
|||
@InjectedBy(JSNativeInjector.class)
|
||||
@NoSideEffects
|
||||
public static native JSObject throwCCEIfFalse(boolean value, JSObject o);
|
||||
|
||||
@InjectedBy(JSNativeInjector.class)
|
||||
@NoSideEffects
|
||||
public static native JSObject argumentsBeginningAt(int index);
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ import java.util.Map;
|
|||
import java.util.function.Predicate;
|
||||
import org.teavm.backend.javascript.codegen.SourceWriter;
|
||||
import org.teavm.backend.javascript.rendering.RenderingManager;
|
||||
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
|
||||
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
|
||||
import org.teavm.backend.javascript.spi.MethodContributor;
|
||||
import org.teavm.backend.javascript.spi.MethodContributorContext;
|
||||
import org.teavm.jso.JSClass;
|
||||
import org.teavm.model.AnnotationReader;
|
||||
import org.teavm.model.ClassReader;
|
||||
|
@ -35,7 +35,7 @@ import org.teavm.model.MethodReference;
|
|||
import org.teavm.vm.BuildTarget;
|
||||
import org.teavm.vm.spi.RendererListener;
|
||||
|
||||
class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
||||
class JSAliasRenderer implements RendererListener, MethodContributor {
|
||||
private static String variableChars = "abcdefghijklmnopqrstuvwxyz";
|
||||
private SourceWriter writer;
|
||||
private ListableClassReaderSource classSource;
|
||||
|
@ -389,7 +389,7 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef) {
|
||||
public boolean isContributing(MethodContributorContext context, MethodReference methodRef) {
|
||||
ClassReader classReader = context.getClassSource().get(methodRef.getClassName());
|
||||
if (classReader == null) {
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 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.impl;
|
||||
|
||||
import org.teavm.backend.javascript.spi.MethodContributor;
|
||||
import org.teavm.backend.javascript.spi.MethodContributorContext;
|
||||
import org.teavm.model.MethodReference;
|
||||
|
||||
public class JSExportedMethodAsFunction implements MethodContributor {
|
||||
@Override
|
||||
public boolean isContributing(MethodContributorContext context, MethodReference methodRef) {
|
||||
var method = context.getClassSource().getMethod(methodRef);
|
||||
if (method != null) {
|
||||
if (method.getAnnotations().get(JSVararg.class.getName()) != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -131,6 +131,8 @@ final class JSMethods {
|
|||
JSObject.class, boolean.class);
|
||||
public static final MethodReference THROW_CCE_IF_FALSE = new MethodReference(JS.class, "throwCCEIfFalse",
|
||||
boolean.class, JSObject.class, JSObject.class);
|
||||
public static final MethodReference ARGUMENTS_BEGINNING_AT = new MethodReference(JS.class,
|
||||
"argumentsBeginningAt", int.class, JSObject.class);
|
||||
|
||||
public static final ValueType JS_OBJECT = ValueType.object(JSObject.class.getName());
|
||||
public static final ValueType OBJECT = ValueType.object("java.lang.Object");
|
||||
|
|
|
@ -237,6 +237,12 @@ public class JSNativeInjector implements Injector, DependencyPlugin {
|
|||
writer.append(")");
|
||||
break;
|
||||
}
|
||||
case "argumentsBeginningAt": {
|
||||
writer.appendFunction("$rt_skip").append("(arguments,").ws();
|
||||
context.writeExpr(context.getArgument(0), Precedence.min());
|
||||
writer.append(")");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
if (methodRef.getName().startsWith("unwrap")) {
|
||||
|
|
|
@ -48,6 +48,7 @@ public class JSOPlugin implements TeaVMPlugin {
|
|||
jsHost.addInjectorProvider(new GeneratorAnnotationInstaller<>(new JSBodyGenerator(),
|
||||
DynamicInjector.class.getName()));
|
||||
jsHost.addVirtualMethods(aliasRenderer);
|
||||
jsHost.addForcedFunctionMethods(new JSExportedMethodAsFunction());
|
||||
|
||||
JSExceptionsGenerator exceptionsGenerator = new JSExceptionsGenerator();
|
||||
jsHost.add(new MethodReference(JSExceptions.class, "getJavaException", JSObject.class, Throwable.class),
|
||||
|
|
|
@ -49,6 +49,7 @@ import org.teavm.model.Program;
|
|||
import org.teavm.model.ValueType;
|
||||
import org.teavm.model.Variable;
|
||||
import org.teavm.model.instructions.ExitInstruction;
|
||||
import org.teavm.model.instructions.IntegerConstantInstruction;
|
||||
import org.teavm.model.instructions.InvocationType;
|
||||
import org.teavm.model.instructions.InvokeInstruction;
|
||||
|
||||
|
@ -111,13 +112,18 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
|||
private void exposeMethods(ClassHolder classHolder, ExposedClass classToExpose, Diagnostics diagnostics,
|
||||
MethodReference functorMethod) {
|
||||
int index = 0;
|
||||
for (MethodDescriptor method : classToExpose.methods.keySet()) {
|
||||
for (var entry : classToExpose.methods.entrySet()) {
|
||||
var method = entry.getKey();
|
||||
var export = entry.getValue();
|
||||
MethodReference methodRef = new MethodReference(classHolder.getName(), method);
|
||||
CallLocation callLocation = new CallLocation(methodRef);
|
||||
|
||||
ValueType[] exportedMethodSignature = Arrays.stream(method.getSignature())
|
||||
.map(type -> ValueType.object(JSObject.class.getName()))
|
||||
.toArray(ValueType[]::new);
|
||||
var paramCount = method.parameterCount();
|
||||
if (export.vararg) {
|
||||
--paramCount;
|
||||
}
|
||||
var exportedMethodSignature = new ValueType[paramCount + 1];
|
||||
Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT);
|
||||
MethodDescriptor exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++,
|
||||
exportedMethodSignature);
|
||||
MethodHolder exportedMethod = new MethodHolder(exportedMethodDesc);
|
||||
|
@ -134,10 +140,15 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
|||
for (int i = 0; i < method.parameterCount(); ++i) {
|
||||
variablesToPass[i] = program.createVariable();
|
||||
}
|
||||
if (export.vararg) {
|
||||
transformVarargParam(variablesToPass, program, marshallInstructions, exportedMethod, 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < method.parameterCount(); ++i) {
|
||||
var byRef = i == method.parameterCount() - 1 && export.vararg
|
||||
&& typeHelper.isSupportedByRefType(method.parameterType(i));
|
||||
variablesToPass[i] = marshaller.unwrapReturnValue(callLocation, variablesToPass[i],
|
||||
method.parameterType(i), false, true);
|
||||
method.parameterType(i), byRef, true);
|
||||
}
|
||||
|
||||
basicBlock.addAll(marshallInstructions);
|
||||
|
@ -161,8 +172,6 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
|||
basicBlock.add(exit);
|
||||
|
||||
classHolder.addMethod(exportedMethod);
|
||||
|
||||
var export = classToExpose.methods.get(method);
|
||||
exportedMethod.getAnnotations().add(createExportAnnotation(export));
|
||||
|
||||
if (methodRef.equals(functorMethod)) {
|
||||
|
@ -179,10 +188,14 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
|||
continue;
|
||||
}
|
||||
|
||||
var paramCount = method.parameterCount();
|
||||
var vararg = method.hasModifier(ElementModifier.VARARGS);
|
||||
if (vararg) {
|
||||
--paramCount;
|
||||
}
|
||||
var callLocation = new CallLocation(method.getReference());
|
||||
var exportedMethodSignature = Arrays.stream(method.getSignature())
|
||||
.map(type -> ValueType.object(JSObject.class.getName()))
|
||||
.toArray(ValueType[]::new);
|
||||
var exportedMethodSignature = new ValueType[paramCount + 1];
|
||||
Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT);
|
||||
var exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++,
|
||||
exportedMethodSignature);
|
||||
var exportedMethod = new MethodHolder(exportedMethodDesc);
|
||||
|
@ -200,10 +213,15 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
|||
for (int i = 0; i < method.parameterCount(); ++i) {
|
||||
variablesToPass[i] = program.createVariable();
|
||||
}
|
||||
if (vararg) {
|
||||
transformVarargParam(variablesToPass, program, marshallInstructions, exportedMethod, 0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < method.parameterCount(); ++i) {
|
||||
var byRef = i == method.parameterCount() - 1 && vararg
|
||||
&& typeHelper.isSupportedByRefType(method.parameterType(i));
|
||||
variablesToPass[i] = marshaller.unwrapReturnValue(callLocation, variablesToPass[i],
|
||||
method.parameterType(i), false, true);
|
||||
method.parameterType(i), byRef, true);
|
||||
}
|
||||
|
||||
basicBlock.addAll(marshallInstructions);
|
||||
|
@ -232,6 +250,25 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
|||
}
|
||||
}
|
||||
|
||||
private void transformVarargParam(Variable[] variablesToPass, Program program,
|
||||
List<Instruction> instructions, MethodHolder method, int additionalSkip) {
|
||||
var last = variablesToPass.length - 1;
|
||||
|
||||
var lastConstant = new IntegerConstantInstruction();
|
||||
lastConstant.setReceiver(program.createVariable());
|
||||
lastConstant.setConstant(last + additionalSkip);
|
||||
instructions.add(lastConstant);
|
||||
|
||||
var extractVarargs = new InvokeInstruction();
|
||||
extractVarargs.setType(InvocationType.SPECIAL);
|
||||
extractVarargs.setMethod(JSMethods.ARGUMENTS_BEGINNING_AT);
|
||||
extractVarargs.setArguments(lastConstant.getReceiver());
|
||||
extractVarargs.setReceiver(variablesToPass[last]);
|
||||
instructions.add(extractVarargs);
|
||||
|
||||
method.getAnnotations().add(new AnnotationHolder(JSVararg.class.getName()));
|
||||
}
|
||||
|
||||
private AnnotationHolder createExportAnnotation(MethodExport export) {
|
||||
String annotationName;
|
||||
switch (export.kind) {
|
||||
|
@ -403,7 +440,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
|||
name = method.getName();
|
||||
}
|
||||
}
|
||||
return new MethodExport(name, kind);
|
||||
return new MethodExport(name, kind, method.hasModifier(ElementModifier.VARARGS));
|
||||
}
|
||||
|
||||
private void addFunctorField(ClassHolder cls, MethodReference method) {
|
||||
|
@ -437,10 +474,12 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
|||
static class MethodExport {
|
||||
final String alias;
|
||||
final MethodKind kind;
|
||||
boolean vararg;
|
||||
|
||||
MethodExport(String alias, MethodKind kind) {
|
||||
MethodExport(String alias, MethodKind kind, boolean vararg) {
|
||||
this.alias = alias;
|
||||
this.kind = kind;
|
||||
this.vararg = vararg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,8 +121,6 @@ class JSTypeHelper {
|
|||
default:
|
||||
return false;
|
||||
}
|
||||
} else if (itemType instanceof ValueType.Object) {
|
||||
return isJavaScriptClass(((ValueType.Object) itemType).getClassName());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
26
jso/impl/src/main/java/org/teavm/jso/impl/JSVararg.java
Normal file
26
jso/impl/src/main/java/org/teavm/jso/impl/JSVararg.java
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2024 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.impl;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Target(ElementType.METHOD)
|
||||
@interface JSVararg {
|
||||
}
|
|
@ -18,8 +18,8 @@ package org.teavm.platform.plugin;
|
|||
import org.teavm.backend.javascript.codegen.SourceWriter;
|
||||
import org.teavm.backend.javascript.spi.Generator;
|
||||
import org.teavm.backend.javascript.spi.GeneratorContext;
|
||||
import org.teavm.backend.javascript.spi.VirtualMethodContributor;
|
||||
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
|
||||
import org.teavm.backend.javascript.spi.MethodContributor;
|
||||
import org.teavm.backend.javascript.spi.MethodContributorContext;
|
||||
import org.teavm.backend.javascript.templating.JavaScriptTemplate;
|
||||
import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory;
|
||||
import org.teavm.dependency.DependencyAgent;
|
||||
|
@ -34,7 +34,7 @@ import org.teavm.model.MethodReader;
|
|||
import org.teavm.model.MethodReference;
|
||||
import org.teavm.model.ValueType;
|
||||
|
||||
public class AsyncMethodGenerator implements Generator, DependencyPlugin, VirtualMethodContributor {
|
||||
public class AsyncMethodGenerator implements Generator, DependencyPlugin, MethodContributor {
|
||||
private static final MethodDescriptor completeMethod = new MethodDescriptor("complete", Object.class, void.class);
|
||||
private static final MethodDescriptor errorMethod = new MethodDescriptor("error", Throwable.class, void.class);
|
||||
private JavaScriptTemplate template;
|
||||
|
@ -101,7 +101,7 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef) {
|
||||
public boolean isContributing(MethodContributorContext context, MethodReference methodRef) {
|
||||
ClassReader cls = context.getClassSource().get(methodRef.getClassName());
|
||||
if (cls == null) {
|
||||
return false;
|
||||
|
|
|
@ -82,6 +82,11 @@ public class ExportTest {
|
|||
testExport("exportClasses", ModuleWithExportedClasses.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void varargs() {
|
||||
testExport("varargs", ModuleWithVararg.class);
|
||||
}
|
||||
|
||||
private void testExport(String name, Class<?> moduleClass) {
|
||||
if (!Boolean.parseBoolean(System.getProperty("teavm.junit.js", "true"))) {
|
||||
return;
|
||||
|
@ -89,6 +94,7 @@ public class ExportTest {
|
|||
try {
|
||||
var jsTarget = new JavaScriptTarget();
|
||||
jsTarget.setModuleType(JSModuleType.ES2015);
|
||||
jsTarget.setObfuscated(false);
|
||||
var teavm = new TeaVMBuilder(jsTarget).build();
|
||||
var outputDir = new File(targetFile, name);
|
||||
teavm.installPlugins();
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright 2024 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.export;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import org.teavm.jso.JSExport;
|
||||
import org.teavm.jso.JSExportClasses;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSProperty;
|
||||
import org.testng.util.Strings;
|
||||
|
||||
@JSExportClasses(ModuleWithVararg.A.class)
|
||||
public class ModuleWithVararg {
|
||||
private ModuleWithVararg() {
|
||||
}
|
||||
|
||||
@JSExport
|
||||
public static String strings(String... values) {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("strings: ");
|
||||
sb.append(Strings.join(", ", values));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@JSExport
|
||||
public static String prefixStrings(int a, String... values) {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("strings(").append(a).append("): ");
|
||||
sb.append(Strings.join(", ", values));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@JSExport
|
||||
public static String ints(int... values) {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("ints: ");
|
||||
sb.append(IntStream.of(values).mapToObj(Integer::toString).collect(Collectors.joining(", ")));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@JSExport
|
||||
public static String prefixInts(String prefix, int... values) {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("ints(").append(prefix).append("): ");
|
||||
sb.append(IntStream.of(values).mapToObj(Integer::toString).collect(Collectors.joining(", ")));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@JSExport
|
||||
public static String objects(ObjectWithString... values) {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("objects: ");
|
||||
sb.append(Stream.of(values).map(v -> v.getStringValue()).collect(Collectors.joining(", ")));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public interface ObjectWithString extends JSObject {
|
||||
@JSProperty
|
||||
String getStringValue();
|
||||
}
|
||||
|
||||
public static class A {
|
||||
@JSExport
|
||||
public A() {
|
||||
}
|
||||
|
||||
@JSExport
|
||||
public String strings(String... values) {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("A.strings: ");
|
||||
sb.append(Strings.join(", ", values));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
28
tests/src/test/resources/org/teavm/jso/export/varargs.js
Normal file
28
tests/src/test/resources/org/teavm/jso/export/varargs.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
import * as varargs from '/tests/varargs/test.js';
|
||||
|
||||
export async function test() {
|
||||
assertEquals("strings: a, b", varargs.strings("a", "b"));
|
||||
assertEquals("strings(23): a, b", varargs.prefixStrings(23, "a", "b"));
|
||||
assertEquals("ints: 23, 42", varargs.ints(23, 42));
|
||||
assertEquals("ints(*): 23, 42", varargs.prefixInts("*", 23, 42));
|
||||
assertEquals("objects: a, b", varargs.objects({ stringValue: "a" }, { stringValue: "b" }));
|
||||
|
||||
let obj = new varargs.A();
|
||||
assertEquals("A.strings: a, b", obj.strings("a", "b"));
|
||||
}
|
Loading…
Reference in New Issue
Block a user