Skip to content

Commit

Permalink
[JS] Allow customization of $isInstance for native types.
Browse files Browse the repository at this point in the history
This allows `instanceof` on native interfaces if they define a custom `$isInstance` method. The custom `$isInstance` method cannot be private.

PiperOrigin-RevId: 561531763
  • Loading branch information
rluble authored and copybara-github committed Aug 31, 2023
1 parent afa547f commit db9ec79
Show file tree
Hide file tree
Showing 15 changed files with 108 additions and 24 deletions.
2 changes: 0 additions & 2 deletions jre/java/java/io/Serializable.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@

import javaemul.internal.ArrayHelper;
import javaemul.internal.JsUtils;
import jsinterop.annotations.JsMethod;

/**
* Provided for interoperability; RPC treats this interface synonymously with
* {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable}.
* The Java serialization protocol is explicitly not supported.
*/
public interface Serializable {
@JsMethod
static boolean $isInstance(HasSerializableTypeMarker instance) {
if (instance == null) {
return false;
Expand Down
2 changes: 0 additions & 2 deletions jre/java/java/lang/Boolean.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.io.Serializable;
import javaemul.internal.JsUtils;
import javaemul.internal.Platform;
import jsinterop.annotations.JsMethod;

/**
* Wraps native <code>boolean</code> as an object.
Expand Down Expand Up @@ -106,7 +105,6 @@ public String toString() {
return toString(booleanValue());
}

@JsMethod
static boolean $isInstance(Object instance) {
return "boolean".equals(JsUtils.typeOf(instance));
}
Expand Down
2 changes: 0 additions & 2 deletions jre/java/java/lang/CharSequence.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
import javaemul.internal.JsUtils;
import jsinterop.annotations.JsMethod;

/**
* Abstracts the notion of a sequence of characters.
Expand Down Expand Up @@ -58,7 +57,6 @@ public boolean hasNext() {
}, Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED, false);
}

@JsMethod
static boolean $isInstance(HasCharSequenceTypeMarker instance) {
if (JsUtils.typeOf(instance).equals("string")) {
return true;
Expand Down
3 changes: 0 additions & 3 deletions jre/java/java/lang/Cloneable.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@
package java.lang;

import javaemul.internal.ArrayHelper;
import jsinterop.annotations.JsMethod;

/**
* Indicates that a class implements <code>clone()</code>.
*/
public interface Cloneable {

@JsMethod
static boolean $isInstance(HasCloneableTypeMarker instance) {
if (instance == null) {
return false;
Expand Down
2 changes: 0 additions & 2 deletions jre/java/java/lang/Comparable.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package java.lang;

import javaemul.internal.JsUtils;
import jsinterop.annotations.JsMethod;

/**
* An interface used a basis for implementing custom ordering. <a
Expand All @@ -28,7 +27,6 @@
public interface Comparable<T> {
int compareTo(T other);

@JsMethod
static boolean $isInstance(HasComparableTypeMarker instance) {
String type = JsUtils.typeOf(instance);
if (type.equals("boolean") || type.equals("number") || type.equals("string")) {
Expand Down
2 changes: 0 additions & 2 deletions jre/java/java/lang/Double.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import javaemul.internal.JsUtils;
import javaemul.internal.Platform;
import jsinterop.annotations.JsMethod;

/**
* Wraps a primitive <code>double</code> as an object.
Expand Down Expand Up @@ -182,7 +181,6 @@ public String toString() {
return toString(doubleValue());
}

@JsMethod
static boolean $isInstance(Object instance) {
return "number".equals(JsUtils.typeOf(instance));
}
Expand Down
1 change: 0 additions & 1 deletion jre/java/java/lang/Number.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ static class __ParseLong {
@JsType(isNative = true, name = "Number$impl", namespace = "java.lang")
private static class JavaLangNumber { }

@JsMethod
static boolean $isInstance(Object instance) {
return "number".equals(JsUtils.typeOf(instance)) || instance instanceof JavaLangNumber;
}
Expand Down
1 change: 0 additions & 1 deletion jre/java/java/lang/Object.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ public final Class<?> getClass() {
return Class.$get(Constructor.of(this));
}

@JsMethod
static boolean $isInstance(Object instance) {
return instance != null;
}
Expand Down
2 changes: 0 additions & 2 deletions jre/java/java/lang/String.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import javaemul.internal.JsUtils;
import javaemul.internal.NativeRegExp;
import javaemul.internal.StringUtil;
import jsinterop.annotations.JsMethod;
import jsinterop.annotations.JsNonNull;
import jsinterop.annotations.JsPackage;
import jsinterop.annotations.JsProperty;
Expand Down Expand Up @@ -694,7 +693,6 @@ private static class NativeString {
public native String toUpperCase();
}

@JsMethod
static boolean $isInstance(Object instance) {
return "string".equals(JsUtils.typeOf(instance));
}
Expand Down
7 changes: 7 additions & 0 deletions tools/java/com/google/j2cl/tools/rta/RapidTypeAnalyser.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ private static void markTypeLive(Type type) {
// When a type is marked as live, we need to explicitly mark the super interfaces as live since
// we need markImplementor call (which are not tracked in AST).
type.getSuperInterfaces().forEach(RapidTypeAnalyser::markTypeLive);

// Types are made live by `instanceof` and casts, so if the type has a custom $isInstance
// it should be also considered as if it was called.
Member isInstanceMember = type.getMemberByName("$isInstance");
if (isInstanceMember != null) {
onMemberReference(isInstanceMember);
}
}

private RapidTypeAnalyser() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,15 @@ public boolean isSyntheticInstanceOfSupportMember() {
}

public static final String CONSTRUCTOR_METHOD_NAME = "<init>";
public static final String INIT_METHOD_NAME = "$init";
static final String INIT_METHOD_NAME = "$init";
public static final String CTOR_METHOD_PREFIX = "$ctor";
public static final String CLINIT_METHOD_NAME = "$clinit";
public static final String VALUE_OF_METHOD_NAME = "valueOf"; // Boxed type valueOf() method.
public static final String IS_INSTANCE_METHOD_NAME = "$isInstance";
static final String IS_INSTANCE_METHOD_NAME = "$isInstance";
public static final String MARK_IMPLEMENTOR_METHOD_NAME = "$markImplementor";
public static final String CREATE_METHOD_NAME = "$create";
public static final String LOAD_MODULES_METHOD_NAME = "$loadModules";
public static final String COPY_METHOD_NAME = "$copy";
static final String LOAD_MODULES_METHOD_NAME = "$loadModules";
static final String COPY_METHOD_NAME = "$copy";

public static String buildMethodSignature(
String name, TypeDescriptor... parameterTypeDescriptors) {
Expand Down Expand Up @@ -683,7 +683,7 @@ public String getMangledName() {
return getSimpleJsName();
}

if (getOrigin().isSyntheticInstanceOfSupportMember()) {
if (getOrigin().isSyntheticInstanceOfSupportMember() || isCustomIsInstanceMethod()) {
// Class support methods, like $isInstance and $markImplementor, should not be mangled.
return getName();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private static ExpressionStatement createMarkImplementorCall(

/** Synthesizes the $isInstance method on the type. */
private static void synthesizeIsInstanceMethod(Type type) {
if (type.containsMethod(MethodDescriptor.IS_INSTANCE_METHOD_NAME)) {
if (type.containsMethod(MethodDescriptor::isCustomIsInstanceMethod)) {
// User provided an $isInstance method, skip generation.
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,9 @@ private void checkTypeReferences(Type type) {
@Override
public void exitInstanceOfExpression(InstanceOfExpression instanceOfExpression) {
TypeDescriptor testTypeDescriptor = instanceOfExpression.getTestTypeDescriptor();
if (testTypeDescriptor.isNative() && testTypeDescriptor.isInterface()) {
if (testTypeDescriptor.isNative()
&& testTypeDescriptor.isInterface()
&& !((DeclaredTypeDescriptor) testTypeDescriptor).hasCustomIsInstanceMethod()) {
problems.error(
instanceOfExpression.getSourcePosition(),
"Cannot do instanceof against native JsType interface '%s'.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import jsinterop.annotations.JsConstructor;
import jsinterop.annotations.JsFunction;
import jsinterop.annotations.JsMethod;
import jsinterop.annotations.JsOverlay;
import jsinterop.annotations.JsPackage;
import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;
Expand All @@ -47,6 +48,8 @@ public static void testAll() {
testInstanceOf_jsoWithNativeButtonProto();
testInstanceOf_jsoWithoutProto();
testInstanceOf_jsoWithProto();
testInstanceOf_classWithCustomIsInstance();
testInstanceOf_interfaceWithCustomIsInstance();
testInstanceOf_withNameSpace();
testJsMethodWithDifferentVisiblities();
testJsTypeField();
Expand Down Expand Up @@ -74,6 +77,38 @@ static class MyNativeJsTypeSubclass extends MyNativeJsType {
public MyNativeJsTypeSubclass() {}
}

@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Array")
static class MyNativeClassWithCustomIsInstance {
@JsOverlay
static boolean $isInstance(Object o) {
return isCustomIsInstanceClassSingleton(o);
}
}

private static final String CUSTOM_IS_INSTANCE_CLASS_SINGLETON = "CustomIsInstanceClass";

// This method was extracted from $isInstance and ONLY called from there to ensure that
// rta traverses custom isInstance methods.
private static boolean isCustomIsInstanceClassSingleton(Object o) {
return o == CUSTOM_IS_INSTANCE_CLASS_SINGLETON;
}

@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Array")
interface MyNativeInterfaceWithCustomIsInstance {
@JsOverlay
static boolean $isInstance(Object o) {
return isCustomIsInstanceInterfaceSingleton(o);
}
}

private static final String CUSTOM_IS_INSTANCE_INTERFACE_SINGLETON = "CustomIsInstanceInterface";

// This method was extracted from $isInstance and ONLY called from there to ensure that
// rta traverses custom isInstance methods.
private static boolean isCustomIsInstanceInterfaceSingleton(Object o) {
return o == CUSTOM_IS_INSTANCE_INTERFACE_SINGLETON;
}

static class MyNativeJsTypeSubclassWithIterator extends MyNativeJsType implements Iterable {
@JsConstructor
public MyNativeJsTypeSubclassWithIterator() {}
Expand Down Expand Up @@ -262,6 +297,8 @@ private static void testInstanceOf_jsoWithProto() {
assertFalse(object instanceof Iterator);
assertTrue(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof MyNativeClassWithCustomIsInstance);
assertFalse(object instanceof MyNativeInterfaceWithCustomIsInstance);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertTrue(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
Expand All @@ -280,6 +317,8 @@ private static void testInstanceOf_jsoWithoutProto() {
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof MyNativeClassWithCustomIsInstance);
assertFalse(object instanceof MyNativeInterfaceWithCustomIsInstance);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
Expand All @@ -298,6 +337,8 @@ private static void testInstanceOf_jsoWithNativeButtonProto() {
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof MyNativeClassWithCustomIsInstance);
assertFalse(object instanceof MyNativeInterfaceWithCustomIsInstance);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
Expand All @@ -317,6 +358,8 @@ private static void testInstanceOf_implementsJsType() {
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof MyNativeClassWithCustomIsInstance);
assertFalse(object instanceof MyNativeInterfaceWithCustomIsInstance);
assertTrue(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
Expand All @@ -336,6 +379,8 @@ private static void testInstanceOf_implementsJsTypeWithPrototype() {
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertTrue(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof MyNativeClassWithCustomIsInstance);
assertFalse(object instanceof MyNativeInterfaceWithCustomIsInstance);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
Expand All @@ -355,6 +400,8 @@ private static void testInstanceOf_concreteJsType() {
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof MyNativeClassWithCustomIsInstance);
assertFalse(object instanceof MyNativeInterfaceWithCustomIsInstance);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
Expand All @@ -376,6 +423,8 @@ private static void testInstanceOf_extendsJsTypeWithProto() {
assertFalse(object instanceof HTMLButtonElementConcreteNativeJsType);
assertTrue(object instanceof Iterable);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof MyNativeClassWithCustomIsInstance);
assertFalse(object instanceof MyNativeInterfaceWithCustomIsInstance);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertTrue(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
Expand All @@ -384,6 +433,48 @@ private static void testInstanceOf_extendsJsTypeWithProto() {
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl[][]);
}

private static void testInstanceOf_classWithCustomIsInstance() {
Object object = CUSTOM_IS_INSTANCE_CLASS_SINGLETON;

assertTrue(object instanceof Object);
assertTrue(object instanceof String);
assertFalse(object instanceof HTMLElementConcreteNativeJsType);
assertFalse(object instanceof HTMLElementAnotherConcreteNativeJsType);
assertFalse(object instanceof HTMLButtonElementConcreteNativeJsType);
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertTrue(object instanceof MyNativeClassWithCustomIsInstance);
assertFalse(object instanceof MyNativeInterfaceWithCustomIsInstance);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
assertFalse(object instanceof ConcreteJsType);
assertFalse(object instanceof MyNativeJsTypeInterface[]);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl[][]);
}

private static void testInstanceOf_interfaceWithCustomIsInstance() {
Object object = CUSTOM_IS_INSTANCE_INTERFACE_SINGLETON;

assertTrue(object instanceof Object);
assertTrue(object instanceof String);
assertFalse(object instanceof HTMLElementConcreteNativeJsType);
assertFalse(object instanceof HTMLElementAnotherConcreteNativeJsType);
assertFalse(object instanceof HTMLButtonElementConcreteNativeJsType);
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof MyNativeClassWithCustomIsInstance);
assertTrue(object instanceof MyNativeInterfaceWithCustomIsInstance);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
assertFalse(object instanceof ConcreteJsType);
assertFalse(object instanceof MyNativeJsTypeInterface[]);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl[][]);
}

private static void testInstanceOf_withNameSpace() {
Object obj1 = createMyNamespacedJsInterface();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3371,6 +3371,7 @@ public void testCustomIsInstanceSucceeds() {
" Object o = null;",
" boolean b = o instanceof InterfaceWithCustomIsInstance;",
" b = o instanceof ClassWithCustomIsInstance;",
" b = o instanceof NativeInterfaceWithCustomIsInstance;",
" b = o instanceof NativeClassWithCustomIsInstance;",
" }",
"}")
Expand Down

0 comments on commit db9ec79

Please sign in to comment.