/*
* Copyright 2009 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.base.Preconditions;
import com.google.javascript.jscomp.CodingConvention.AssertionFunctionSpec;
import com.google.javascript.jscomp.NodeTraversal.AbstractScopedCallback;
import com.google.javascript.jscomp.type.ReverseAbstractInterpreter;
import com.google.javascript.rhino.Node;
import java.util.HashMap;
import java.util.Map;
/**
* A compiler pass to run the type inference analysis.
*
*/
class TypeInferencePass implements CompilerPass {
static final DiagnosticType DATAFLOW_ERROR = DiagnosticType.error(
"JSC_INTERNAL_ERROR_DATAFLOW",
"non-monotonic data-flow analysis");
private final AbstractCompiler compiler;
private final ReverseAbstractInterpreter reverseInterpreter;
private final TypedScope topScope;
private final MemoizedScopeCreator scopeCreator;
private final Map<String, AssertionFunctionSpec> assertionFunctionsMap;
TypeInferencePass(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
TypedScope topScope, MemoizedScopeCreator scopeCreator) {
this.compiler = compiler;
this.reverseInterpreter = reverseInterpreter;
this.topScope = topScope;
this.scopeCreator = scopeCreator;
assertionFunctionsMap = new HashMap<>();
for (AssertionFunctionSpec assertionFunction :
compiler.getCodingConvention().getAssertionFunctions()) {
assertionFunctionsMap.put(assertionFunction.getFunctionName(),
assertionFunction);
}
}
/**
* Main entry point for type inference when running over the whole tree.
*
* @param externsRoot The root of the externs parse tree.
* @param jsRoot The root of the input parse tree to be checked.
*/
@Override
public void process(Node externsRoot, Node jsRoot) {
Node externsAndJs = jsRoot.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(
externsRoot == null || externsAndJs.hasChild(externsRoot));
inferAllScopes(externsAndJs);
}
/** Entry point for type inference when running over part of the tree. */
void inferAllScopes(Node node) {
// Type analysis happens in two major phases.
// 1) Finding all the symbols.
// 2) Propagating all the inferred types.
//
// The order of this analysis is non-obvious. In a complete inference
// system, we may need to backtrack arbitrarily far. But the compile-time
// costs would be unacceptable.
//
// We do one pass where we do typed scope creation for all scopes
// in pre-order.
//
// Then we do a second pass where we do all type inference
// (type propagation) in pre-order.
//
// We use a memoized scope creator so that we never create a scope
// more than once.
//
// This will allow us to handle cases like:
// var ns = {};
// (function() { /** JSDoc */ ns.method = function() {}; })();
// ns.method();
// In this code, we need to build the symbol table for the inner scope in
// order to propagate the type of ns.method in the outer scope.
(new NodeTraversal(
compiler, new FirstScopeBuildingCallback(), scopeCreator))
.traverseWithScope(node, topScope);
for (TypedScope s : scopeCreator.getAllMemoizedScopes()) {
s.resolveTypes();
}
(new NodeTraversal(
compiler, new SecondScopeBuildingCallback(), scopeCreator))
.traverseWithScope(node, topScope);
}
void inferScope(Node n, TypedScope scope) {
TypeInference typeInference =
new TypeInference(
compiler, computeCfg(n), reverseInterpreter, scope,
assertionFunctionsMap);
try {
typeInference.analyze();
// Resolve any new type names found during the inference.
compiler.getTypeRegistry().resolveTypesInScope(scope);
} catch (DataFlowAnalysis.MaxIterationsExceededException e) {
compiler.report(JSError.make(n, DATAFLOW_ERROR));
}
}
private static class FirstScopeBuildingCallback extends AbstractScopedCallback {
@Override
public void enterScope(NodeTraversal t) {
t.getTypedScope();
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// Do nothing
}
}
private class SecondScopeBuildingCallback extends AbstractScopedCallback {
@Override
public void enterScope(NodeTraversal t) {
// Only infer the entry root, rather than the scope root.
// This ensures that incremental compilation only touches the root
// that's been swapped out.
inferScope(t.getCurrentNode(), t.getTypedScope());
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// Do nothing
}
}
private ControlFlowGraph<Node> computeCfg(Node n) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, false);
cfa.process(null, n);
return cfa.getCfg();
}
}