mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
jso: improve support of instanceof and cast against JS wrapper types
Fix #808
This commit is contained in:
parent
e069bc3a44
commit
a6fb67817c
|
@ -125,6 +125,13 @@ let $rt_throwCCE = () => teavm_javaConstructorExists("java.lang.ClassCastExcepti
|
|||
? $rt_throw(teavm_javaConstructor("java.lang.ClassCastException", "()V")())
|
||||
: $rt_throw($rt_createException($rt_str("")));
|
||||
|
||||
let $rt_throwCCEIfFalse = (value, o) => {
|
||||
if (!value) {
|
||||
$rt_throwCCE();
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
let $rt_createStackElement = (className, methodName, fileName, lineNumber) => {
|
||||
if (teavm_javaConstructorExists("java.lang.StackTraceElement",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V")) {
|
||||
|
|
|
@ -18,7 +18,9 @@ package org.teavm.jso.core;
|
|||
import org.teavm.interop.NoSideEffects;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSPrimitiveType;
|
||||
|
||||
@JSPrimitiveType("boolean")
|
||||
public abstract class JSBoolean implements JSObject {
|
||||
private JSBoolean() {
|
||||
}
|
||||
|
|
|
@ -16,11 +16,19 @@
|
|||
package org.teavm.jso.core;
|
||||
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSClass;
|
||||
import org.teavm.jso.JSFunctor;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSProperty;
|
||||
|
||||
public abstract class JSError implements JSObject {
|
||||
@JSClass(name = "Error")
|
||||
public class JSError implements JSObject {
|
||||
public JSError() {
|
||||
}
|
||||
|
||||
public JSError(String message) {
|
||||
}
|
||||
|
||||
@JSBody(params = { "tryClause", "catchClause" }, script = ""
|
||||
+ "try {"
|
||||
+ "return tryClause();"
|
||||
|
@ -29,17 +37,19 @@ public abstract class JSError implements JSObject {
|
|||
+ "}")
|
||||
public static native <T> T catchNative(TryClause<T> tryClause, CatchClause<T> catchClause);
|
||||
|
||||
@JSBody(params = "object", script = "return object instanceof Error;")
|
||||
public static native boolean isError(JSObject object);
|
||||
@Deprecated
|
||||
public static boolean isError(JSObject object) {
|
||||
return object instanceof JSError;
|
||||
}
|
||||
|
||||
@JSProperty
|
||||
public abstract String getStack();
|
||||
public native String getStack();
|
||||
|
||||
@JSProperty
|
||||
public abstract String getMessage();
|
||||
public native String getMessage();
|
||||
|
||||
@JSProperty
|
||||
public abstract String getName();
|
||||
public native String getName();
|
||||
|
||||
@JSFunctor
|
||||
public interface TryClause<T> extends JSObject {
|
||||
|
|
|
@ -17,8 +17,10 @@ package org.teavm.jso.core;
|
|||
|
||||
import org.teavm.jso.JSByRef;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSPrimitiveType;
|
||||
import org.teavm.jso.JSProperty;
|
||||
|
||||
@JSPrimitiveType("function")
|
||||
public abstract class JSFunction implements JSObject {
|
||||
@JSProperty
|
||||
public abstract int getLength();
|
||||
|
|
|
@ -18,7 +18,9 @@ package org.teavm.jso.core;
|
|||
import org.teavm.interop.NoSideEffects;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSPrimitiveType;
|
||||
|
||||
@JSPrimitiveType("number")
|
||||
public abstract class JSNumber implements JSObject {
|
||||
private JSNumber() {
|
||||
}
|
||||
|
|
|
@ -43,13 +43,14 @@ public final class JSObjects {
|
|||
@NoSideEffects
|
||||
public static native <T extends JSObject> T createWithoutProto();
|
||||
|
||||
@JSBody(params = "object", script = "return typeof object === 'undefined';")
|
||||
@NoSideEffects
|
||||
public static native boolean isUndefined(Object object);
|
||||
public static boolean isUndefined(Object object) {
|
||||
return object instanceof JSUndefined;
|
||||
}
|
||||
|
||||
@JSBody(script = "return void 0;")
|
||||
@NoSideEffects
|
||||
public static native JSObject undefined();
|
||||
@Deprecated
|
||||
public static JSObject undefined() {
|
||||
return JSUndefined.instance();
|
||||
}
|
||||
|
||||
@JSBody(params = "object", script = "return typeof object;")
|
||||
@NoSideEffects
|
||||
|
|
|
@ -18,8 +18,10 @@ package org.teavm.jso.core;
|
|||
import org.teavm.interop.NoSideEffects;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSPrimitiveType;
|
||||
import org.teavm.jso.JSProperty;
|
||||
|
||||
@JSPrimitiveType("string")
|
||||
public abstract class JSString implements JSObject {
|
||||
private JSString() {
|
||||
}
|
||||
|
|
|
@ -17,8 +17,11 @@ package org.teavm.jso.core;
|
|||
|
||||
import org.teavm.interop.NoSideEffects;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSIndexer;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSPrimitiveType;
|
||||
|
||||
@JSPrimitiveType("symbol")
|
||||
public class JSSymbol<T> implements JSObject {
|
||||
private JSSymbol() {
|
||||
}
|
||||
|
@ -26,10 +29,10 @@ public class JSSymbol<T> implements JSObject {
|
|||
@JSBody(params = "name", script = "return Symbol(name);")
|
||||
public static native <T> JSSymbol<T> create(String name);
|
||||
|
||||
@JSBody(params = "obj", script = "return obj[this];")
|
||||
@JSIndexer
|
||||
public native T get(Object obj);
|
||||
|
||||
@JSBody(params = { "obj", "value" }, script = "obj[this] = value;")
|
||||
@JSIndexer
|
||||
@NoSideEffects
|
||||
public native void set(Object obj, T value);
|
||||
}
|
||||
|
|
33
jso/apis/src/main/java/org/teavm/jso/core/JSUndefined.java
Normal file
33
jso/apis/src/main/java/org/teavm/jso/core/JSUndefined.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.teavm.interop.NoSideEffects;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSClass;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSPrimitiveType;
|
||||
|
||||
@JSClass
|
||||
@JSPrimitiveType("undefined")
|
||||
public class JSUndefined implements JSObject {
|
||||
private JSUndefined() {
|
||||
}
|
||||
|
||||
@JSBody(script = "return void 0;")
|
||||
@NoSideEffects
|
||||
public static native JSUndefined instance();
|
||||
}
|
|
@ -24,4 +24,6 @@ import java.lang.annotation.Target;
|
|||
@Target(ElementType.TYPE)
|
||||
public @interface JSClass {
|
||||
String name() default "";
|
||||
|
||||
boolean transparent() default false;
|
||||
}
|
||||
|
|
27
jso/core/src/main/java/org/teavm/jso/JSPrimitiveType.java
Normal file
27
jso/core/src/main/java/org/teavm/jso/JSPrimitiveType.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface JSPrimitiveType {
|
||||
String value();
|
||||
}
|
|
@ -704,4 +704,16 @@ final class JS {
|
|||
@InjectedBy(JSNativeInjector.class)
|
||||
@NoSideEffects
|
||||
public static native JSObject importModule(String name);
|
||||
|
||||
@InjectedBy(JSNativeInjector.class)
|
||||
@NoSideEffects
|
||||
public static native boolean instanceOf(JSObject obj, JSObject cls);
|
||||
|
||||
@InjectedBy(JSNativeInjector.class)
|
||||
@NoSideEffects
|
||||
public static native boolean isPrimitive(JSObject obj, JSObject primitive);
|
||||
|
||||
@InjectedBy(JSNativeInjector.class)
|
||||
@NoSideEffects
|
||||
public static native JSObject throwCCEIfFalse(boolean value, JSObject o);
|
||||
}
|
||||
|
|
|
@ -35,8 +35,10 @@ import org.teavm.diagnostics.Diagnostics;
|
|||
import org.teavm.interop.NoSideEffects;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSByRef;
|
||||
import org.teavm.jso.JSClass;
|
||||
import org.teavm.jso.JSFunctor;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSPrimitiveType;
|
||||
import org.teavm.model.AnnotationContainerReader;
|
||||
import org.teavm.model.AnnotationHolder;
|
||||
import org.teavm.model.AnnotationReader;
|
||||
|
@ -86,6 +88,10 @@ class JSClassProcessor {
|
|||
Object.class, JSObject.class);
|
||||
private static final MethodReference IS_JS = new MethodReference(JSWrapper.class, "isJs",
|
||||
Object.class, boolean.class);
|
||||
private static final MethodReference IS_PRIMITIVE = new MethodReference(JSWrapper.class, "isPrimitive",
|
||||
Object.class, JSObject.class, boolean.class);
|
||||
private static final MethodReference INSTANCE_OF = new MethodReference(JSWrapper.class, "instanceOf",
|
||||
Object.class, JSObject.class, boolean.class);
|
||||
private final ClassReaderSource classSource;
|
||||
private final JSBodyRepository repository;
|
||||
private final JavaInvocationProcessor javaInvocationProcessor;
|
||||
|
@ -470,11 +476,23 @@ class JSClassProcessor {
|
|||
|
||||
ClassReader targetClass = classSource.get(targetClassName);
|
||||
if (targetClass.getAnnotations().get(JSFunctor.class.getName()) == null) {
|
||||
AssignInstruction assign = new AssignInstruction();
|
||||
assign.setLocation(location.getSourceLocation());
|
||||
assign.setAssignee(cast.getValue());
|
||||
assign.setReceiver(cast.getReceiver());
|
||||
replacement.add(assign);
|
||||
if (isTransparent(targetClassName)) {
|
||||
var assign = new AssignInstruction();
|
||||
assign.setLocation(location.getSourceLocation());
|
||||
assign.setAssignee(cast.getValue());
|
||||
assign.setReceiver(cast.getReceiver());
|
||||
replacement.add(assign);
|
||||
} else {
|
||||
var instanceOfResult = program.createVariable();
|
||||
processIsInstanceUnwrapped(cast.getLocation(), cast.getValue(), targetClassName, instanceOfResult);
|
||||
|
||||
var invoke = new InvokeInstruction();
|
||||
invoke.setType(InvocationType.SPECIAL);
|
||||
invoke.setMethod(JSMethods.THROW_CCE_IF_FALSE);
|
||||
invoke.setArguments(instanceOfResult, cast.getValue());
|
||||
invoke.setReceiver(cast.getReceiver());
|
||||
replacement.add(invoke);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -499,23 +517,107 @@ class JSClassProcessor {
|
|||
return;
|
||||
}
|
||||
|
||||
var type = types.typeOf(isInstance.getValue());
|
||||
if (type == JSType.JS) {
|
||||
var replacement = new IntegerConstantInstruction();
|
||||
replacement.setConstant(1);
|
||||
replacement.setReceiver(isInstance.getReceiver());
|
||||
replacement.setLocation(isInstance.getLocation());
|
||||
isInstance.replace(replacement);
|
||||
return;
|
||||
}
|
||||
replacement.clear();
|
||||
processIsInstance(isInstance.getLocation(), types.typeOf(isInstance.getValue()), isInstance.getValue(),
|
||||
targetClassName, isInstance.getReceiver());
|
||||
isInstance.insertPreviousAll(replacement);
|
||||
isInstance.delete();
|
||||
replacement.clear();
|
||||
}
|
||||
|
||||
var replacement = new InvokeInstruction();
|
||||
replacement.setType(InvocationType.SPECIAL);
|
||||
replacement.setMethod(IS_JS);
|
||||
replacement.setArguments(isInstance.getValue());
|
||||
replacement.setReceiver(isInstance.getReceiver());
|
||||
replacement.setLocation(isInstance.getLocation());
|
||||
isInstance.replace(replacement);
|
||||
private void processIsInstance(TextLocation location, JSType type, Variable value, String targetClassName,
|
||||
Variable receiver) {
|
||||
if (type == JSType.JS) {
|
||||
if (isTransparent(targetClassName)) {
|
||||
var cst = new IntegerConstantInstruction();
|
||||
cst.setConstant(1);
|
||||
cst.setReceiver(receiver);
|
||||
cst.setLocation(location);
|
||||
replacement.add(cst);
|
||||
} else {
|
||||
var primitiveType = getPrimitiveType(targetClassName);
|
||||
var invoke = new InvokeInstruction();
|
||||
invoke.setType(InvocationType.SPECIAL);
|
||||
invoke.setMethod(primitiveType != null ? JSMethods.IS_PRIMITIVE : JSMethods.INSTANCE_OF);
|
||||
var secondArg = primitiveType != null
|
||||
? marshaller.addJsString(primitiveType, location)
|
||||
: marshaller.classRef(targetClassName, location);
|
||||
invoke.setArguments(value, secondArg);
|
||||
invoke.setReceiver(receiver);
|
||||
invoke.setLocation(location);
|
||||
replacement.add(invoke);
|
||||
}
|
||||
} else {
|
||||
if (isTransparent(targetClassName)) {
|
||||
var invoke = new InvokeInstruction();
|
||||
invoke.setType(InvocationType.SPECIAL);
|
||||
invoke.setMethod(IS_JS);
|
||||
invoke.setArguments(value);
|
||||
invoke.setReceiver(receiver);
|
||||
invoke.setLocation(location);
|
||||
replacement.add(invoke);
|
||||
} else {
|
||||
var primitiveType = getPrimitiveType(targetClassName);
|
||||
var invoke = new InvokeInstruction();
|
||||
invoke.setType(InvocationType.SPECIAL);
|
||||
invoke.setMethod(primitiveType != null ? IS_PRIMITIVE : INSTANCE_OF);
|
||||
var secondArg = primitiveType != null
|
||||
? marshaller.addJsString(primitiveType, location)
|
||||
: marshaller.classRef(targetClassName, location);
|
||||
invoke.setArguments(value, secondArg);
|
||||
invoke.setReceiver(receiver);
|
||||
invoke.setLocation(location);
|
||||
replacement.add(invoke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processIsInstanceUnwrapped(TextLocation location, Variable value, String targetClassName,
|
||||
Variable receiver) {
|
||||
var primitiveType = getPrimitiveType(targetClassName);
|
||||
var invoke = new InvokeInstruction();
|
||||
invoke.setType(InvocationType.SPECIAL);
|
||||
invoke.setMethod(primitiveType != null ? JSMethods.IS_PRIMITIVE : JSMethods.INSTANCE_OF);
|
||||
var secondArg = primitiveType != null
|
||||
? marshaller.addJsString(primitiveType, location)
|
||||
: marshaller.classRef(targetClassName, location);
|
||||
invoke.setArguments(value, secondArg);
|
||||
invoke.setReceiver(receiver);
|
||||
invoke.setLocation(location);
|
||||
replacement.add(invoke);
|
||||
}
|
||||
|
||||
private boolean isTransparent(String className) {
|
||||
var cls = classSource.get(className);
|
||||
if (cls == null) {
|
||||
return true;
|
||||
}
|
||||
if (cls.hasModifier(ElementModifier.INTERFACE)) {
|
||||
return true;
|
||||
}
|
||||
var clsAnnot = cls.getAnnotations().get(JSClass.class.getName());
|
||||
if (clsAnnot != null) {
|
||||
var transparent = clsAnnot.getValue("transparent");
|
||||
if (transparent != null) {
|
||||
return transparent.getBoolean();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getPrimitiveType(String className) {
|
||||
var cls = classSource.get(className);
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
var clsAnnot = cls.getAnnotations().get(JSPrimitiveType.class.getName());
|
||||
if (clsAnnot != null) {
|
||||
var value = clsAnnot.getValue("value");
|
||||
if (value != null) {
|
||||
return value.getString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Variable wrapJsAsJava(Instruction instruction, Variable var, ValueType type) {
|
||||
|
|
|
@ -125,6 +125,13 @@ final class JSMethods {
|
|||
public static final MethodReference IMPORT_MODULE = new MethodReference(JS.class, "importModule",
|
||||
String.class, JSObject.class);
|
||||
|
||||
public static final MethodReference INSTANCE_OF = new MethodReference(JS.class, "instanceOf", JSObject.class,
|
||||
JSObject.class, boolean.class);
|
||||
public static final MethodReference IS_PRIMITIVE = new MethodReference(JS.class, "isPrimitive", JSObject.class,
|
||||
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 ValueType JS_OBJECT = ValueType.object(JSObject.class.getName());
|
||||
public static final ValueType OBJECT = ValueType.object("java.lang.Object");
|
||||
public static final ValueType JS_ARRAY = ValueType.object(JSArray.class.getName());
|
||||
|
|
|
@ -190,6 +190,41 @@ public class JSNativeInjector implements Injector, DependencyPlugin {
|
|||
writer.appendFunction(context.importModule(name));
|
||||
break;
|
||||
}
|
||||
case "instanceOf": {
|
||||
if (context.getPrecedence().ordinal() >= Precedence.CONDITIONAL.ordinal()) {
|
||||
writer.append("(");
|
||||
}
|
||||
context.writeExpr(context.getArgument(0), Precedence.COMPARISON.next());
|
||||
writer.append(" instanceof ");
|
||||
context.writeExpr(context.getArgument(1), Precedence.COMPARISON.next());
|
||||
writer.ws().append("?").ws().append("1").ws().append(":").ws().append("0");
|
||||
if (context.getPrecedence().ordinal() >= Precedence.CONDITIONAL.ordinal()) {
|
||||
writer.append(")");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "isPrimitive": {
|
||||
if (context.getPrecedence().ordinal() >= Precedence.CONDITIONAL.ordinal()) {
|
||||
writer.append("(");
|
||||
}
|
||||
writer.append("typeof ");
|
||||
context.writeExpr(context.getArgument(0), Precedence.UNARY.next());
|
||||
writer.ws().append("===").ws();
|
||||
context.writeExpr(context.getArgument(1), Precedence.COMPARISON.next());
|
||||
writer.ws().append("?").ws().append("1").ws().append(":").ws().append("0");
|
||||
if (context.getPrecedence().ordinal() >= Precedence.CONDITIONAL.ordinal()) {
|
||||
writer.append(")");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "throwCCEIfFalse": {
|
||||
writer.appendFunction("$rt_throwCCEIfFalse").append("(");
|
||||
context.writeExpr(context.getArgument(0), Precedence.min());
|
||||
writer.append(",").ws();
|
||||
context.writeExpr(context.getArgument(1), Precedence.min());
|
||||
writer.append(")");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
if (methodRef.getName().startsWith("unwrap")) {
|
||||
|
|
|
@ -550,7 +550,7 @@ class JSValueMarshaller {
|
|||
|
||||
Variable addString(String str, TextLocation location) {
|
||||
Variable var = program.createVariable();
|
||||
StringConstantInstruction nameInsn = new StringConstantInstruction();
|
||||
var nameInsn = new StringConstantInstruction();
|
||||
nameInsn.setReceiver(var);
|
||||
nameInsn.setConstant(str);
|
||||
nameInsn.setLocation(location);
|
||||
|
@ -558,6 +558,10 @@ class JSValueMarshaller {
|
|||
return var;
|
||||
}
|
||||
|
||||
Variable addJsString(String str, TextLocation location) {
|
||||
return addStringWrap(addString(str, location), location);
|
||||
}
|
||||
|
||||
Variable classRef(String className, TextLocation location) {
|
||||
String name = null;
|
||||
String module = null;
|
||||
|
@ -586,16 +590,10 @@ class JSValueMarshaller {
|
|||
}
|
||||
|
||||
Variable globalRef(String name, TextLocation location) {
|
||||
var nameInsn = new StringConstantInstruction();
|
||||
nameInsn.setReceiver(program.createVariable());
|
||||
nameInsn.setConstant(name);
|
||||
nameInsn.setLocation(location);
|
||||
replacement.add(nameInsn);
|
||||
|
||||
var invoke = new InvokeInstruction();
|
||||
invoke.setType(InvocationType.SPECIAL);
|
||||
invoke.setMethod(JSMethods.GLOBAL);
|
||||
invoke.setArguments(nameInsn.getReceiver());
|
||||
invoke.setArguments(addString(name, location));
|
||||
invoke.setReceiver(program.createVariable());
|
||||
invoke.setLocation(location);
|
||||
replacement.add(invoke);
|
||||
|
@ -618,26 +616,11 @@ class JSValueMarshaller {
|
|||
invoke.setLocation(location);
|
||||
replacement.add(invoke);
|
||||
|
||||
var nameInsn = new StringConstantInstruction();
|
||||
nameInsn.setReceiver(program.createVariable());
|
||||
nameInsn.setConstant(name);
|
||||
nameInsn.setLocation(location);
|
||||
replacement.add(nameInsn);
|
||||
|
||||
var wrapName = new InvokeInstruction();
|
||||
wrapName.setType(InvocationType.SPECIAL);
|
||||
wrapName.setMethod(referenceCache.getCached(new MethodReference(JS.class, "wrap",
|
||||
String.class, JSObject.class)));
|
||||
wrapName.setReceiver(program.createVariable());
|
||||
wrapName.setArguments(nameInsn.getReceiver());
|
||||
wrapName.setLocation(location);
|
||||
replacement.add(wrapName);
|
||||
|
||||
var get = new InvokeInstruction();
|
||||
get.setType(InvocationType.SPECIAL);
|
||||
get.setMethod(JSMethods.GET_PURE);
|
||||
get.setReceiver(program.createVariable());
|
||||
get.setArguments(invoke.getReceiver(), wrapName.getReceiver());
|
||||
get.setArguments(invoke.getReceiver(), addJsString(name, location));
|
||||
get.setLocation(location);
|
||||
replacement.add(get);
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ package org.teavm.jso.impl;
|
|||
|
||||
import org.teavm.interop.NoSideEffects;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSClass;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.core.JSBoolean;
|
||||
import org.teavm.jso.core.JSFinalizationRegistry;
|
||||
|
@ -24,11 +25,12 @@ import org.teavm.jso.core.JSMap;
|
|||
import org.teavm.jso.core.JSNumber;
|
||||
import org.teavm.jso.core.JSObjects;
|
||||
import org.teavm.jso.core.JSString;
|
||||
import org.teavm.jso.core.JSUndefined;
|
||||
import org.teavm.jso.core.JSWeakMap;
|
||||
import org.teavm.jso.core.JSWeakRef;
|
||||
|
||||
public final class JSWrapper {
|
||||
private static final JSWeakMap<JSObject, JSNumber> hashCodes = new JSWeakMap<>();
|
||||
private static final JSWeakMap<JSObject, JSTransparentInt> hashCodes = new JSWeakMap<>();
|
||||
private static final JSWeakMap<JSObject, JSWeakRef<JSObject>> wrappers = JSWeakRef.isSupported()
|
||||
? new JSWeakMap<>() : null;
|
||||
private static final JSMap<JSString, JSWeakRef<JSObject>> stringWrappers = JSWeakRef.isSupported()
|
||||
|
@ -68,8 +70,8 @@ public final class JSWrapper {
|
|||
if (wrappers != null) {
|
||||
if (isObject) {
|
||||
var existingRef = get(wrappers, js);
|
||||
var existing = !JSObjects.isUndefined(existingRef) ? deref(existingRef) : JSObjects.undefined();
|
||||
if (JSObjects.isUndefined(existing)) {
|
||||
var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance();
|
||||
if (isUndefined(existing)) {
|
||||
var wrapper = new JSWrapper(js);
|
||||
set(wrappers, js, createWeakRef(wrapperToJs(wrapper)));
|
||||
return wrapper;
|
||||
|
@ -79,8 +81,8 @@ public final class JSWrapper {
|
|||
} else if (type.equals("string")) {
|
||||
var jsString = (JSString) js;
|
||||
var existingRef = get(stringWrappers, jsString);
|
||||
var existing = !JSObjects.isUndefined(existingRef) ? deref(existingRef) : JSObjects.undefined();
|
||||
if (JSObjects.isUndefined(existing)) {
|
||||
var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance();
|
||||
if (isUndefined(existing)) {
|
||||
var wrapper = new JSWrapper(js);
|
||||
var wrapperAsJs = wrapperToJs(wrapper);
|
||||
set(stringWrappers, jsString, createWeakRef(wrapperAsJs));
|
||||
|
@ -92,8 +94,8 @@ public final class JSWrapper {
|
|||
} else if (type.equals("number")) {
|
||||
var jsNumber = (JSNumber) js;
|
||||
var existingRef = get(numberWrappers, jsNumber);
|
||||
var existing = !JSObjects.isUndefined(existingRef) ? deref(existingRef) : JSObjects.undefined();
|
||||
if (JSObjects.isUndefined(existing)) {
|
||||
var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance();
|
||||
if (isUndefined(existing)) {
|
||||
var wrapper = new JSWrapper(js);
|
||||
var wrapperAsJs = wrapperToJs(wrapper);
|
||||
set(numberWrappers, jsNumber, createWeakRef(wrapperAsJs));
|
||||
|
@ -104,8 +106,8 @@ public final class JSWrapper {
|
|||
}
|
||||
} else if (type.equals("undefined")) {
|
||||
var existingRef = undefinedWrapper;
|
||||
var existing = existingRef != null ? deref(existingRef) : JSObjects.undefined();
|
||||
if (JSObjects.isUndefined(existing)) {
|
||||
var existing = existingRef != null ? deref(existingRef) : JSUndefined.instance();
|
||||
if (isUndefined(existing)) {
|
||||
var wrapper = new JSWrapper(js);
|
||||
var wrapperAsJs = wrapperToJs(wrapper);
|
||||
undefinedWrapper = createWeakRef(wrapperAsJs);
|
||||
|
@ -215,13 +217,21 @@ public final class JSWrapper {
|
|||
return !isJava(o) || o instanceof JSWrapper;
|
||||
}
|
||||
|
||||
public static boolean isPrimitive(Object o, JSObject primitive) {
|
||||
return isJs(o) && JS.isPrimitive(maybeUnwrap(o), primitive);
|
||||
}
|
||||
|
||||
public static boolean instanceOf(Object o, JSObject type) {
|
||||
return isJs(o) && JS.instanceOf(maybeUnwrap(o), type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
var type = JSObjects.typeOf(js);
|
||||
if (type.equals("object") || type.equals("symbol") || type.equals("function")) {
|
||||
var code = hashCodes.get(js);
|
||||
if (JSObjects.isUndefined(code)) {
|
||||
code = JSNumber.valueOf(++hashCodeGen);
|
||||
if (isUndefined(code)) {
|
||||
code = JSTransparentInt.valueOf(++hashCodeGen);
|
||||
hashCodes.set(js, code);
|
||||
}
|
||||
return code.intValue();
|
||||
|
@ -263,6 +273,18 @@ public final class JSWrapper {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSObjects.isUndefined(js) ? "undefined" : JSObjects.toString(js);
|
||||
return isUndefined(js) ? "undefined" : JSObjects.toString(js);
|
||||
}
|
||||
|
||||
@JSClass(transparent = true)
|
||||
static abstract class JSTransparentInt implements JSObject {
|
||||
@JSBody(script = "return this;")
|
||||
native int intValue();
|
||||
|
||||
@JSBody(params = "value", script = "return value;")
|
||||
static native JSTransparentInt valueOf(int value);
|
||||
}
|
||||
|
||||
@JSBody(params = "obj", script = "return typeof obj == 'undefined'")
|
||||
private static native boolean isUndefined(JSObject obj);
|
||||
}
|
||||
|
|
118
tests/src/test/java/org/teavm/jso/test/InstanceOfTest.java
Normal file
118
tests/src/test/java/org/teavm/jso/test/InstanceOfTest.java
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSClass;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSProperty;
|
||||
import org.teavm.jso.core.JSNumber;
|
||||
import org.teavm.junit.AttachJavaScript;
|
||||
import org.teavm.junit.EachTestCompiledSeparately;
|
||||
import org.teavm.junit.OnlyPlatform;
|
||||
import org.teavm.junit.SkipJVM;
|
||||
import org.teavm.junit.TeaVMTestRunner;
|
||||
import org.teavm.junit.TestPlatform;
|
||||
|
||||
@RunWith(TeaVMTestRunner.class)
|
||||
@SkipJVM
|
||||
@OnlyPlatform(TestPlatform.JAVASCRIPT)
|
||||
@EachTestCompiledSeparately
|
||||
public class InstanceOfTest {
|
||||
@Test
|
||||
@AttachJavaScript("org/teavm/jso/test/classWithConstructor.js")
|
||||
public void instanceOf() {
|
||||
var a = createClassWithConstructor();
|
||||
assertTrue(a instanceof ClassWithConstructor);
|
||||
assertFalse(a instanceof JSNumber);
|
||||
assertTrue(a instanceof I);
|
||||
assertTrue(a instanceof C);
|
||||
|
||||
var b = callCreateClassWithConstructor();
|
||||
assertTrue(b instanceof ClassWithConstructor);
|
||||
assertFalse(b instanceof JSNumber);
|
||||
assertTrue(b instanceof I);
|
||||
assertTrue(b instanceof C);
|
||||
|
||||
var c = createNumber();
|
||||
assertFalse(c instanceof ClassWithConstructor);
|
||||
assertTrue(c instanceof JSNumber);
|
||||
assertTrue(c instanceof I);
|
||||
assertTrue(c instanceof C);
|
||||
|
||||
var d = callCreateNumber();
|
||||
assertFalse(d instanceof ClassWithConstructor);
|
||||
assertTrue(d instanceof JSNumber);
|
||||
assertTrue(d instanceof I);
|
||||
assertTrue(d instanceof C);
|
||||
}
|
||||
|
||||
@Test
|
||||
@AttachJavaScript("org/teavm/jso/test/classWithConstructor.js")
|
||||
public void cast() {
|
||||
var a = createClassWithConstructor();
|
||||
assertEquals(99, ((ClassWithConstructor) a).getFoo());
|
||||
|
||||
try {
|
||||
assertEquals(99, ((JSNumber) a).intValue());
|
||||
fail("CCE not thrown");
|
||||
} catch (ClassCastException e) {
|
||||
// expected
|
||||
}
|
||||
assertEquals(99, ((I) a).getFoo());
|
||||
assertEquals(99, ((C) a).getFoo());
|
||||
|
||||
var c = createNumber();
|
||||
assertEquals(23, ((JSNumber) c).intValue());
|
||||
try {
|
||||
assertEquals(99, ((ClassWithConstructor) c).getFoo());
|
||||
fail("CCE not thrown");
|
||||
} catch (ClassCastException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
private Object callCreateClassWithConstructor() {
|
||||
return createClassWithConstructor();
|
||||
}
|
||||
|
||||
@JSBody(script = "return new ClassWithConstructor();")
|
||||
private static native JSObject createClassWithConstructor();
|
||||
|
||||
private Object callCreateNumber() {
|
||||
return createNumber();
|
||||
}
|
||||
|
||||
@JSBody(script = "return 23;")
|
||||
private static native JSObject createNumber();
|
||||
|
||||
interface I extends JSObject {
|
||||
@JSProperty
|
||||
int getFoo();
|
||||
}
|
||||
|
||||
@JSClass(transparent = true)
|
||||
abstract class C implements JSObject {
|
||||
@JSProperty
|
||||
public abstract int getFoo();
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ import org.teavm.jso.JSObject;
|
|||
import org.teavm.jso.core.JSNumber;
|
||||
import org.teavm.jso.core.JSObjects;
|
||||
import org.teavm.jso.core.JSString;
|
||||
import org.teavm.jso.core.JSUndefined;
|
||||
import org.teavm.junit.EachTestCompiledSeparately;
|
||||
import org.teavm.junit.OnlyPlatform;
|
||||
import org.teavm.junit.SkipJVM;
|
||||
|
@ -313,10 +314,10 @@ public class JSWrapperTest {
|
|||
|
||||
@Test
|
||||
public void wrapUndefined() {
|
||||
field1 = JSObjects.undefined();
|
||||
field1 = JSUndefined.instance();
|
||||
assertEquals("undefined", field1.toString());
|
||||
assertEquals(JSObjects.undefined(), field1);
|
||||
assertSame(JSObjects.undefined(), field1);
|
||||
assertEquals(JSUndefined.instance(), field1);
|
||||
assertSame(JSUndefined.instance(), field1);
|
||||
assertTrue(field1 instanceof JSObject);
|
||||
assertTrue(JSObjects.isUndefined(field1));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user