Skip to content

Commit

Permalink
Wrap the type parameters of a function in a new type, and handle TTL …
Browse files Browse the repository at this point in the history
…parameters

in function types instead of accessing them through the jsdoc.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=164316838
  • Loading branch information
dimvar committed Aug 7, 2017
1 parent ec39d4f commit b0302fa
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 181 deletions.
29 changes: 0 additions & 29 deletions src/com/google/javascript/jscomp/GlobalTypeInfoCollector.java
Expand Up @@ -577,7 +577,6 @@ private void checkAndFreezeNominalType(RawNominalType rawType) {
superMethodType.toFunctionType().toString(),
localMethodType.toFunctionType().toString()));
}
superMethodType = mayInstantiateGenericsWithUnknown(rawType, pname, superMethodType);
DeclaredFunctionType updatedMethodType =
localMethodType.withTypeInfoFromSuper(superMethodType, getsTypeFromParent);
localPropDef.updateMethodType(updatedMethodType);
Expand Down Expand Up @@ -655,34 +654,6 @@ private void checkAndFreezeNominalType(RawNominalType rawType) {
}
}

/**
* When a class/interface inherits from an ancestor class/interface with a method that uses TTL,
* instantiate the type variables with unknown in the child method.
* Don't bother handling multiple ancestor methods with TTL.
* TODO(dimvar): delete this when we handle TTL inside newtypes/FunctionType.
*/
private DeclaredFunctionType mayInstantiateGenericsWithUnknown(
RawNominalType rawType, String propName, DeclaredFunctionType inheritedPropType) {
Set<NominalType> superInterfaces = rawType.getInterfaces();

NominalType superClass = rawType.getSuperClass();
if (superClass != null) {
JSDocInfo jsdoc = superClass.getPropertyJsdoc(propName);
if (jsdoc != null && !jsdoc.getTypeTransformations().isEmpty()) {
return inheritedPropType.instantiateGenericsWithUnknown();
}
}

for (NominalType superInterface : superInterfaces) {
JSDocInfo jsdoc = superInterface.getPropertyJsdoc(propName);
if (jsdoc != null && !jsdoc.getTypeTransformations().isEmpty()) {
return inheritedPropType.instantiateGenericsWithUnknown();
}
}

return inheritedPropType;
}

