/*
 * 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 static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.rhino.jstype.JSTypeNative.UNKNOWN_TYPE;

import com.google.javascript.jscomp.CodingConvention.AssertionFunctionLookup;
import com.google.javascript.jscomp.NodeTraversal.AbstractScopedCallback;
import com.google.javascript.jscomp.type.ReverseAbstractInterpreter;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.JSTypeResolver;

/** A compiler pass to run the type inference analysis. */
class TypeInferencePass {

  static final DiagnosticType DATAFLOW_ERROR = DiagnosticType.error(
      "JSC_INTERNAL_ERROR_DATAFLOW",
      "non-monotonic data-flow analysis");

  private final AbstractCompiler compiler;
  private final JSTypeRegistry registry;
  private final ReverseAbstractInterpreter reverseInterpreter;
  private TypedScope topScope;
  private final TypedScopeCreator scopeCreator;
  private final AssertionFunctionLookup assertionFunctionLookup;

  TypeInferencePass(
      AbstractCompiler compiler,
      ReverseAbstractInterpreter reverseInterpreter,
      TypedScopeCreator scopeCreator) {
    this.compiler = compiler;
    this.registry = compiler.getTypeRegistry();
    this.reverseInterpreter = reverseInterpreter;
    this.scopeCreator = scopeCreator;
    this.assertionFunctionLookup =
        AssertionFunctionLookup.of(compiler.getCodingConvention().getAssertionFunctions());
  }

  // TODO(b/187747884): Delete this method.
  TypeInferencePass reuseTopScope(TypedScope topScope) {
    checkNotNull(topScope);
    checkState(this.topScope == null);
    this.topScope = topScope;
    return this;
  }

  /**
   * Execute type inference running over part of the scope tree.
   *
   * @return the top scope, either newly created, or patched by this inference.
   */
  TypedScope inferAllScopes(Node inferenceRoot) {
    // 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.
    try (JSTypeResolver.Closer closer = this.registry.getResolver().openForDefinition()) {
      if (this.topScope == null) {
        checkState(inferenceRoot.isRoot());
        checkState(inferenceRoot.getParent() == null);
        this.topScope = scopeCreator.createScope(inferenceRoot, null);
      } else {
        checkState(inferenceRoot.isScript());
        scopeCreator.patchGlobalScope(this.topScope, inferenceRoot);
      }

      NodeTraversal.builder()
          .setCompiler(compiler)
          .setCallback(new FirstScopeBuildingCallback())
          .setScopeCreator(scopeCreator)
          .traverseWithScope(inferenceRoot, this.topScope);
      scopeCreator.resolveWeakImportsPreResolution();
    }
    scopeCreator.undoTypeAliasChains();

    NodeTraversal.builder()
        .setCompiler(compiler)
        .setCallback(new SecondScopeBuildingCallback())
        .setScopeCreator(scopeCreator)
        .traverseWithScope(inferenceRoot, this.topScope);

    // Normalize TypedVars to have the '?' type instead of null after inference is complete. This
    // currently cannot be done any earlier because it breaks inference of variables assigned in
    // local scopes.
    // TODO(b/149843534): this should be a crash instead.
    final JSType unknownType = this.registry.getNativeType(UNKNOWN_TYPE);
    for (TypedVar var : this.scopeCreator.getAllSymbols()) {
      if (var.getType() == null) {
        var.setType(unknownType);
      }
    }

    return this.topScope;
  }

  private void inferScope(Node n, TypedScope scope) {
    TypeInference typeInference =
        new TypeInference(
            compiler,
            computeCfg(n),
            reverseInterpreter,
            scope,
            scopeCreator,
            assertionFunctionLookup);
    try {
      typeInference.analyze();
    } 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.
      TypedScope scope = t.getTypedScope();
      if (!scope.isBlockScope() && !scope.isModuleScope()) {
        // ignore scopes that don't have their own CFGs and module scopes, which are visited
        // as if they were a regular script.
        inferScope(t.getCurrentNode(), scope);
      }
    }

    @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();
  }
}

@@ -650,73 +650,8 @@
 p;%0A%0A
-import static com.google.common.base.Preconditions.checkNotNull;%0A
 impo
@@ -709,16 +709,16 @@
 kState;%0A
+
 import s
@@ -2192,228 +2192,8 @@
  %7D%0A%0A
-  // TODO(b/187747884): Delete this method.%0A  TypeInferencePass reuseTopScope(TypedScope topScope) %7B%0A    checkNotNull(topScope);%0A    checkState(this.topScope == null);%0A    this.topScope = topScope;%0A    return this;%0A  %7D%0A%0A
   /*
@@ -3438,45 +3438,8 @@
 ) %7B%0A
-      if (this.topScope == null) %7B%0A  
     
@@ -3472,26 +3472,24 @@
 .isRoot());%0A
-  
       checkS
@@ -3535,18 +3535,27 @@
 ;%0A      
-  
+checkState(
 this.top
@@ -3565,170 +3565,64 @@
 pe =
- scopeCreator.createScope(inferenceRoot, null);%0A      %7D else %7B%0A        checkState(inferenceRoot.isScript());%0A        scopeCreator.patchGlobalScope(this.topScope, 
+= null);%0A      this.topScope = scopeCreator.createScope(
 infe
@@ -3630,26 +3630,24 @@
 enceRoot
-);%0A      %7D
+, null);
 %0A%0A      

Delete hotswap support from TypeInferencePass.