jso: improve support of instanceof and cast against JS wrapper types

Fix #808
This commit is contained in:
Alexey Andreev 2024-04-04 21:18:54 +02:00
parent e069bc3a44
commit a6fb67817c
19 changed files with 445 additions and 74 deletions

View File

@ -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")) {

View File

@ -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() {
}

View File

@ -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 {

View File

@ -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();

View File

@ -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() {
}

View File

@ -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

View File

@ -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() {
}

View File

@ -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);
}

View 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();
}

View File

@ -24,4 +24,6 @@ import java.lang.annotation.Target;
@Target(ElementType.TYPE)
public @interface JSClass {
String name() default "";
boolean transparent() default false;
}

View 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();
}

View File

@ -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);
}

View File

@ -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) {

View File

@ -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());

View File

@ -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")) {

View File

@ -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);

View File

@ -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);
}

View 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();
}
}

View File

@ -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));
}