// TODO(dimvar): the finalization method and this one should be cleaned up;
// they are very hard to understand.
private void checkSuperProperty(
Expand Down
11 changes: 1 addition & 10 deletions src/com/google/javascript/jscomp/NTIScope.java
Expand Up @@ -146,8 +146,7 @@ void setDeclaredType(DeclaredFunctionType declaredType) {
if (this.root.isFromExterns()) {
this.root.setTypeI(this.commonTypes.fromFunctionType(declaredType.toFunctionType()));
}
if (!getTypeTransformations().isEmpty()) {
Set<String> ttlVars = getTypeTransformations().keySet();
if (!declaredType.getTypeParameters().getTypeTransformations().isEmpty()) {
this.declaredTypeForOwnBody = declaredType.instantiateGenericsWithUnknown();
}
}
Expand All @@ -169,14 +168,6 @@ boolean isTopLevel() {
return parent == null;
}

/**
* Returns a non-null map from TTL variables to their transformations in AST form.
*/
private Map<String, Node> getTypeTransformations() {
JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(this.root);
return jsdoc == null ? ImmutableMap.<String, Node>of() : jsdoc.getTypeTransformations();
}

boolean isConstructor() {
JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(root);
return isFunction() && jsdoc != null && jsdoc.isConstructor();
Expand Down
60 changes: 15 additions & 45 deletions src/com/google/javascript/jscomp/NewTypeInference.java
Expand Up @@ -2025,60 +2025,31 @@ private EnvTypePair analyzeObjLitCastFwd(ObjectLiteralCast cast, Node call, Type
}

/**
* Given the qualified name of a function, find the AST node that defines the function.
*/
private Node getFunctionDeclNodeFromQname(QualifiedName funQname, TypeEnv env) {
if (funQname.isIdentifier()) {
return this.currentScope.isKnownFunction(funQname)
? this.currentScope.getScope(funQname.getLeftmostName()).getRoot() : null;
}
JSType recvType = envGetTypeOfQname(env, funQname.getAllButRightmost());
return recvType == null ? null : recvType.getPropertyDefSite(funQname.getRightmostName());
}

private ImmutableMap<String, Node> getTypeTransformationsOfCallee(Node callee, TypeEnv env) {
if (callee.isQualifiedName()) {
QualifiedName qname = QualifiedName.fromNode(callee);
Node funDeclNode = getFunctionDeclNodeFromQname(qname, env);
if (funDeclNode != null) {
JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(funDeclNode);
if (jsdoc != null && !jsdoc.getTypeTransformations().isEmpty()) {
return jsdoc.getTypeTransformations();
}
}
}
return null;
}

/**
* When calling a generic function that uses TTL, we need to map the TTL variables to types.
* This method finds the TTL expressions, calls TypeTransformation#eval to evaluate them,
* and updates the type map at the generic call.
* Instantiate the generic function using the appropriate type map.
*
* NOTE(dimvar): we only handle TTL when the callee is a qualified name that is the name of
* a TTL function. In other words, we do not treat TTL types as first class; we do not propagate
* them through the type system. If the TTL function is assigned to some other variable and
* then called with that name, we do not detect that.
* We'll handle this in a follow-up CL; it is needed for inheritance (child method w/ @override)
* and possibly for module aliases as well.
* If the generic function uses TTL, we need to map the TTL variables to types.
* In this method, we find the TTL expressions, call TypeTransformation#eval to evaluate them,
* and update the type map.
*/
private ImmutableMap<String, JSType> calcTtlInstantiationFwd(
Node callee, FunctionType calleeType, ImmutableMap<String, JSType> typeMap, TypeEnv env) {
ImmutableMap<String, Node> typeTransformations = getTypeTransformationsOfCallee(callee, env);
if (typeTransformations == null || !this.compiler.getOptions().getUseTTLinNTI()) {
return typeMap;
private FunctionType instantiateCalleeMaybeWithTTL(
FunctionType calleeType, ImmutableMap<String, JSType> typeMap) {
Map<String, Node> typeTransformations = calleeType.getTypeTransformations();
if (typeTransformations.isEmpty()) {
return calleeType.instantiateGenerics(typeMap);
}
if (!this.compiler.getOptions().getUseTTLinNTI()) {
return calleeType.instantiateGenericsWithUnknown();
}
LinkedHashMap<String, JSType> newTypeMap = new LinkedHashMap<>();
newTypeMap.putAll(typeMap);
List<String> calleeTypeParams = calleeType.getTypeParameters();
for (Map.Entry<String, Node> entry : typeTransformations.entrySet()) {
String ttlVar = UniqueNameGenerator.findGeneratedName(entry.getKey(), calleeTypeParams);
String ttlVar = entry.getKey();
Node transform = entry.getValue();
@SuppressWarnings({"unchecked", "rawtypes"})
JSType t = (JSType) this.ttlObj.eval(transform, (ImmutableMap) typeMap);
newTypeMap.put(ttlVar, t);
}
return ImmutableMap.copyOf(newTypeMap);
return calleeType.instantiateGenerics(ImmutableMap.copyOf(newTypeMap));
}

private EnvTypePair analyzeInvocationFwd(
Expand Down Expand Up @@ -2151,8 +2122,7 @@ private EnvTypePair analyzeInvocationFwd(
Node firstArg = expr.getSecondChild();
ImmutableMap<String, JSType> typeMap =
calcTypeInstantiationFwd(expr, receiver, firstArg, funType, envAfterCallee);
typeMap = calcTtlInstantiationFwd(callee, funType, typeMap, calleePair.env);
funType = funType.instantiateGenerics(typeMap);
funType = instantiateCalleeMaybeWithTTL(funType, typeMap);
println("Instantiated function type: ", funType);
}
// argTypes collects types of actuals for deferred checks.
Expand Down
Expand Up @@ -20,7 +20,6 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.collect.ImmutableList;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -49,8 +48,7 @@ public final class DeclaredFunctionType implements Serializable {
// If this DeclaredFunctionType is a prototype method, this field stores the
// type of the instance.
private final JSType receiverType;
// Non-empty iff this function has an @template annotation
private final ImmutableList<String> typeParameters;
private final TypeParameters typeParameters;

private final JSTypes commonTypes;
private final boolean isAbstract;
Expand All @@ -63,7 +61,7 @@ private DeclaredFunctionType(
JSType retType,
JSType nominalType,
JSType receiverType,
ImmutableList<String> typeParameters,
TypeParameters typeParameters,
boolean isAbstract) {
checkArgument(retType == null || !retType.isBottom());
checkNotNull(commonTypes);
Expand Down Expand Up @@ -103,17 +101,14 @@ static DeclaredFunctionType make(
JSType retType,
JSType nominalType,
JSType receiverType,
ImmutableList<String> typeParameters,
TypeParameters typeParameters,
boolean isAbstract) {
if (requiredFormals == null) {
requiredFormals = new ArrayList<>();
}
if (optionalFormals == null) {
optionalFormals = new ArrayList<>();
}
if (typeParameters == null) {
typeParameters = ImmutableList.of();
}
return new DeclaredFunctionType(
commonTypes,
requiredFormals, optionalFormals, restFormals, retType,
Expand Down Expand Up @@ -194,8 +189,8 @@ public boolean isGeneric() {
return !typeParameters.isEmpty();
}

public ImmutableList<String> getTypeParameters() {
return typeParameters;
public TypeParameters getTypeParameters() {
return this.typeParameters;
}

public boolean isAbstract() {
Expand All @@ -207,7 +202,7 @@ public boolean isTypeVariableDefinedLocally(String tvar) {
}

public String getTypeVariableDefinedLocally(String tvar) {
String tmp = UniqueNameGenerator.findGeneratedName(tvar, this.typeParameters);
String tmp = UniqueNameGenerator.findGeneratedName(tvar, this.typeParameters.asList());
if (tmp != null) {
return tmp;
}
Expand Down Expand Up @@ -306,7 +301,7 @@ private FunctionTypeBuilder substituteGenerics(Map<String, JSType> typeMap) {
// Before we switched to unique generated names for type variables, a method's type variables
// could shadow type variables defined on the class. Check that this no longer happens.
if (!this.commonTypes.MAP_TO_UNKNOWN.equals(typeMap)) {
for (String typeParam : this.typeParameters) {
for (String typeParam : this.typeParameters.asList()) {
checkState(!typeMap.containsKey(typeParam));
}
}
Expand Down
45 changes: 25 additions & 20 deletions src/com/google/javascript/jscomp/newtypes/FunctionType.java
Expand Up @@ -16,6 +16,7 @@

package com.google.javascript.jscomp.newtypes;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
Expand All @@ -29,6 +30,7 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.TypeI;
import java.io.Serializable;
import java.util.ArrayList;
Expand Down Expand Up @@ -60,10 +62,10 @@ public final class FunctionType implements Serializable {
// If this FunctionType is a prototype method, this field stores the
// type of the instance.
private final JSType receiverType;
// Non-empty iff this function has an @template annotation. Note that a function type can have
// type variables as formal parameters and still have empty typeParameters, eg, a method without
// @template defined on a generic class.
private final ImmutableList<String> typeParameters;
// Set to TypeParameters.EMPTY for a function without a @template annotation.
// Note that a function type can have type variables as formal parameters and still have empty
// typeParameters, e.g., a method without @template defined on a generic class.
private final TypeParameters typeParameters;
private static final boolean DEBUGGING = false;

private FunctionType(
Expand All @@ -75,7 +77,7 @@ private FunctionType(
JSType nominalType,
JSType receiverType,
ImmutableMap<String, JSType> outerVars,
ImmutableList<String> typeParameters,
TypeParameters typeParameters,
boolean isLoose,
boolean isAbstract) {
checkNotNull(commonTypes);
Expand Down Expand Up @@ -109,7 +111,7 @@ private FunctionType(JSTypes commonTypes, boolean isLoose) {
this.nominalType = null;
this.receiverType = null;
this.outerVarPreconditions = null;
this.typeParameters = ImmutableList.of();
this.typeParameters = TypeParameters.EMPTY;
this.isLoose = isLoose;
this.isAbstract = false;
}
Expand Down Expand Up @@ -141,6 +143,7 @@ private void checkValid() {
}
checkState(restFormals == null || !restFormals.isBottom());
checkNotNull(returnType);
checkNotNull(typeParameters);
}

/** Returns the JSTypes instance stored by this object. */
Expand Down Expand Up @@ -199,7 +202,7 @@ static FunctionType normalized(
JSType nominalType,
JSType receiverType,
Map<String, JSType> outerVars,
ImmutableList<String> typeParameters,
TypeParameters typeParameters,
boolean isLoose,
boolean isAbstract) {
if (requiredFormals == null) {
Expand All @@ -211,9 +214,6 @@ static FunctionType normalized(
if (outerVars == null) {
outerVars = ImmutableMap.of();
}
if (typeParameters == null) {
typeParameters = ImmutableList.of();
}
if (restFormals != null) {
// Remove trailing optional params w/ type equal to restFormals
for (int i = optionalFormals.size() - 1; i >= 0; i--) {
Expand All @@ -230,7 +230,7 @@ static FunctionType normalized(
ImmutableList.copyOf(optionalFormals),
restFormals, retType, nominalType, receiverType,
ImmutableMap.copyOf(outerVars),
typeParameters,
firstNonNull(typeParameters, TypeParameters.EMPTY),
isLoose,
isAbstract);
}
Expand Down Expand Up @@ -467,7 +467,7 @@ private FunctionTypeBuilder transformCallApplyHelper() {
}
NominalType nt = this.receiverType.getNominalTypeIfSingletonObj();
if (nt != null && nt.isUninstantiatedGenericType()) {
builder.addTypeParameters(nt.getTypeParameters());
builder.addTypeParameters(TypeParameters.make(nt.getTypeParameters()));
NominalType ntWithIdentity = nt.instantiateGenericsWithIdentity();
builder.addReqFormal(JSType.fromObjectType(ObjectType.fromNominalType(ntWithIdentity)));
} else {
Expand Down Expand Up @@ -1010,7 +1010,12 @@ public boolean isGeneric() {
* instantiated, the type parameters have already been substituted away.)
*/
public List<String> getTypeParameters() {
return typeParameters;
return this.typeParameters.asList();
}

/** Always returns a non-null map. */
public Map<String, Node> getTypeTransformations() {
return this.typeParameters.getTypeTransformations();
}

/**
Expand Down Expand Up @@ -1114,7 +1119,7 @@ boolean unifyWithSubtype(FunctionType other, List<String> typeParameters,
other.returnType, typeParameters, typeMultimap, subSuperMap);
}

private FunctionType instantiateGenericsWithUnknown() {
public FunctionType instantiateGenericsWithUnknown() {
if (!isGeneric()) {
return this;
}
Expand Down Expand Up @@ -1243,7 +1248,7 @@ private FunctionType substituteNominalGenerics(Map<String, JSType> typeMap) {
if (!this.commonTypes.MAP_TO_UNKNOWN.equals(typeMap)) {
// Before we switched to unique generated names for type variables, a method's type variables
// could shadow type variables defined on the class. Check that this no longer happens.
for (String typeParam : this.typeParameters) {
for (String typeParam : this.typeParameters.asList()) {
checkState(!typeMap.containsKey(typeParam));
}
}
Expand Down Expand Up @@ -1335,8 +1340,8 @@ public FunctionType instantiateGenericsFromArgumentTypes(JSType recvtype, List<J
}
Multimap<String, JSType> typeMultimap = LinkedHashMultimap.create();
if (recvtype != null
&& !getThisType()
.unifyWithSubtype(recvtype, typeParameters, typeMultimap, SubtypeCache.create())) {
&& !getThisType().unifyWithSubtype(
recvtype, typeParameters.asList(), typeMultimap, SubtypeCache.create())) {
return null;
}
for (int i = 0, size = argTypes.size(); i < size; i++) {
Expand All @@ -1345,12 +1350,12 @@ public FunctionType instantiateGenericsFromArgumentTypes(JSType recvtype, List<J
continue;
}
if (!this.getFormalType(i).unifyWithSubtype(
argType, typeParameters, typeMultimap, SubtypeCache.create())) {
argType, typeParameters.asList(), typeMultimap, SubtypeCache.create())) {
return null;
}
}
ImmutableMap.Builder<String, JSType> builder = ImmutableMap.builder();
for (String typeParam : typeParameters) {
for (String typeParam : typeParameters.asList()) {
Collection<JSType> types = typeMultimap.get(typeParam);
if (types.size() > 1) {
return null;
Expand Down Expand Up @@ -1453,7 +1458,7 @@ public StringBuilder appendTo(StringBuilder builder, ToStringContext ctx) {
}
if (!this.typeParameters.isEmpty()) {
builder.append("<");
builder.append(Joiner.on(",").join(getPrettyTypeParams(this.typeParameters, ctx)));
builder.append(Joiner.on(",").join(getPrettyTypeParams(this.typeParameters.asList(), ctx)));
builder.append(">");
}
builder.append("function(");
Expand Down

0 comments on commit b0302fa

Please sign in to comment.