Skip to content

Commit

Permalink
Initial support for nullable types.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 698112982
  • Loading branch information
mollyibot authored and copybara-github committed Nov 19, 2024
1 parent 3d0c35b commit df52e9a
Show file tree
Hide file tree
Showing 49 changed files with 569 additions and 299 deletions.
63 changes: 36 additions & 27 deletions java/jsinterop/generator/closure/helper/ClosureTypeRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ public TypeReference createTypeReference(JSType jsType) {
}

public TypeReference createTypeReference(JSType jsType, ReferenceContext referenceContext) {
return new TypeReferenceCreator(referenceContext).resolveTypeReference(jsType);
return new TypeReferenceCreator(referenceContext, jsType.isNullable())
.resolveTypeReference(jsType);
}

/**
Expand All @@ -102,19 +103,17 @@ public void registerThisTemplateType(TemplateType thisTemplateType, JSType jsTyp

private class TypeReferenceCreator extends AbstractNoOpVisitor<TypeReference> {
private final ReferenceContext referenceContext;
private final boolean isNullable;

TypeReferenceCreator(ReferenceContext referenceContext) {
TypeReferenceCreator(ReferenceContext referenceContext, boolean isNullable) {
this.referenceContext = referenceContext;
this.isNullable = isNullable;
}

private TypeReference resolveTypeReference(JSType type) {
if (type.isVoidType() || type.isNullType()) {
return type.visit(this);
}

// Nullable type and optional type are represented in closure by an union type with
// respectively Null and Undefined. We don't use this information (yet), so we remove Null
// or Undefined before to visit the type.
return type.restrictByNotNullOrUndefined().visit(this);
}

Expand All @@ -124,56 +123,58 @@ private List<TypeReference> resolveTypeReferences(List<? extends JSType> types)

@Override
public TypeReference caseNoType(NoType type) {
return OBJECT.getReference();
return OBJECT.getReference(isNullable);
}

@Override
public TypeReference caseEnumElementType(EnumElementType type) {
return new JavaTypeReference(checkNotNull(getJavaType(type)));
return new JavaTypeReference(checkNotNull(getJavaType(type)), isNullable);
}

@Override
public TypeReference caseAllType() {
return OBJECT.getReference();
return OBJECT.getReference(isNullable);
}

@Override
public TypeReference caseBooleanType() {
return (referenceContext == REGULAR ? BOOLEAN : BOOLEAN_OBJECT).getReference();
return (referenceContext == REGULAR && !isNullable ? BOOLEAN : BOOLEAN_OBJECT)
.getReference(isNullable);
}

@Override
public TypeReference caseNoObjectType() {
return OBJECT.getReference();
return OBJECT.getReference(isNullable);
}

@Override
public TypeReference caseFunctionType(FunctionType type) {
if (type.isConstructor() && type.getDisplayName() == null) {
// We use upper bounded wildcard type because constructor function are always producers.
return new ParametrizedTypeReference(
JS_CONSTRUCTOR_FN.getReference(),
JS_CONSTRUCTOR_FN.getReference(isNullable),
ImmutableList.of(
WildcardTypeReference.createWildcardUpperBound(
resolveTypeReference(type.getTypeOfThis()))));
new TypeReferenceCreator(IN_TYPE_ARGUMENTS, false)
.resolveTypeReference(type.getTypeOfThis()))));
}
return new JavaTypeReference(checkNotNull(getJavaType(type)));
return new JavaTypeReference(checkNotNull(getJavaType(type)), isNullable);
}

@Override
public TypeReference caseObjectType(ObjectType type) {
String typeFqn = type.getNormalizedReferenceName();

if (PredefinedTypes.isPredefinedType(typeFqn)) {
return PredefinedTypes.getPredefinedType(typeFqn).getReference();
return PredefinedTypes.getPredefinedType(typeFqn).getReference(isNullable);
}

return new JavaTypeReference(checkNotNull(getJavaType(type)));
return new JavaTypeReference(checkNotNull(getJavaType(type)), isNullable);
}

@Override
public TypeReference caseUnknownType() {
return OBJECT.getReference();
return OBJECT.getReference(isNullable);
}

@Override
Expand All @@ -188,33 +189,40 @@ public TypeReference caseProxyObjectType(ProxyObjectType type) {

@Override
public TypeReference caseNumberType() {
return (referenceContext == REGULAR ? DOUBLE : DOUBLE_OBJECT).getReference();
return (referenceContext == REGULAR && !isNullable ? DOUBLE : DOUBLE_OBJECT)
.getReference(isNullable);
}

@Override
public TypeReference caseBigIntType() {
return JS_BIGINT.getReference();
return JS_BIGINT.getReference(isNullable);
}

@Override
public TypeReference caseStringType() {
return STRING.getReference();
return STRING.getReference(isNullable);
}

@Override
public TypeReference caseSymbolType() {
// TODO(b/73255220): add support for symbol type.
return OBJECT.getReference();
return OBJECT.getReference(isNullable);
}

@Override
public TypeReference caseVoidType() {
return (referenceContext == REGULAR ? VOID : VOID_OBJECT).getReference();
return (referenceContext == REGULAR && !isNullable ? VOID : VOID_OBJECT)
.getReference(isNullable);
}

@Override
public TypeReference caseUnionType(UnionType type) {
return new UnionTypeReference(resolveTypeReferences(type.getAlternates()));
return new UnionTypeReference(
// In our type model the UnionTypeReference will carry the nullability information. The
// alternates are always considered as non-nullable.
new TypeReferenceCreator(referenceContext, false)
.resolveTypeReferences(type.getAlternates()),
isNullable);
}

@Override
Expand All @@ -230,7 +238,7 @@ public TypeReference caseTemplateType(TemplateType templateType) {
return createMethodDefiningTypeReferenceFrom(templateType);
}

return new TypeVariableReference(templateType.getReferenceName(), null);
return new TypeVariableReference(templateType.getReferenceName(), null, false);
}

private TypeReference createMethodDefiningTypeReferenceFrom(TemplateType templateType) {
Expand All @@ -249,7 +257,9 @@ private TypeReference createMethodDefiningTypeReferenceFrom(TemplateType templat
return typeReference;
} else if (jsType.isArrayType()) {
checkState(templateKeys.size() == 1, templateKeys);
return new ArrayTypeReference(resolveTypeReference(templateKeys.get(0)));
TypeReference mainTypeReference = resolveTypeReference(templateKeys.get(0));

return new ArrayTypeReference(mainTypeReference);
} else {
return createParametrizedTypeReference(jsType, templateKeys);
}
Expand All @@ -260,8 +270,7 @@ private ParametrizedTypeReference createParametrizedTypeReference(
TypeReference templatizedType = resolveTypeReference(referencedType);

List<TypeReference> templates =
new TypeReferenceCreator(IN_TYPE_ARGUMENTS).resolveTypeReferences(templatesTypes);

new TypeReferenceCreator(IN_TYPE_ARGUMENTS, false).resolveTypeReferences(templatesTypes);
return new ParametrizedTypeReference(templatizedType, templates);
}
}
Expand Down
8 changes: 4 additions & 4 deletions java/jsinterop/generator/helper/ModelHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,9 @@ public static Type createApiExtensionType(
castMethod.setBody(
new ReturnStatement(
MethodInvocation.builder()
.setInvocationTarget(new TypeQualifier(JS.getReference()))
.setInvocationTarget(new TypeQualifier(JS.getReference(false)))
.setMethodName("cast")
.setArgumentTypes(OBJECT.getReference())
.setArgumentTypes(OBJECT.getReference(false))
.setArguments(new LiteralExpression("o"))
.build()));

Expand Down Expand Up @@ -309,9 +309,9 @@ public static Expression callUncheckedCast(
// will generate: Js.<$originalParameter.type>uncheckedCast($overloadParameter.name)
// We need to add the local type argument to ensure to call the original method.
return MethodInvocation.builder()
.setInvocationTarget(new TypeQualifier(JS.getReference()))
.setInvocationTarget(new TypeQualifier(JS.getReference(false)))
.setMethodName("uncheckedCast")
.setArgumentTypes(OBJECT.getReference())
.setArgumentTypes(OBJECT.getReference(false))
.setArguments(new LiteralExpression(overloadParameter.getName()))
.setLocalTypeArguments(
originalParameter.isVarargs()
Expand Down
4 changes: 4 additions & 0 deletions java/jsinterop/generator/model/AbstractTypeReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
/** Abstract implementation of TypeReference */
@Visitable
public abstract class AbstractTypeReference extends TypeReference {
public AbstractTypeReference(boolean isNullable) {
super(isNullable);
}

@Override
public final boolean equals(Object o) {
if (this == o) {
Expand Down
3 changes: 2 additions & 1 deletion java/jsinterop/generator/model/AnnotationType.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ public enum AnnotationType {
JS_FUNCTION(PredefinedTypes.JS_FUNCTION, false),
JS_OVERLAY(PredefinedTypes.JS_OVERLAY, false),
DEPRECATED(PredefinedTypes.DEPRECATED, false),
NULLABLE(PredefinedTypes.NULLABLE, false),
FUNCTIONAL_INTERFACE(PredefinedTypes.FUNCTIONAL_INTERFACE, false);

private final TypeReference type;
private final boolean isJsInteropTypeAnnotation;

AnnotationType(PredefinedTypes type, boolean isJsInteropTypeAnnotation) {
this.type = type.getReference();
this.type = type.getReference(false);
this.isJsInteropTypeAnnotation = isJsInteropTypeAnnotation;
}

Expand Down
16 changes: 15 additions & 1 deletion java/jsinterop/generator/model/ArrayTypeReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,24 @@ public class ArrayTypeReference extends TypeReference implements DelegableTypeRe
@Visitable TypeReference arrayType;

public ArrayTypeReference(TypeReference arrayType) {
super(false);
this.arrayType = arrayType;
}

public ArrayTypeReference() {}
@Override
public boolean isNullable() {
return arrayType.isNullable();
}

@Override
public TypeReference toNonNullableTypeReference() {
return new ArrayTypeReference(this.arrayType.toNonNullableTypeReference());
}

@Override
public TypeReference toNullableTypeReference() {
return new ArrayTypeReference(this.arrayType.toNullableTypeReference());
}

@Override
public String getImport() {
Expand Down
15 changes: 15 additions & 0 deletions java/jsinterop/generator/model/JavaTypeReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public class JavaTypeReference extends AbstractTypeReference {
public String comment;

public JavaTypeReference(Type typeDeclaration) {
this(typeDeclaration, false);
}

public JavaTypeReference(Type typeDeclaration, boolean isNullable) {
super(isNullable);
this.typeDeclaration = typeDeclaration;
}

Expand Down Expand Up @@ -65,6 +70,16 @@ public String getJniSignature() {
return "L" + typeDeclaration.getJavaFqn().replace('.', '/') + ";";
}

@Override
public TypeReference toNonNullableTypeReference() {
return new JavaTypeReference(this.typeDeclaration, false);
}

@Override
public TypeReference toNullableTypeReference() {
return new JavaTypeReference(this.typeDeclaration, true);
}

@Override
public Type getTypeDeclaration() {
return typeDeclaration;
Expand Down
18 changes: 18 additions & 0 deletions java/jsinterop/generator/model/ParametrizedTypeReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class ParametrizedTypeReference extends AbstractTypeReference

public ParametrizedTypeReference(
TypeReference mainType, Collection<TypeReference> actualTypeArguments) {
super(false);
this.mainType = mainType;
setActualTypeArguments(actualTypeArguments);
}
Expand All @@ -44,6 +45,23 @@ public List<TypeReference> getActualTypeArguments() {
return actualTypeArguments;
}

@Override
public boolean isNullable() {
return mainType.isNullable();
}

@Override
public TypeReference toNonNullableTypeReference() {
return new ParametrizedTypeReference(
this.mainType.toNonNullableTypeReference(), this.getActualTypeArguments());
}

@Override
public TypeReference toNullableTypeReference() {
return new ParametrizedTypeReference(
this.mainType.toNullableTypeReference(), this.getActualTypeArguments());
}

@Override
public String getImport() {
return mainType.getImport();
Expand Down
30 changes: 27 additions & 3 deletions java/jsinterop/generator/model/PredefinedTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static jsinterop.generator.model.LiteralExpression.NULL;
import static jsinterop.generator.model.LiteralExpression.ZERO;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import java.util.Arrays;
import java.util.function.Function;
Expand All @@ -41,6 +42,7 @@ public enum PredefinedTypes {
STRING("java.lang.String", "string"),
CLASS("java.lang.Class", null),
DEPRECATED("java.lang.Deprecated", "deprecated"),
NULLABLE("org.jspecify.annotations.Nullable", null),
JS_ENUM("jsinterop.annotations.JsEnum", null),
JS_TYPE("jsinterop.annotations.JsType", null),
JS_PROPERTY("jsinterop.annotations.JsProperty", null),
Expand Down Expand Up @@ -117,15 +119,27 @@ public static PredefinedTypes getPredefinedType(String nativeFqn) {
this.isNativeJsTypeInterface = isNativeJsTypeInterface;
}

public TypeReference getReference() {
return new PredefinedTypeReference(this);
public static boolean isPrimitiveTypeReference(TypeReference typeReference) {
return (typeReference instanceof PredefinedTypeReference)
&& ((PredefinedTypeReference) typeReference).predefinedType.isPrimitive();
}

private boolean isPrimitive() {
return typeImport == null;
}

public TypeReference getReference(boolean isNullable) {
return new PredefinedTypeReference(this, isNullable);
}

private static class PredefinedTypeReference extends TypeReference {
private final PredefinedTypes predefinedType;

private PredefinedTypeReference(PredefinedTypes predefinedType) {
private PredefinedTypeReference(PredefinedTypes predefinedType, boolean isNullable) {
super(isNullable);
this.predefinedType = predefinedType;
// Nullable reference to a primitive type is not allowed.
Preconditions.checkState(!isNullable || !predefinedType.isPrimitive());
}

@Override
Expand Down Expand Up @@ -158,6 +172,16 @@ public String getJniSignature() {
return predefinedType.typeSignature;
}

@Override
public TypeReference toNonNullableTypeReference() {
return new PredefinedTypeReference(this.predefinedType, false);
}

@Override
public TypeReference toNullableTypeReference() {
return new PredefinedTypeReference(this.predefinedType, true);
}

@Override
public Expression getDefaultValue() {
return predefinedType.defaultValue;
Expand Down
Loading

0 comments on commit df52e9a

Please sign in to comment.