All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.NTIScope Maven / Gradle / Ivy

/*
 * Copyright 2013 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.newtypes.Declaration;
import com.google.javascript.jscomp.newtypes.DeclaredFunctionType;
import com.google.javascript.jscomp.newtypes.DeclaredTypeRegistry;
import com.google.javascript.jscomp.newtypes.EnumType;
import com.google.javascript.jscomp.newtypes.FunctionNamespace;
import com.google.javascript.jscomp.newtypes.JSType;
import com.google.javascript.jscomp.newtypes.JSTypeCreatorFromJSDoc;
import com.google.javascript.jscomp.newtypes.JSTypes;
import com.google.javascript.jscomp.newtypes.Namespace;
import com.google.javascript.jscomp.newtypes.NamespaceLit;
import com.google.javascript.jscomp.newtypes.QualifiedName;
import com.google.javascript.jscomp.newtypes.RawNominalType;
import com.google.javascript.jscomp.newtypes.Typedef;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author [email protected] (Ben Lickly)
 * @author [email protected] (Dimitris Vardoulakis)
 */
final class NTIScope implements DeclaredTypeRegistry, Serializable {
  private final NTIScope parent;
  private final Node root;
  // Name on the function AST node; null for top scope & anonymous functions
  private final String name;
  private final JSTypes commonTypes;
  // Becomes true after freezeScope is run; so it's true during NTI.
  private boolean isFrozen = false;

  // A local w/out declared type is mapped to null, not to this.commonTypes.UNKNOWN.
  private final Map locals = new LinkedHashMap<>();
  private final Map externs;
  private final Set constVars = new LinkedHashSet<>();
  private final List formals;
  // Variables that are defined in this scope and used in inner scopes.
  private Set escapedVars = new LinkedHashSet<>();
  // outerVars are the variables that appear free in this scope
  // and are defined in an outer scope.
  private final Set outerVars = new LinkedHashSet<>();
  // When a function is also used as a namespace, we add entries to both
  // localFunDefs and localNamespaces. After freezeScope (when NTI runs),
  // the function has an entry in localFunDefs, and in locals or externs.
  private final Map localFunDefs = new LinkedHashMap<>();
  private ImmutableSet unknownTypeNames = ImmutableSet.of();
  private Map localTypedefs = new LinkedHashMap<>();
  private Map localNamespaces = new LinkedHashMap<>();
  // The namespace map that we preserve post-finalization, purely for use
  // in GlobalTypeInfo for symbol table purposes.
  private Map preservedNamespaces;
  // The set localEnums is used for enum resolution, and then discarded.
  private Set localEnums = new LinkedHashSet<>();

  // For top level, the DeclaredFunctionType just includes a type for THIS.
  // For functions, the DeclaredFunctionType is never null, even those without jsdoc.
  // Any inferred parameters or return will be set to null individually.
  // If the function uses TTL, we instantiate the TTL variables to ?.
  // TODO(dimvar): instead, we want to evaluate the TTL variables when the non-TTL variables are
  // unknown, and use that for the declaredType. Will try in a follow-up CL.
  private DeclaredFunctionType declaredType;

  NTIScope(Node root, NTIScope parent, List formals, JSTypes commonTypes) {
    checkNotNull(commonTypes);
    if (parent == null) {
      this.name = null;
      this.externs = new LinkedHashMap<>();
    } else {
      String nameOnAst = root.getFirstChild().getString();
      this.name = nameOnAst.isEmpty() ? null : nameOnAst;
      this.externs = ImmutableMap.of();
    }
    this.root = root;
    this.parent = parent;
    this.formals = formals;
    this.commonTypes = commonTypes;
  }

  Node getRoot() {
    return this.root;
  }

  NTIScope getParent() {
    return this.parent;
  }

  Node getBody() {
    checkArgument(root.isFunction());
    return NodeUtil.getFunctionBody(root);
  }

  /** Used only for error messages; null for top scope */
  String getReadableName() {
    // TODO(dimvar): don't return null for anonymous functions
    return isTopLevel() ? null : NodeUtil.getName(root);
  }

  String getName() {
    return name;
  }

  @Override
  public JSTypes getCommonTypes() {
    return this.commonTypes;
  }

