Skip to content

Commit

Permalink
Support basic ES6 class syntax in typed scope creation
Browse files Browse the repository at this point in the history
So far, we only support the class and extends keyword and an optional constructor method.  All other methods are ignored and will probably cause an error if left untranspiled (since TypedScopeCreator will not annotate them with types).

This should unblock work on class visibility.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=199388038
  • Loading branch information
shicks authored and tjgq committed Jun 8, 2018
1 parent 8699e07 commit cde39a4
Show file tree
Hide file tree
Showing 12 changed files with 993 additions and 174 deletions.
180 changes: 103 additions & 77 deletions src/com/google/javascript/jscomp/FunctionTypeBuilder.java
Expand Up @@ -80,6 +80,7 @@ final class FunctionTypeBuilder {
private List<ObjectType> extendedInterfaces = null;
private ObjectType baseType = null;
private JSType thisType = null;
private boolean isClass = false;
private boolean isConstructor = false;
private boolean makesStructs = false;
private boolean makesDicts = false;
Expand Down Expand Up @@ -349,110 +350,132 @@ FunctionTypeBuilder inferReturnType(
return this;
}

FunctionTypeBuilder usingClassSyntax() {
this.isClass = true;
return this;
}

/**
* Infer the role of the function (whether it's a constructor or interface)
* and what it inherits from in JSDocInfo.
*/
FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info) {
FunctionTypeBuilder inferInheritance(
@Nullable JSDocInfo info, @Nullable ObjectType baseType) {
if (info != null) {
isConstructor = info.isConstructor();
isInterface = info.isInterface();
isRecord = info.usesImplicitMatch();
isAbstract = info.isAbstract();
makesStructs = info.makesStructs();
makesDicts = info.makesDicts();
}
if (isClass) {
// If a CLASS literal has not been explicitly declared an interface, it's a constructor.
// If it's not expicitly @dict or @unrestricted then it's @struct.
isConstructor = !isInterface;
makesStructs = info == null || (!makesDicts && !info.makesUnrestricted());
}

if (makesStructs && !(isConstructor || isInterface)) {
reportWarning(CONSTRUCTOR_REQUIRED, "@struct", formatFnName());
} else if (makesDicts && !isConstructor) {
reportWarning(CONSTRUCTOR_REQUIRED, "@dict", formatFnName());
}

if (makesStructs && !(isConstructor || isInterface)) {
reportWarning(CONSTRUCTOR_REQUIRED, "@struct", formatFnName());
} else if (makesDicts && !isConstructor) {
reportWarning(CONSTRUCTOR_REQUIRED, "@dict", formatFnName());
// TODO(b/74253232): maybeGetNativeTypesOfBuiltin should also handle cases where a local type
// declaration shadows a templatized native type.
ImmutableList<TemplateType> nativeClassTemplateTypeNames =
typeRegistry.maybeGetTemplateTypesOfBuiltin(fnName);
ImmutableList<String> infoTemplateTypeNames =
info != null ? info.getTemplateTypeNames() : ImmutableList.of();
// TODO(b/73386087): Make infoTemplateTypeNames.size() == nativeClassTemplateTypeName.size() a
// Preconditions check. It currently fails for "var symbol" in the externs.
if (nativeClassTemplateTypeNames != null
&& infoTemplateTypeNames.size() == nativeClassTemplateTypeNames.size()) {
classTemplateTypeNames = nativeClassTemplateTypeNames;
typeRegistry.setTemplateTypeNames(classTemplateTypeNames);
} else if (!infoTemplateTypeNames.isEmpty() && (isConstructor || isInterface)) {
// Otherwise, create new template type for
// the template values of the constructor/interface
// Class template types, which can be used in the scope of a constructor
// definition.
ImmutableList.Builder<TemplateType> builder = ImmutableList.builder();
for (String typeParameter : infoTemplateTypeNames) {
builder.add(typeRegistry.createTemplateType(typeParameter));
}
classTemplateTypeNames = builder.build();
typeRegistry.setTemplateTypeNames(classTemplateTypeNames);
}

// TODO(b/74253232): maybeGetNativeTypesOfBuiltin should also handle cases where a local type
// declaration shadows a templatized native type.
ImmutableList<TemplateType> nativeClassTemplateTypeNames =
typeRegistry.maybeGetTemplateTypesOfBuiltin(fnName);
ImmutableList<String> infoTemplateTypeNames = info.getTemplateTypeNames();
// TODO(b/73386087): Make infoTemplateTypeNames.size() == nativeClassTemplateTypeName.size() a
// Preconditions check. It currently fails for "var symbol" in the externs.
if (nativeClassTemplateTypeNames != null
&& infoTemplateTypeNames.size() == nativeClassTemplateTypeNames.size()) {
classTemplateTypeNames = nativeClassTemplateTypeNames;
typeRegistry.setTemplateTypeNames(classTemplateTypeNames);
// base type
if (info != null && info.hasBaseType()) {
if (isConstructor) {
ObjectType infoBaseType =
info.getBaseType().evaluate(scope, typeRegistry).toMaybeObjectType();
// TODO(sdh): ensure that JSDoc's baseType and AST's baseType are compatible if both are set
baseType = infoBaseType;
} else {
// Otherwise, create new template type for
// the template values of the constructor/interface
// Class template types, which can be used in the scope of a constructor
// definition.
ImmutableList<String> typeParameters = info.getTemplateTypeNames();
if (!typeParameters.isEmpty() && (isConstructor || isInterface)) {
ImmutableList.Builder<TemplateType> builder = ImmutableList.builder();
for (String typeParameter : typeParameters) {
builder.add(typeRegistry.createTemplateType(typeParameter));
}
classTemplateTypeNames = builder.build();
typeRegistry.setTemplateTypeNames(classTemplateTypeNames);
}
reportWarning(EXTENDS_WITHOUT_TYPEDEF, formatFnName());
}

// base type
if (info.hasBaseType()) {
if (isConstructor) {
JSType maybeBaseType =
info.getBaseType().evaluate(scope, typeRegistry);
if (maybeBaseType != null &&
maybeBaseType.setValidator(new ExtendedTypeValidator())) {
baseType = (ObjectType) maybeBaseType;
}
} else {
reportWarning(EXTENDS_WITHOUT_TYPEDEF, formatFnName());
}
}
if (baseType != null && isConstructor) {
if (baseType.setValidator(new ExtendedTypeValidator())) {
this.baseType = baseType;
}
}

// Implemented interfaces (for constructors only).
if (info.getImplementedInterfaceCount() > 0) {
if (isConstructor) {
implementedInterfaces = new ArrayList<>();
Set<JSType> baseInterfaces = new HashSet<>();
for (JSTypeExpression t : info.getImplementedInterfaces()) {
JSType maybeInterType = t.evaluate(scope, typeRegistry);

if (maybeInterType != null &&
maybeInterType.setValidator(new ImplementedTypeValidator())) {
// Disallow implementing the same base (not templatized) interface
// type more than once.
JSType baseInterface = maybeInterType;
if (baseInterface.toMaybeTemplatizedType() != null) {
baseInterface =
baseInterface.toMaybeTemplatizedType().getReferencedType();
}
if (!baseInterfaces.add(baseInterface)) {
reportWarning(SAME_INTERFACE_MULTIPLE_IMPLEMENTS, baseInterface.toString());
}

implementedInterfaces.add((ObjectType) maybeInterType);
// Implemented interfaces (for constructors only).
if (info != null && info.getImplementedInterfaceCount() > 0) {
if (isConstructor) {
implementedInterfaces = new ArrayList<>();
Set<JSType> baseInterfaces = new HashSet<>();
for (JSTypeExpression t : info.getImplementedInterfaces()) {
JSType maybeInterType = t.evaluate(scope, typeRegistry);

if (maybeInterType != null &&
maybeInterType.setValidator(new ImplementedTypeValidator())) {
// Disallow implementing the same base (not templatized) interface
// type more than once.
JSType baseInterface = maybeInterType;
if (baseInterface.toMaybeTemplatizedType() != null) {
baseInterface =
baseInterface.toMaybeTemplatizedType().getReferencedType();
}
if (!baseInterfaces.add(baseInterface)) {
reportWarning(SAME_INTERFACE_MULTIPLE_IMPLEMENTS, baseInterface.toString());
}

implementedInterfaces.add((ObjectType) maybeInterType);
}
} else if (isInterface) {
reportWarning(
TypeCheck.CONFLICTING_IMPLEMENTED_TYPE, formatFnName());
} else {
reportWarning(CONSTRUCTOR_REQUIRED, "@implements", formatFnName());
}
} else if (isInterface) {
reportWarning(
TypeCheck.CONFLICTING_IMPLEMENTED_TYPE, formatFnName());
} else {
reportWarning(CONSTRUCTOR_REQUIRED, "@implements", formatFnName());
}
}

// extended interfaces (for interfaces only)
// We've already emitted a warning if this is not an interface.
if (isInterface) {
extendedInterfaces = new ArrayList<>();
// extended interfaces (for interfaces only)
// We've already emitted a warning if this is not an interface.
if (isInterface) {
extendedInterfaces = new ArrayList<>();
if (info != null) {
for (JSTypeExpression t : info.getExtendedInterfaces()) {
JSType maybeInterfaceType = t.evaluate(scope, typeRegistry);
if (maybeInterfaceType != null &&
maybeInterfaceType.setValidator(new ExtendedTypeValidator())) {
extendedInterfaces.add((ObjectType) maybeInterfaceType);
}
// de-dupe baseType (from extends keyword) if it's also in @extends jsdoc.
if (baseType != null && maybeInterfaceType.isSubtypeOf(baseType)) {
baseType = null;
}
}
}
if (baseType != null && baseType.setValidator(new ExtendedTypeValidator())) {
extendedInterfaces.add(baseType);
}
}

return this;
Expand Down Expand Up @@ -512,8 +535,7 @@ FunctionTypeBuilder inferParameterTypes(JSDocInfo info) {
* Infer the parameter types from the list of argument names and
* the doc info.
*/
FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent,
@Nullable JSDocInfo info) {
FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent, @Nullable JSDocInfo info) {
if (argsParent == null) {
if (info == null) {
return this;
Expand Down Expand Up @@ -592,6 +614,11 @@ FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent,
return this;
}

FunctionTypeBuilder inferImplicitConstructorParameters(Node parametersNode) {
this.parametersNode = parametersNode;
return this;
}

/**
* @return Whether the given param is an optional param.
*/
Expand Down Expand Up @@ -624,8 +651,7 @@ private boolean isVarArgsParameter(
/**
* Infer the template type from the doc info.
*/
FunctionTypeBuilder inferTemplateTypeName(
@Nullable JSDocInfo info, JSType ownerType) {
FunctionTypeBuilder inferTemplateTypeName(@Nullable JSDocInfo info, @Nullable JSType ownerType) {
// NOTE: these template type names may override a list
// of inherited ones from an overridden function.
if (info != null) {
Expand Down
3 changes: 1 addition & 2 deletions src/com/google/javascript/jscomp/NodeUtil.java
Expand Up @@ -5221,8 +5221,7 @@ public static Node getBestJSDocInfoNode(Node n) {
/** Find the l-value that the given r-value is being assigned to. */
public static Node getBestLValue(Node n) {
Node parent = n.getParent();
boolean isFunctionDeclaration = isFunctionDeclaration(n);
if (isFunctionDeclaration) {
if (isFunctionDeclaration(n) || isClassDeclaration(n)) {
return n.getFirstChild();
} else if (parent.isName()) {
return parent;
Expand Down

0 comments on commit cde39a4

Please sign in to comment.