Skip to content

Commit

Permalink
Add support for @this annotation in the JsInterop-generator and clean…
Browse files Browse the repository at this point in the history
… up elemental2 diff that were removing @this annotation.

We differentiate two cases:
- @this {THIS} @template THIS: this is used in closure type system to reflect the class used when a method is called. This pattern is used for allowing subclasses methods chaining.
When this pattern is used, we will just use the enclosing type of the
method as return type. This is the current behavior we have with the diff solution.
A better solution would be to override the method by specializing the return type on each known subclasses.

- Generic methods: Generic methods use @this for redefining the minimum contract that has to be satisfied by the instance the method is called on. For that purpose, they also redefine the template types at method level. For the java conversion purpose, we ignore the @this annotation and we don't redefine the template type (used in @this annotation) at the method level. I've also created google/elemental2#107 that propose a possible improvement for generic methods

PiperOrigin-RevId: 245975596
  • Loading branch information
jDramaix authored and copybara-github committed Apr 30, 2019
1 parent 0b9ff4d commit fadccf4
Show file tree
Hide file tree
Showing 10 changed files with 403 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import jsinterop.generator.closure.visitor.EnumMemberCollector;
import jsinterop.generator.closure.visitor.InheritanceVisitor;
import jsinterop.generator.closure.visitor.MemberCollector;
import jsinterop.generator.closure.visitor.ThisTemplateTypeVisitor;
import jsinterop.generator.closure.visitor.TypeCollector;
import jsinterop.generator.closure.visitor.TypeParameterCollector;
import jsinterop.generator.helper.GeneratorUtils;
Expand Down Expand Up @@ -144,6 +145,8 @@ private Program generateJavaProgram() {

new AnonymousTypeCollector(ctx).accept(topScope);

new ThisTemplateTypeVisitor(ctx).accept(topScope);

new MemberCollector(ctx).accept(topScope);

new EnumMemberCollector(ctx).accept(topScope);
Expand Down
123 changes: 123 additions & 0 deletions java/jsinterop/generator/closure/helper/AbstractNoOpVisitor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright 2019 Google Inc.
*
* 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
*
* https://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 jsinterop.generator.closure.helper;

import com.google.javascript.rhino.jstype.EnumElementType;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.NamedType;
import com.google.javascript.rhino.jstype.NoType;
import com.google.javascript.rhino.jstype.ObjectType;
import com.google.javascript.rhino.jstype.ProxyObjectType;
import com.google.javascript.rhino.jstype.TemplateType;
import com.google.javascript.rhino.jstype.TemplatizedType;
import com.google.javascript.rhino.jstype.UnionType;
import com.google.javascript.rhino.jstype.Visitor;

/**
* Nominal implementation of Visitor<T> returning null for every methods. The purpose of this class
* is to reduce boiler plate code when we are interested to visit small number of kind of types.
*/
public abstract class AbstractNoOpVisitor<T> implements Visitor<T> {
@Override
public T caseNoType(NoType type) {
return null;
}

@Override
public T caseEnumElementType(EnumElementType type) {
return null;
}

@Override
public T caseAllType() {
return null;
}

@Override
public T caseBooleanType() {
return null;
}

@Override
public T caseNoObjectType() {
return null;
}

@Override
public T caseFunctionType(FunctionType type) {
return null;
}

@Override
public T caseObjectType(ObjectType type) {
return null;
}

@Override
public T caseUnknownType() {
return null;
}

@Override
public T caseNullType() {
return null;
}

@Override
public T caseNamedType(NamedType type) {
return null;
}

@Override
public T caseProxyObjectType(ProxyObjectType type) {
return null;
}

@Override
public T caseNumberType() {
return null;
}

@Override
public T caseStringType() {
return null;
}

@Override
public T caseSymbolType() {
return null;
}

@Override
public T caseVoidType() {
return null;
}

@Override
public T caseUnionType(UnionType type) {
return null;
}

@Override
public T caseTemplatizedType(TemplatizedType type) {
return null;
}

@Override
public T caseTemplateType(TemplateType templateType) {
return null;
}
}
91 changes: 66 additions & 25 deletions java/jsinterop/generator/closure/helper/ClosureTypeRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@
import com.google.javascript.rhino.jstype.TemplateType;
import com.google.javascript.rhino.jstype.TemplatizedType;
import com.google.javascript.rhino.jstype.UnionType;
import com.google.javascript.rhino.jstype.Visitor;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import jsinterop.generator.helper.AbstractTypeRegistry;
import jsinterop.generator.model.ArrayTypeReference;
import jsinterop.generator.model.JavaTypeReference;
Expand All @@ -59,6 +60,8 @@
/** Implementation of {@link AbstractTypeRegistry} specific to closure. */
public class ClosureTypeRegistry extends AbstractTypeRegistry<JSType> {

private final Map<TemplateType, JSType> jsTypeByThisTemplateType = new HashMap<>();

public ClosureTypeRegistry() {
// JsCompiler considers two different RecordType with the same structure as equivalent in terms
// of equals() and hashcode() even if they are associated to two different symbols.
Expand Down Expand Up @@ -86,17 +89,25 @@ public TypeReference createTypeReference(JSType jsType) {
}

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

/**
* Create a mapping between a template variable used as receiver type of a method (with @this
* annotation) and the type defining the method.
*/
public void registerThisTemplateType(TemplateType thisTemplateType, JSType jsType) {
jsTypeByThisTemplateType.put(thisTemplateType, jsType);
}

private class TypeReferenceCreator implements Visitor<TypeReference> {
private class TypeReferenceCreator extends AbstractNoOpVisitor<TypeReference> {
private final ReferenceContext referenceContext;

TypeReferenceCreator(ReferenceContext referenceContext) {
this.referenceContext = referenceContext;
}

private TypeReference visit(JSType type) {
private TypeReference resolveTypeReference(JSType type) {
if (type.isVoidType() || type.isNullType()) {
return type.visit(this);
}
Expand All @@ -107,6 +118,10 @@ private TypeReference visit(JSType type) {
return type.restrictByNotNullOrUndefined().visit(this);
}

private List<TypeReference> resolveTypeReferences(List<? extends JSType> types) {
return types.stream().map(this::resolveTypeReference).collect(toList());
}

@Override
public TypeReference caseNoType(NoType type) {
return OBJECT;
Expand Down Expand Up @@ -139,7 +154,8 @@ public TypeReference caseFunctionType(FunctionType type) {
return new ParametrizedTypeReference(
PredefinedTypeReference.JS_CONSTRUCTOR_FN,
ImmutableList.of(
WildcardTypeReference.createWildcardUpperBound(visit(type.getTypeOfThis()))));
WildcardTypeReference.createWildcardUpperBound(
resolveTypeReference(type.getTypeOfThis()))));
}
return new JavaTypeReference(checkNotNull(getJavaType(type)));
}
Expand All @@ -154,17 +170,12 @@ public TypeReference caseUnknownType() {
return OBJECT;
}

@Override
public TypeReference caseNullType() {
return null;
}

@Override
public TypeReference caseNamedType(NamedType type) {
// Reference to undefined types are wrapped in a NamedType with a reference to UnknownType.
checkState(!type.isNoResolvedType(), "Type %s is unknown", type.getReferenceName());

return visit(type.getReferencedType());
return resolveTypeReference(type.getReferencedType());
}

@Override
Expand Down Expand Up @@ -196,7 +207,7 @@ public TypeReference caseVoidType() {
@Override
public TypeReference caseUnionType(UnionType type) {
return new UnionTypeReference(
type.getAlternates().stream().map(this::visit).collect(toList()));
type.getAlternates().stream().map(this::resolveTypeReference).collect(toList()));
}

@Override
Expand All @@ -207,35 +218,65 @@ public TypeReference caseTemplatizedType(TemplatizedType type) {
arrayType =
new TypeReferenceCreator(
referenceContext == IN_HERITAGE_CLAUSE ? IN_TYPE_ARGUMENTS : REGULAR)
.visit(type.getTemplateTypes().get(0));
.resolveTypeReference(type.getTemplateTypes().get(0));
}
if (referenceContext == IN_HERITAGE_CLAUSE) {
// In java you cannot extends classic array. In this case create a parametrized reference
// to JsArray class.
return new ParametrizedTypeReference(
visit(type.getReferencedType()), newArrayList(arrayType));
resolveTypeReference(type.getReferencedType()), newArrayList(arrayType));
} else {
// Convert array type to classic java array where it's valid.
return new ArrayTypeReference(arrayType);
}
}

TypeReference templatizedType = visit(type.getReferencedType());

TypeReferenceCreator typeArgumentsReferenceCreator =
new TypeReferenceCreator(IN_TYPE_ARGUMENTS);
List<TypeReference> templates =
type.getTemplateTypes()
.stream()
.map(typeArgumentsReferenceCreator::visit)
.collect(toList());

return new ParametrizedTypeReference(templatizedType, templates);
return createParametrizedTypeReference(type.getReferencedType(), type.getTemplateTypes());
}

@Override
public TypeReference caseTemplateType(TemplateType templateType) {
if (jsTypeByThisTemplateType.containsKey(templateType)) {
// The templateType is used in @this annotations in order to allow method chaining.
// Replace it by the type where the method is defined.
return createMethodDefiningTypeReferenceFrom(templateType);
}

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

private TypeReference createMethodDefiningTypeReferenceFrom(TemplateType templateType) {
JSType jsType = jsTypeByThisTemplateType.get(templateType);
checkNotNull(jsType, "%s is not used a method receiver.", templateType);

TypeReference typeReference = resolveTypeReference(jsType);
List<TemplateType> templateKeys =
(jsType instanceof ObjectType ? ((ObjectType) jsType).getConstructor() : jsType)
.getTemplateTypeMap()
.getTemplateKeys();
// Create a ParametrizedTypeReference if the replacing type has template type. The JSType
// stored in the map is never a TemplatizedType because it's the type definition not a type
// reference.
if (templateKeys.isEmpty()) {
return typeReference;
} else if (jsType.isArrayType()) {
// JsCompiler uses its own built-in definition of Array Type using two type parameters:
// Array<IObject#Value, T>
checkState(templateKeys.size() == 2);
return new ArrayTypeReference(resolveTypeReference(templateKeys.get(1)));
} else {
return createParametrizedTypeReference(jsType, templateKeys);
}
}

private ParametrizedTypeReference createParametrizedTypeReference(
JSType referencedType, List<? extends JSType> templatesTypes) {
TypeReference templatizedType = resolveTypeReference(referencedType);

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

return new ParametrizedTypeReference(templatizedType, templates);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2019 Google Inc.
*
* 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
*
* https://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 jsinterop.generator.closure.visitor;

import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.TemplateType;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Optional;
import jsinterop.generator.closure.helper.GenerationContext;

/**
* Visit methods scanning for @this annotations that declare the receiver type to be just a template
* variable defined in the method.
*
* <p>In the Closure type system, @this annotation can be used on a method to specify (or customize)
* the type of the instance the method is called on. Because the template variable can be any type,
* this pattern is useful for allowing seamless fluent style method even in subclasses.
*
* <p>This visitor is used to record these type variables so that they can be handled in a
* meaningful way by the generator.
*/
public class ThisTemplateTypeVisitor extends AbstractClosureVisitor {
private final Deque<JSType> currentJsTypeStack = new ArrayDeque<>();

public ThisTemplateTypeVisitor(GenerationContext context) {
super(context);
}

@Override
protected boolean visitClassOrInterface(FunctionType type) {
currentJsTypeStack.push(type.getInstanceType());
return true;
}

@Override
protected void endVisitClassOrInterface(FunctionType type) {
currentJsTypeStack.pop();
}

@Override
protected boolean visitMethod(FunctionType method, boolean isStatic) {
Optional<TemplateType> thisTemplateType = getThisTemplateType(method);
thisTemplateType.ifPresent(
templateType ->
getJavaTypeRegistry()
.registerThisTemplateType(templateType, currentJsTypeStack.peek()));
return true;
}

private Optional<TemplateType> getThisTemplateType(FunctionType type) {
return type.getTypeOfThis().isTemplateType()
? Optional.of(type.getTypeOfThis().toMaybeTemplateType())
: Optional.empty();
}
}
Loading

0 comments on commit fadccf4

Please sign in to comment.