  void setDeclaredType(DeclaredFunctionType declaredType) {
    checkNotNull(declaredType);
    Map typeTransformations = getTypeTransformations();
    if (typeTransformations.isEmpty()) {
      this.declaredType = declaredType;
    } else {
      Set ttlVars = typeTransformations.keySet();
      this.declaredType = declaredType.substituteTTLGenericsWithUnknown(ttlVars);
    }
    // In NTI, we set the type of a function node after we create the summary.
    // NTI doesn't analyze externs, so we set the type for extern functions here.
    if (this.root.isFromExterns()) {
      this.root.setTypeI(this.commonTypes.fromFunctionType(declaredType.toFunctionType()));
    }
  }

  @Override
  public DeclaredFunctionType getDeclaredFunctionType() {
    return this.declaredType;
  }

  boolean isFunction() {
    return root.isFunction();
  }

  boolean isTopLevel() {
    return parent == null;
  }

  /**
   * Returns a non-null map from TTL variables to their transformations in AST form.
   */
  private Map getTypeTransformations() {
    JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(this.root);
    return jsdoc == null ? ImmutableMap.of() : jsdoc.getTypeTransformations();
  }

  boolean isConstructor() {
    JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(root);
    return isFunction() && jsdoc != null && jsdoc.isConstructor();
  }

  boolean isInterface() {
    JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(root);
    return isFunction() && jsdoc != null && jsdoc.isInterface();
  }

  boolean isPrototypeMethod() {
    checkArgument(root != null);
    return NodeUtil.isPrototypeMethod(root);
  }

  void addUnknownTypeNames(Set names) {
    // TODO(dimvar): if sm uses a goog.forwardDeclare in a local scope, give
    // an error instead of crashing.
    checkState(this.isTopLevel());
    this.unknownTypeNames = ImmutableSet.copyOf(names);
  }

  void addLocalFunDef(String name, NTIScope scope) {
    checkArgument(!name.isEmpty());
    checkArgument(!name.contains("."));
    checkArgument(!isDefinedLocally(name, false));
    localFunDefs.put(name, scope);
  }

  boolean isFormalParam(String name) {
    return formals.contains(name);
  }

  boolean isFormalParamInAnyAncestorScope(String name) {
    return isFormalParam(name)
        || (this.parent != null && this.parent.isFormalParamInAnyAncestorScope(name));
  }

  boolean isLocalFunDef(String name) {
    return localFunDefs.containsKey(name);
  }

  boolean isFunctionNamespace(String name) {
    checkArgument(!name.contains("."));
    checkState(isFrozen);
    Declaration d = getDeclaration(name, false);
    if (d == null || d.getFunctionScope() == null || d.getTypeOfSimpleDecl() == null) {
      return false;
    }
    return d.getTypeOfSimpleDecl().isNamespace();
  }

  // In other languages, type names and variable names are in distinct
  // namespaces and don't clash.
  // But because our typedefs and enums are var declarations, they are in the
  // same namespace as other variables.
  boolean isDefinedLocally(String name, boolean includeTypes) {
    checkNotNull(name);
    checkState(!name.contains("."));
    if (locals.containsKey(name)
        || formals.contains(name)
        || localNamespaces.containsKey(name)
        || localFunDefs.containsKey(name)
        || "this".equals(name)
        || externs.containsKey(name)
        || localTypedefs.containsKey(name)) {
      return true;
    }
    if (includeTypes) {
      return unknownTypeNames.contains(name)
          || (declaredType != null && declaredType.isTypeVariableDefinedLocally(name));
    }
    return false;
  }

  // For variables it is the same as isDefinedLocally; for properties it looks
  // for a definition in any scope.
  boolean isDefined(Node qnameNode) {
    checkArgument(qnameNode.isQualifiedName());
    if (qnameNode.isName()) {
      return isDefinedLocally(qnameNode.getString(), false);
    } else if (qnameNode.isThis()) {
      return true;
    }
    QualifiedName qname = QualifiedName.fromNode(qnameNode);
    String leftmost = qname.getLeftmostName();
    if (isNamespace(leftmost)) {
      return getNamespace(leftmost).isDefined(qname.getAllButLeftmost());
    }
    return parent == null ? false : parent.isDefined(qnameNode);
  }

