Skip to content

Commit

Permalink
Factor out a QualifiedName class
Browse files Browse the repository at this point in the history
This provides a lighter-weight alternative to using Node to pass around qualified names, without the extra string-parsing required to deal with Strings. QualifiedName objects can be built from Nodes, strings, or by adding additional property accesses on top of an existing QualifiedName object. This should be used anywhere we're currently writing `owner + "." + prop`, as well as replace most usages of Node.matchesQualifiedName(String).

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=220899304
  • Loading branch information
shicks authored and blickly committed Nov 12, 2018
1 parent 5f40d28 commit 2a0ce5f
Show file tree
Hide file tree
Showing 4 changed files with 529 additions and 48 deletions.
75 changes: 27 additions & 48 deletions src/com/google/javascript/jscomp/TypedScopeCreator.java
Expand Up @@ -63,11 +63,11 @@
import com.google.javascript.jscomp.NodeTraversal.AbstractScopedCallback;
import com.google.javascript.jscomp.NodeTraversal.AbstractShallowStatementCallback;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.NominalTypeBuilder;
import com.google.javascript.rhino.QualifiedName;
import com.google.javascript.rhino.StaticSymbolTable;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.EnumType;
Expand Down Expand Up @@ -225,37 +225,19 @@ void resolve() {
}
}

/**
* Stores the type and qualified name for a destructuring rvalue, which has no actual AST node.
* The {@code qualifiedName} is therefore a detached Node cloned out of the source tree with extra
* getprops added onto it. Cloning should generally be pretty cheap (since it's always just a
* qualified name). Nevertheless, we may pull out a QualifiedName class abstraction at some point
* in the future, which would allow avoiding the clone in the first place. Using a Node here is
* necessary to avoid duplicating logic for non-destructured qualified name aliasing.
*/
/** Stores the type and qualified name for a destructuring rvalue. */
private static class RValueInfo {
@Nullable final JSType type;
@Nullable final Node qualifiedName;
@Nullable final QualifiedName qualifiedName;

RValueInfo(JSType type, Node qualifiedName) {
RValueInfo(JSType type, QualifiedName qualifiedName) {
this.type = type;
this.qualifiedName = qualifiedName;
}

static RValueInfo empty() {
return new RValueInfo(null, null);
}

/** Returns a new RValueInfo whose qualified name has an extra property. */
RValueInfo forProperty(JSType type, String property) {
if (qualifiedName == null) {
return new RValueInfo(type, null);
} else if (qualifiedName.getParent() == null) {
return new RValueInfo(type, IR.getprop(qualifiedName, property));
}
// qualified name is already in the tree, so we need to clone it first.
return new RValueInfo(type, IR.getprop(qualifiedName.cloneTree(), property));
}
}

TypedScopeCreator(AbstractCompiler compiler) {
Expand Down Expand Up @@ -901,7 +883,7 @@ void defineVarChild(JSDocInfo declarationInfo, Node child, TypedScope scope) {
value != null
? new RValueInfo(
getDeclaredRValueType(/* lValue= */ null, value),
/* qualifiedName= */ value)
value.getQualifiedNameObject())
: new RValueInfo(unknownType, /* qualifiedName= */ null));
}
}
Expand All @@ -928,14 +910,16 @@ private RValueInfo inferTypeForDestructuredTarget(
RValueInfo rvalue = patternTypeSupplier.get();
JSType patternType = rvalue.type;
String propertyName = target.getStringKey().getString();
QualifiedName qname =
rvalue.qualifiedName != null ? rvalue.qualifiedName.getprop(propertyName) : null;
if (patternType == null || patternType.isUnknownType()) {
return rvalue.forProperty(null, propertyName);
return new RValueInfo(null, qname);
}
if (patternType.hasProperty(propertyName)) {
JSType type = patternType.findPropertyType(propertyName);
return rvalue.forProperty(type, propertyName);
return new RValueInfo(type, qname);
}
return rvalue.forProperty(null, propertyName);
return new RValueInfo(null, qname);
}

