diff --git a/src/com/google/javascript/jscomp/AbstractScope.java b/src/com/google/javascript/jscomp/AbstractScope.java index 9f90c695a74..fc9ecc09085 100644 --- a/src/com/google/javascript/jscomp/AbstractScope.java +++ b/src/com/google/javascript/jscomp/AbstractScope.java @@ -35,7 +35,7 @@ * @see NodeTraversal * */ -public abstract class AbstractScope, V extends Var> +abstract class AbstractScope, V extends AbstractVar> implements StaticScope, Serializable { final Map vars = new LinkedHashMap<>(); S parent; diff --git a/src/com/google/javascript/jscomp/AbstractVar.java b/src/com/google/javascript/jscomp/AbstractVar.java new file mode 100644 index 00000000000..2888ce8ad64 --- /dev/null +++ b/src/com/google/javascript/jscomp/AbstractVar.java @@ -0,0 +1,224 @@ +/* + * Copyright 2018 The Closure Compiler Authors. + * + * 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 + * + * http://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 com.google.javascript.jscomp; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.javascript.rhino.JSDocInfo; +import com.google.javascript.rhino.Node; +import com.google.javascript.rhino.StaticRef; +import com.google.javascript.rhino.StaticSlot; +import com.google.javascript.rhino.StaticSourceFile; +import com.google.javascript.rhino.Token; + +/** + * Used by {@code Scope} to store information about variables. + */ +public class AbstractVar, V extends AbstractVar> + implements StaticSlot, StaticRef { + + final String name; + + /** Var node */ + final Node nameNode; + + /** Input source */ + final CompilerInput input; + + /** + * The index at which the var is declared. e.g. if it's 0, it's the first + * declared variable in that scope + */ + final int index; + + /** The enclosing scope */ + final S scope; + + AbstractVar(String name, Node nameNode, S scope, int index, CompilerInput input) { + this.name = name; + this.nameNode = nameNode; + this.scope = scope; + this.index = index; + this.input = input; + } + + // Non-final for jsdev tests + @Override + public String getName() { + return name; + } + + @Override + public final Node getNode() { + return nameNode; + } + + final CompilerInput getInput() { + return input; + } + + @Override + public StaticSourceFile getSourceFile() { + return nameNode.getStaticSourceFile(); + } + + // Temporarily non-final for TypedVar + @Override + public V getSymbol() { + return thisVar(); + } + + // Temporarily non-final for TypedVar + @Override + public V getDeclaration() { + return nameNode == null ? null : thisVar(); + } + + public final Node getParentNode() { + return nameNode == null ? null : nameNode.getParent(); + } + + /** + * Whether this is a bleeding function (an anonymous named function + * that bleeds into the inner scope). + */ + public boolean isBleedingFunction() { + return NodeUtil.isFunctionExpression(getParentNode()); + } + + // Temporarily non-final for TypedVar + public S getScope() { + return scope; + } + + // Non-final for jsdev tests + public boolean isGlobal() { + return scope.isGlobal(); + } + + public final boolean isLocal() { + return scope.isLocal(); + } + + final boolean isExtern() { + return input == null || input.isExtern(); + } + + /** + * Returns {@code true} if the variable is declared as a constant, + * based on the value reported by {@code NodeUtil}. + */ + public final boolean isInferredConst() { + if (nameNode == null) { + return false; + } + + return nameNode.getBooleanProp(Node.IS_CONSTANT_VAR) + || nameNode.getBooleanProp(Node.IS_CONSTANT_NAME); + } + + /** + * Returns {@code true} if the variable is declared as a define. + * A variable is a define if it is annotated by {@code @define}. + */ + public final boolean isDefine() { + JSDocInfo info = getJSDocInfo(); + return info != null && info.isDefine(); + } + + public final Node getInitialValue() { + return NodeUtil.getRValueOfLValue(nameNode); + } + + // Non-final for jsdev tests + public Node getNameNode() { + return nameNode; + } + + // Non-final for jsdev tests + @Override + public JSDocInfo getJSDocInfo() { + return nameNode == null ? null : NodeUtil.getBestJSDocInfo(nameNode); + } + + final boolean isVar() { + return declarationType() == Token.VAR; + } + + final boolean isCatch() { + return declarationType() == Token.CATCH; + } + + final boolean isLet() { + return declarationType() == Token.LET; + } + + final boolean isConst() { + return declarationType() == Token.CONST; + } + + final boolean isClass() { + return declarationType() == Token.CLASS; + } + + final boolean isParam() { + return declarationType() == Token.PARAM_LIST; + } + + public final boolean isDefaultParam() { + Node parent = nameNode.getParent(); + return parent.getParent().isParamList() && parent.isDefaultValue() + && parent.getFirstChild() == nameNode; + } + + final boolean isImport() { + return declarationType() == Token.IMPORT; + } + + public boolean isArguments() { + return false; + } + + private static final ImmutableSet DECLARATION_TYPES = Sets.immutableEnumSet( + Token.VAR, + Token.LET, + Token.CONST, + Token.FUNCTION, + Token.CLASS, + Token.CATCH, + Token.IMPORT, + Token.PARAM_LIST); + + protected Token declarationType() { + for (Node current = nameNode; current != null; + current = current.getParent()) { + if (DECLARATION_TYPES.contains(current.getToken())) { + return current.getToken(); + } + } + throw new IllegalStateException("The nameNode for " + this + " must be a descendant" + + " of one of: " + DECLARATION_TYPES); + } + + // This is safe because any concrete subclass of AbstractVar should be assignable to V. + // While it's theoretically possible to do otherwise, such a class would be very awkward to + // implement, and is therefore not worth worrying about. + @SuppressWarnings("unchecked") + private V thisVar() { + return (V) this; + } +} diff --git a/src/com/google/javascript/jscomp/MemoizedTypedScopeCreator.java b/src/com/google/javascript/jscomp/MemoizedTypedScopeCreator.java index e865a55b5ce..fb1bfcac113 100644 --- a/src/com/google/javascript/jscomp/MemoizedTypedScopeCreator.java +++ b/src/com/google/javascript/jscomp/MemoizedTypedScopeCreator.java @@ -64,7 +64,7 @@ public Iterable getReferences(TypedVar var) { @Override public TypedScope getScope(TypedVar var) { - return var.scope; + return (TypedScope) var.scope; } @Override diff --git a/src/com/google/javascript/jscomp/TypedVar.java b/src/com/google/javascript/jscomp/TypedVar.java index 4d7e5112aba..4823857d66f 100644 --- a/src/com/google/javascript/jscomp/TypedVar.java +++ b/src/com/google/javascript/jscomp/TypedVar.java @@ -17,22 +17,17 @@ package com.google.javascript.jscomp; import com.google.javascript.rhino.ErrorReporter; -import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; -import com.google.javascript.rhino.StaticSourceFile; import com.google.javascript.rhino.TypeI; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.StaticTypedRef; import com.google.javascript.rhino.jstype.StaticTypedSlot; /** - * Several methods in this class, such as {@code isVar} throw an exception when called, and several - * methods are currently identical to the ones in Var. The reason for this is that we want to shadow - * methods from the parent class, to avoid calling them accidentally. + * {@link AbstractVar} subclass for use with {@link TypedScope}. */ public class TypedVar extends Var implements StaticTypedSlot, StaticTypedRef { - final TypedScope scope; private JSType type; // The next two fields and the associated methods are only used by // TypeInference.java. Maybe there is a way to avoid having them in all typed variable instances. @@ -50,94 +45,22 @@ public class TypedVar extends Var implements StaticTypedSlot, StaticType TypedScope scope, int index, CompilerInput input) { super(name, nameNode, scope, index, input); this.type = type; - this.scope = scope; this.typeInferred = inferred; } - @Override - public String getName() { - return name; - } - - @Override - public Node getNode() { - return nameNode; - } - - @Override - CompilerInput getInput() { - return input; - } - - @Override - public StaticSourceFile getSourceFile() { - return nameNode.getStaticSourceFile(); - } - @Override public TypedVar getSymbol() { - return this; + return (TypedVar) super.getSymbol(); } @Override public TypedVar getDeclaration() { - return nameNode == null ? null : this; - } - - @Override - public Node getParentNode() { - return nameNode == null ? null : nameNode.getParent(); - } - - @Override - public boolean isBleedingFunction() { - throw new IllegalStateException( - "Method isBleedingFunction cannot be called on typed variables."); + return (TypedVar) super.getDeclaration(); } @Override public TypedScope getScope() { - return scope; - } - - @Override - public boolean isGlobal() { - return scope.isGlobal(); - } - - @Override - public boolean isLocal() { - return scope.isLocal(); - } - - @Override - boolean isExtern() { - return input == null || input.isExtern(); - } - - @Override - public boolean isInferredConst() { - throw new IllegalStateException("Method isInferredConst cannot be called on typed variables."); - } - - @Override - public boolean isDefine() { - throw new IllegalStateException("Method isDefine cannot be called on typed variables."); - } - - @Override - public Node getInitialValue() { - return NodeUtil.getRValueOfLValue(nameNode); - } - - @Override - public Node getNameNode() { - return nameNode; - } - - @Override - public JSDocInfo getJSDocInfo() { - return nameNode == null ? null : NodeUtil.getBestJSDocInfo(nameNode); + return (TypedScope) super.getScope(); } /** @@ -160,7 +83,7 @@ void setType(JSType type) { void resolveType(ErrorReporter errorReporter) { if (type != null) { - type = type.resolve(errorReporter, scope); + type = type.resolve(errorReporter, (TypedScope) scope); } } @@ -213,24 +136,4 @@ void markAssignedExactlyOnce() { boolean isMarkedAssignedExactlyOnce() { return markedAssignedExactlyOnce; } - - @Override - boolean isVar() { - throw new IllegalStateException("Method isVar cannot be called on typed variables."); - } - - @Override - boolean isLet() { - throw new IllegalStateException("Method isLet cannot be called on typed variables."); - } - - @Override - boolean isConst() { - throw new IllegalStateException("Method isConst cannot be called on typed variables."); - } - - @Override - boolean isParam() { - throw new IllegalStateException("Method isParam cannot be called on typed variables."); - } } diff --git a/src/com/google/javascript/jscomp/Var.java b/src/com/google/javascript/jscomp/Var.java index 3b76418e63e..7cfeb165140 100644 --- a/src/com/google/javascript/jscomp/Var.java +++ b/src/com/google/javascript/jscomp/Var.java @@ -16,11 +16,6 @@ package com.google.javascript.jscomp; -import static com.google.common.base.Preconditions.checkArgument; - -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; -import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.StaticRef; import com.google.javascript.rhino.StaticSlot; @@ -30,129 +25,18 @@ /** * Used by {@code Scope} to store information about variables. */ -public class Var implements StaticSlot, StaticRef { - final String name; - - /** Var node */ - final Node nameNode; - - /** Input source */ - final CompilerInput input; - - /** - * The index at which the var is declared. e.g. if it's 0, it's the first - * declared variable in that scope - */ - final int index; +public class Var extends AbstractVar implements StaticSlot, StaticRef { - /** The enclosing scope */ - final Scope scope; + static final String ARGUMENTS = "arguments"; Var(String name, Node nameNode, Scope scope, int index, CompilerInput input) { - this.name = name; - this.nameNode = nameNode; - this.scope = scope; - this.index = index; - this.input = input; + super(name, nameNode, scope, index, input); } static Var makeArgumentsVar(Scope scope) { - checkArgument(!(scope instanceof TypedScope)); return new Arguments(scope); } - @Override - public String getName() { - return name; - } - - @Override - public Node getNode() { - return nameNode; - } - - CompilerInput getInput() { - return input; - } - - @Override - public StaticSourceFile getSourceFile() { - return nameNode.getStaticSourceFile(); - } - - @Override - public Var getSymbol() { - return this; - } - - @Override - public Var getDeclaration() { - return nameNode == null ? null : this; - } - - public Node getParentNode() { - return nameNode == null ? null : nameNode.getParent(); - } - - /** - * Whether this is a bleeding function (an anonymous named function - * that bleeds into the inner scope). - */ - public boolean isBleedingFunction() { - return NodeUtil.isFunctionExpression(getParentNode()); - } - - public Scope getScope() { - return scope; - } - - public boolean isGlobal() { - return scope.isGlobal(); - } - - public boolean isLocal() { - return scope.isLocal(); - } - - boolean isExtern() { - return input == null || input.isExtern(); - } - - /** - * Returns {@code true} if the variable is declared as a constant, - * based on the value reported by {@code NodeUtil}. - */ - public boolean isInferredConst() { - if (nameNode == null) { - return false; - } - - return nameNode.getBooleanProp(Node.IS_CONSTANT_VAR) - || nameNode.getBooleanProp(Node.IS_CONSTANT_NAME); - } - - /** - * Returns {@code true} if the variable is declared as a define. - * A variable is a define if it is annotated by {@code @define}. - */ - public boolean isDefine() { - JSDocInfo info = getJSDocInfo(); - return info != null && info.isDefine(); - } - - public Node getInitialValue() { - return NodeUtil.getRValueOfLValue(nameNode); - } - - public Node getNameNode() { - return nameNode; - } - - @Override - public JSDocInfo getJSDocInfo() { - return nameNode == null ? null : NodeUtil.getBestJSDocInfo(nameNode); - } - @Override public boolean equals(Object other) { if (!(other instanceof Var)) { @@ -173,66 +57,6 @@ public String toString() { return "Var " + name + " @ " + nameNode; } - boolean isVar() { - return declarationType() == Token.VAR; - } - - boolean isCatch() { - return declarationType() == Token.CATCH; - } - - boolean isLet() { - return declarationType() == Token.LET; - } - - boolean isConst() { - return declarationType() == Token.CONST; - } - - boolean isClass() { - return declarationType() == Token.CLASS; - } - - boolean isParam() { - return declarationType() == Token.PARAM_LIST; - } - - public boolean isDefaultParam() { - Node parent = nameNode.getParent(); - return parent.getParent().isParamList() && parent.isDefaultValue() - && parent.getFirstChild() == nameNode; - } - - boolean isImport() { - return declarationType() == Token.IMPORT; - } - - public boolean isArguments() { - return false; - } - - private static final ImmutableSet DECLARATION_TYPES = Sets.immutableEnumSet( - Token.VAR, - Token.LET, - Token.CONST, - Token.FUNCTION, - Token.CLASS, - Token.CATCH, - Token.IMPORT, - Token.PARAM_LIST); - - protected Token declarationType() { - for (Node current = nameNode; current != null; - current = current.getParent()) { - if (DECLARATION_TYPES.contains(current.getToken())) { - return current.getToken(); - } - } - throw new IllegalStateException("The nameNode for " + this + " must be a descendant" - + " of one of: " + DECLARATION_TYPES); - } - - /** * A special subclass of Var used to distinguish "arguments" in the current * scope. @@ -240,7 +64,7 @@ protected Token declarationType() { private static class Arguments extends Var { Arguments(Scope scope) { super( - "arguments", // always arguments + ARGUMENTS, // always arguments null, // no declaration node scope, -1, // no variable index @@ -270,12 +94,8 @@ protected Token declarationType() { @Override public boolean equals(Object other) { - if (!(other instanceof Arguments)) { - return false; - } - - Arguments otherVar = (Arguments) other; - return otherVar.scope.getRootNode() == scope.getRootNode(); + // Note: Arguments vars are singletons. + return this == other; } @Override