  boolean isNamespace(Node expr) {
    if (expr.isName()) {
      return isNamespace(expr.getString());
    }
    if (!expr.isGetProp()) {
      return false;
    }
    return isNamespace(QualifiedName.fromNode(expr));
  }

  boolean isNamespace(QualifiedName qname) {
    if (qname == null) {
      return false;
    }
    String leftmost = qname.getLeftmostName();
    return isNamespace(leftmost)
        && (qname.isIdentifier()
            || getNamespace(leftmost).hasSubnamespace(qname.getAllButLeftmost()));
  }

  boolean isNamespace(String name) {
    checkArgument(!name.contains("."));
    Declaration decl = getDeclaration(name, false);
    if (decl == null) {
      return false;
    }
    JSType simpleType = decl.getTypeOfSimpleDecl();
    return decl.getNamespace() != null || (simpleType != null && simpleType.isNamespace());
  }

  boolean isVisibleInScope(String name) {
    checkArgument(!name.contains("."));
    return isDefinedLocally(name, false)
        || name.equals(this.name)
        || (parent != null && parent.isVisibleInScope(name));
  }

  boolean isConstVar(String name) {
    checkArgument(!name.contains("."));
    Declaration decl = getDeclaration(name, false);
    return decl != null && decl.isConstant();
  }

  boolean isOuterVarEarly(String name) {
    checkArgument(!name.contains("."));
    return !isDefinedLocally(name, false)
        && parent != null && parent.isVisibleInScope(name);
  }

  boolean isGlobalVar(String varName) {
    NTIScope s = this;
    while (s.parent != null) {
      if (s.isDefinedLocally(varName, false)) {
        return false;
      }
      s = s.parent;
    }
    return true;
  }

  boolean isUndeclaredFormal(String name) {
    checkArgument(!name.contains("."));
    return formals.contains(name) && getDeclaredTypeOf(name) == null;
  }

  List getFormals() {
    return new ArrayList<>(formals);
  }

  Set getOuterVars() {
    return new LinkedHashSet<>(outerVars);
  }

  Set getLocalFunDefs() {
    return ImmutableSet.copyOf(localFunDefs.keySet());
  }

  boolean isOuterVar(String name) {
    return outerVars.contains(name);
  }

  boolean isUndeclaredOuterVar(String name) {
    return outerVars.contains(name) && getDeclaredTypeOf(name) == null;
  }

  boolean isEscapedVar(String name) {
    return this.escapedVars.contains(name);
  }

  boolean hasThis() {
    DeclaredFunctionType dft = this.declaredType;
    // dft is null early during GlobalTypeInfo
    return dft != null && dft.getThisType() != null;
  }

  @Override
  public JSType getDeclaredTypeOf(String name) {
    checkArgument(!name.contains("."));
    if ("this".equals(name)) {
      if (!hasThis()) {
        return null;
      }
      return getDeclaredFunctionType().getThisType();
    }
    Declaration decl = getLocalDeclaration(name, false);
    if (decl != null) {
      if (decl.getTypeOfSimpleDecl() != null) {
        Preconditions.checkState(!decl.getTypeOfSimpleDecl().isBottom(),
            "%s was bottom", name);
        return decl.getTypeOfSimpleDecl();
      }
      NTIScope funScope = (NTIScope) decl.getFunctionScope();
      if (funScope != null) {
        Preconditions.checkNotNull(
            funScope.getDeclaredFunctionType(),
            "decl=%s, funScope=%s", decl, funScope);
        return this.commonTypes.fromFunctionType(
            funScope.getDeclaredFunctionType().toFunctionType());
      }
      checkState(decl.getNamespace() == null);
      return null;
    }
    // When a function is a namespace, the parent scope has a better type.
    if (name.equals(this.name) && !parent.isFunctionNamespace(name)) {
      return this.commonTypes
          .fromFunctionType(getDeclaredFunctionType().toFunctionType());
    }
    if (parent != null) {
      return parent.getDeclaredTypeOf(name);
    }
    return null;
  }