void defineDestructuringPatternInVarDeclaration(
Expand Down Expand Up @@ -1890,7 +1874,7 @@ && shouldUseFunctionLiteralType(
if (NodeUtil.isConstantDeclaration(compiler.getCodingConvention(), info, lValue)) {
if (rValue != null) {
JSType rValueType = getDeclaredRValueType(lValue, rValue);
maybeDeclareAliasType(lValue, rValue, rValueType);
maybeDeclareAliasType(lValue, rValue.getQualifiedNameObject(), rValueType);
if (rValueType != null) {
return rValueType;
}
Expand Down Expand Up @@ -1920,7 +1904,7 @@ && shouldUseFunctionLiteralType(
* @param rvalueName the rvalue's qualified name if it exists, null otherwise
*/
private void maybeDeclareAliasType(
Node lValue, @Nullable Node rValue, @Nullable JSType rValueType) {
Node lValue, @Nullable QualifiedName rValue, @Nullable JSType rValueType) {
// NOTE: this allows some strange patterns such allowing instance properties
// to be aliases of constructors, and then creating a local alias of that to be
// used as a type name. Consider restricting this.
Expand Down Expand Up @@ -1949,25 +1933,23 @@ private void maybeDeclareAliasType(
FunctionType functionType = rValueType.toMaybeFunctionType();
typeRegistry.declareType(
currentScope, lValue.getQualifiedName(), functionType.getInstanceType());
} else if (rValue.isQualifiedName()) {
} else if (rValue != null) {
// Also infer a type name for aliased @typedef
JSType rhsNamedType = typeRegistry.getType(currentScope, rValue.getQualifiedName());
JSType rhsNamedType = typeRegistry.getType(currentScope, rValue.join());
if (rhsNamedType != null) {
typeRegistry.declareType(currentScope, lValue.getQualifiedName(), rhsNamedType);
}
}
}

/** Returns the AST node associated with the definition, if any. */
private Node getDefinitionNode(Node qnameNode) {
if (!qnameNode.isGetProp()) {
TypedVar var = currentScope.getVar(qnameNode.getQualifiedName());
private Node getDefinitionNode(QualifiedName qname) {
if (qname.isSimple()) {
TypedVar var = currentScope.getVar(qname.getComponent());
return var != null ? var.getNameNode() : null;
}
ObjectType parent = ObjectType.cast(lookupQualifiedName(qnameNode.getFirstChild()));
return parent != null
? parent.getPropertyDefSite(qnameNode.getLastChild().getString())
: null;
ObjectType parent = ObjectType.cast(lookupQualifiedName(qname.getOwner()));
return parent != null ? parent.getPropertyDefSite(qname.getComponent()) : null;
}

/**
Expand Down Expand Up @@ -1996,7 +1978,7 @@ private JSType getDeclaredRValueType(@Nullable Node lValue, Node rValue) {

// If rValue is a name, try looking it up in the current scope.
if (rValue.isQualifiedName()) {
return lookupQualifiedName(rValue);
return lookupQualifiedName(rValue.getQualifiedNameObject());
}

// Check for simple invariant operations, such as "!x" or "+x" or "''+x"
Expand All @@ -2013,7 +1995,7 @@ private JSType getDeclaredRValueType(@Nullable Node lValue, Node rValue) {
}

if (rValue.isNew() && rValue.getFirstChild().isQualifiedName()) {
JSType targetType = lookupQualifiedName(rValue.getFirstChild());
JSType targetType = lookupQualifiedName(rValue.getFirstChild().getQualifiedNameObject());
if (targetType != null) {
FunctionType fnType = targetType
.restrictByNotNullOrUndefined()
Expand Down Expand Up @@ -2047,23 +2029,20 @@ private JSType getDeclaredRValueType(@Nullable Node lValue, Node rValue) {
return null;
}

private JSType lookupQualifiedName(Node n) {
String name = n.getQualifiedName();
TypedVar slot = currentScope.getVar(name);
private JSType lookupQualifiedName(QualifiedName qname) {
TypedVar slot = currentScope.getVar(qname.join());
if (slot != null && !slot.isTypeInferred()) {
JSType type = slot.getType();
if (type != null && !type.isUnknownType()) {
return type;
}
} else if (n.isGetProp()) {
JSType type = lookupQualifiedName(n.getFirstChild());
} else if (!qname.isSimple()) {
JSType type = lookupQualifiedName(qname.getOwner());
// NOTE: The scope only contains declared types at this
// point so any property we find is a value type
// to look up properties on.
if (type != null) {
JSType propType = type.findPropertyType(
n.getLastChild().getString());
return propType;
return type.findPropertyType(qname.getComponent());
}
}
return null;
Expand Down Expand Up @@ -2504,7 +2483,7 @@ void checkForTypedef(NodeTraversal t, Node candidate, JSDocInfo info) {
.withType(getNativeType(NO_TYPE))
.allowLaterTypeInference(false)
.defineSlot();
Node definitionNode = getDefinitionNode(candidate.getFirstChild());
Node definitionNode = getDefinitionNode(candidate.getFirstChild().getQualifiedNameObject());
if (definitionNode != null) {
ObjectType parent = ObjectType.cast(definitionNode.getJSType());
if (parent != null) {
Expand Down
5 changes: 5 additions & 0 deletions src/com/google/javascript/rhino/Node.java
Expand Up @@ -2111,6 +2111,11 @@ public final String getQualifiedName() {
}
}

@Nullable
public final QualifiedName getQualifiedNameObject() {
return isQualifiedName() ? new QualifiedName.NodeQname(this) : null;
}

/**
* Helper method for {@link #getQualifiedName} to handle GETPROP nodes.
*
Expand Down

0 comments on commit 2a0ce5f

Please sign in to comment.