  boolean hasUndeclaredFormalsOrOuters() {
    for (String formal : formals) {
      if (getDeclaredTypeOf(formal) == null) {
        return true;
      }
    }
    for (String outer : outerVars) {
      JSType declType = getDeclaredTypeOf(outer);
      if (declType == null
          // Undeclared functions have a non-null declared type,
          //  but they always have a return type of unknown
          || (declType.getFunType() != null
              && !declType.getFunType().isSomeConstructorOrInterface()
              && declType.getFunType().getReturnType().isUnknown())) {
        return true;
      }
    }
    return false;
  }

  private NTIScope getScopeHelper(QualifiedName qname) {
    Declaration decl = getDeclaration(qname, false);
    return decl == null ? null : (NTIScope) decl.getFunctionScope();
  }

  boolean isKnownFunction(String fnName) {
    checkArgument(!fnName.contains("."));
    return getScopeHelper(new QualifiedName(fnName)) != null;
  }

  boolean isKnownFunction(QualifiedName qname) {
    return getScopeHelper(qname) != null;
  }

  boolean isExternalFunction(String fnName) {
    NTIScope s = getScopeHelper(new QualifiedName(fnName));
    return s.root.isFromExterns();
  }

  NTIScope getScope(String fnName) {
    NTIScope s = getScopeHelper(new QualifiedName(fnName));
    checkState(s != null);
    return s;
  }

  Set getLocals() {
    return ImmutableSet.copyOf(locals.keySet());
  }

  Set getExterns() {
    return ImmutableSet.copyOf(externs.keySet());
  }

  // We don't check for duplicates here, mainly because we add some
  // intentionally during the two phases of GlobalTypeInfo.
  // If a variable is declared many times in a scope, the last definition
  // overwrites the previous ones. For correctness, we rely on the fact that
  // the var-check passes run before type checking.
  void addLocal(String name, JSType declType, boolean isConstant, boolean isFromExterns) {
    checkArgument(!name.contains("."));
    if (isConstant) {
      constVars.add(name);
    }
    if (isFromExterns) {
      externs.put(name, declType);
    } else {
      locals.put(name, declType);
    }
  }

  static void mayRecordEscapedVar(NTIScope s, String name) {
    if (s.isDefinedLocally(name, false)) {
      return;
    }
    while (s != null) {
      if (s.isDefinedLocally(name, false)) {
        s.escapedVars.add(name);
        return;
      }
      s = s.parent;
    }
  }

  RawNominalType getNominalType(QualifiedName qname) {
    Declaration decl = getDeclaration(qname, false);
    return decl == null ? null : decl.getNominal();
  }

  Typedef getTypedef(String name) {
    checkState(!name.contains("."));
    Declaration decl = getDeclaration(name, false);
    return decl == null ? null : decl.getTypedef();
  }

  EnumType getEnum(QualifiedName qname) {
    Declaration decl = getDeclaration(qname, false);
    return decl == null ? null : decl.getEnum();
  }

  Namespace getNamespace(String name) {
    checkArgument(!name.contains("."));
    Declaration decl = getDeclaration(name, false);
    return decl == null ? null : decl.getNamespace();
  }

  void addFunNamespace(Node qnameNode) {
    if (qnameNode.isName()) {
      String varName = qnameNode.getString();
      checkArgument(isDefinedLocally(varName, false));
      checkState(!this.localNamespaces.containsKey(varName));
      NTIScope s = checkNotNull(this.localFunDefs.get(varName));
      this.localNamespaces.put(varName,
          new FunctionNamespace(this.commonTypes, varName, s, qnameNode));
    } else {
      checkArgument(!isNamespace(qnameNode));
      QualifiedName qname = QualifiedName.fromNode(qnameNode);
      Namespace ns = getNamespace(qname.getLeftmostName());
      NTIScope s = (NTIScope) ns.getDeclaration(qname).getFunctionScope();
      ns.addNamespace(qname.getAllButLeftmost(),
          new FunctionNamespace(this.commonTypes, qname.toString(), s, qnameNode));
    }
  }

  void addNamespaceLit(Node qnameNode) {
    addNamespace(qnameNode,
        new NamespaceLit(this.commonTypes, qnameNode.getQualifiedName(), qnameNode));
  }

  void updateType(String name, JSType newDeclType) {
    if (isDefinedLocally(name, false)) {
      locals.put(name, newDeclType);
    } else if (parent != null) {
      parent.updateType(name, newDeclType);
    } else {
      throw new RuntimeException(
          "Cannot update type of unknown variable: " + name);
    }
  }

  void addOuterVar(String name) {
    outerVars.add(name);
  }

  void addTypedef(Node qnameNode, Typedef td) {
    if (qnameNode.isName()) {
      checkState(!localTypedefs.containsKey(qnameNode.getString()));
      localTypedefs.put(qnameNode.getString(), td);
    } else {
      checkState(!isDefined(qnameNode));
      QualifiedName qname = QualifiedName.fromNode(qnameNode);
      Namespace ns = getNamespace(qname.getLeftmostName());
      ns.addTypedef(qname.getAllButLeftmost(), td);
    }
  }

  void addNamespace(Node qnameNode, Namespace ns) {
    if (ns instanceof EnumType) {
      this.localEnums.add((EnumType) ns);
    }
    if (qnameNode.isName()) {
      String varName = qnameNode.getString();
      Preconditions.checkState(!this.localNamespaces.containsKey(varName),
          "Namespace %s already defined.", varName);
      this.localNamespaces.put(varName, ns);
      if (qnameNode.isFromExterns() && !this.externs.containsKey(varName)) {
        // We don't know the full type of a namespace until after we see all
        // its properties. But we want to add it to the externs, otherwise it
        // is treated as a local and initialized to the wrong thing in NTI.
        this.externs.put(qnameNode.getString(), null);
      }
    } else {
      checkState(!isDefined(qnameNode));
      QualifiedName qname = QualifiedName.fromNode(qnameNode);
      Namespace rootns = getNamespace(qname.getLeftmostName());
      rootns.addNamespace(qname.getAllButLeftmost(), ns);
    }
  }

  Namespace getNamespace(QualifiedName qname) {
    Namespace ns = getNamespace(qname.getLeftmostName());
    return (ns == null || qname.isIdentifier())
        ? ns : ns.getSubnamespace(qname.getAllButLeftmost());
  }

  private Declaration getLocalDeclaration(String name, boolean includeTypes) {
    checkArgument(!name.contains("."));
    if (!isDefinedLocally(name, includeTypes)) {
      return null;
    }
    JSType type = null;
    boolean isTypeVar = false;
    if ("this".equals(name)) {
      type = getDeclaredTypeOf("this");
    } else if (locals.containsKey(name)) {
      type = locals.get(name);
    } else if (formals.contains(name)) {
      int formalIndex = formals.indexOf(name);
      if (declaredType != null && formalIndex != -1) {
        JSType formalType = declaredType.getFormalType(formalIndex);
        if (formalType != null && !formalType.isBottom()) {
          type = formalType;
        }
      }
    } else if (localFunDefs.containsKey(name)) {
      // After finalization, the externs contain the correct type for
      // external function namespaces, don't rely on localFunDefs
      if (isFrozen && externs.containsKey(name)) {
        type = externs.get(name);
      }
    } else if (localTypedefs.containsKey(name) || localNamespaces.containsKey(name)) {
      // Any further declarations are shadowed
    } else if (declaredType != null && declaredType.isTypeVariableDefinedLocally(name)) {
      isTypeVar = true;
      type = JSType.fromTypeVar(this.commonTypes, declaredType.getTypeVariableDefinedLocally(name));
    } else if (externs.containsKey(name)) {
      type = externs.get(name);
    }
    Namespace ns = null;
    if (localNamespaces.containsKey(name)) {
      ns = localNamespaces.get(name);
    } else if (preservedNamespaces != null) {
      ns = preservedNamespaces.get(name);
    }

    return new Declaration(type, localTypedefs.get(name),
        ns, localFunDefs.get(name), isTypeVar,
        constVars.contains(name));
  }

  @Override
  public Declaration getDeclaration(QualifiedName qname, boolean includeTypes) {
    if (qname.isIdentifier()) {
      return getDeclaration(qname.getLeftmostName(), includeTypes);
    }
    Namespace ns = getNamespace(qname.getLeftmostName());
    if (ns == null) {
      return maybeGetForwardDeclaration(qname.toString());
    }
    Declaration decl = ns.getDeclaration(qname.getAllButLeftmost());
    return decl != null ? decl : maybeGetForwardDeclaration(qname.toString());
  }

  private Declaration maybeGetForwardDeclaration(String qname) {
    NTIScope globalScope = this;
    while (globalScope.parent != null) {
      globalScope = globalScope.parent;
    }
    if (globalScope.unknownTypeNames.contains(qname)) {
      return new Declaration(this.commonTypes.UNKNOWN, null, null, null, false, false);
    }
    return null;
  }

  public Declaration getDeclaration(String name, boolean includeTypes) {
    checkArgument(!name.contains("."));
    Declaration decl = getLocalDeclaration(name, includeTypes);
    if (decl != null) {
      return decl;
    }
    return parent == null ? null : parent.getDeclaration(name, includeTypes);
  }

  public JSType getType(String typeName) {
    checkNotNull(preservedNamespaces, "Failed to preserve namespaces post-finalization");
    QualifiedName qname = QualifiedName.fromQualifiedString(typeName);
    Namespace ns = preservedNamespaces.get(qname.getLeftmostName());
    if (ns != null && !qname.isIdentifier()) {
      ns = ns.getSubnamespace(qname.getAllButLeftmost());
    }
    if (ns instanceof RawNominalType) {
      return ((RawNominalType) ns).getInstanceAsJSType();
    }
    return null;
  }

  void resolveTypedefs(JSTypeCreatorFromJSDoc typeParser) {
    for (Typedef td : localTypedefs.values()) {
      if (!td.isResolved()) {
        typeParser.resolveTypedef(td, this);
      }
    }
  }

  void resolveEnums(JSTypeCreatorFromJSDoc typeParser) {
    for (EnumType e : localEnums) {
      if (!e.isResolved()) {
        typeParser.resolveEnum(e, this);
      }
    }
    localEnums = null;
  }

  void freezeScope() {
    Preconditions.checkNotNull(this.declaredType, "No declared type for scope: %s", this.root);
    unknownTypeNames = ImmutableSet.of();
    // For now, we put types of namespaces directly into the locals.
    // Alternatively, we could move this into NewTypeInference.initEdgeEnvs
    for (Map.Entry entry : localNamespaces.entrySet()) {
      String name = entry.getKey();
      Namespace ns = entry.getValue();
      JSType t;
      if (ns instanceof NamespaceLit) {
        constVars.add(name);
        NamespaceLit nslit = (NamespaceLit) ns;
        // The argument to maybeSetWindowInstance should only be non-null for
        // window, but we don't check here to avoid hard-coding the name.
        // Enforced in GlobalTypeInfo.
        nslit.maybeSetWindowInstance(externs.get(name));
        t = nslit.toJSType();
      } else {
        t = ns.toJSType();
      }
      if (externs.containsKey(name)) {
        externs.put(name, t);
      } else {
        locals.put(name, t);
      }
    }
    for (String typedefName : localTypedefs.keySet()) {
      locals.put(typedefName, this.commonTypes.UNDEFINED);
    }
    copyOuterVarsTransitively(this);
    preservedNamespaces = localNamespaces;
    localNamespaces = ImmutableMap.of();
    localTypedefs = ImmutableMap.of();
    escapedVars = ImmutableSet.of();
    isFrozen = true;
  }

  // A scope must know about the free variables used in outer scopes,
  // otherwise we end up with invalid type envs.
  private static void copyOuterVarsTransitively(NTIScope s) {
    if (s.isTopLevel()) {
      return;
    }
    NTIScope parent = s.parent;
    Set outerVars = s.outerVars;
    while (parent.isFunction()) {
      boolean copiedOneVar = false;
      for (String v : outerVars) {
        if (!parent.isDefinedLocally(v, false)) {
          copiedOneVar = true;
          parent.addOuterVar(v);
        }
      }
      if (!copiedOneVar) {
        break;
      }
      outerVars = parent.outerVars;
      parent = parent.parent;
    }
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    if (isTopLevel()) {
      sb.append("");
    } else {
      sb.append(getReadableName());
      sb.append('(');
      Joiner.on(',').appendTo(sb, formals);
      sb.append(')');
    }
    sb.append(" with root: ");
    sb.append(root);
    return sb.toString();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy