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

com.google.javascript.jscomp.disambiguate.TypeGraphBuilder Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2019 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.disambiguate;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.TypeMismatch;
import com.google.javascript.jscomp.graph.LinkedDirectedGraph;
import com.google.javascript.jscomp.graph.LinkedDirectedGraph.LinkedDiGraphNode;
import com.google.javascript.jscomp.graph.LowestCommonAncestorFinder;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.ArrayList;
import java.util.Collection;

/** Builds a graph of the type-system from a specified set of seed types. */
final class TypeGraphBuilder {

  /**
   * The type relationship that caused an edge to be created.
   *
   * 

This information is only retained for diagnostics, not correctness. */ enum EdgeReason { ALGEBRAIC, ENUM_ELEMENT, FORCED, INTERFACE, PROTOTYPE; } private final TypeFlattener flattener; private final LowestCommonAncestorFinder lcaFinder; private final LinkedDiGraphNode topNode; /** * The graph of types as defined by `holdsInstanceOf`. * *

We use `holdsInstanceOf` rather than `isSupertypeOf` because we only actaully care about * edges that were "used" in the program. If instances never flow over an edge at runtime, then * properties also don't need to be tracked across that edge either. Of course, since we don't * track the types of all assignments in a program, many of the edges are `isSupertypeOf` edges * that we include to be conservative. * *

This graph, when fully constructed, is still only an approximation. This is the due to both * memory and time constraints. The following quirks are expected: * *

    *
  • Some types, such as primitives, will not have nodes. *
  • Transitive edges (shortcut edges for which there exist alternate paths) are kept minimal. *
*/ private LinkedDirectedGraph typeHoldsInstanceGraph = LinkedDirectedGraph.createWithoutAnnotations(); private final ArrayList deferredForcedEdges = new ArrayList<>(); TypeGraphBuilder( TypeFlattener flattener, LowestCommonAncestorFinder.Factory lcaFinderFactory) { this.flattener = flattener; this.lcaFinder = lcaFinderFactory.create(this.typeHoldsInstanceGraph); this.topNode = this.typeHoldsInstanceGraph.createNode(this.flattener.flatten(JSTypeNative.ALL_TYPE)); } public void add(FlatType flat) { this.addInternal(flat); } public void addAll(Collection flats) { flats.forEach(this::add); } public void addForcedEdge(TypeMismatch mismatch) { this.deferredForcedEdges.add(mismatch); // The types on both sides of the edge need to be inserted immediately. this.addInternal(mismatch.getFound()); this.addInternal(mismatch.getRequired()); } public LinkedDirectedGraph build() { this.typeHoldsInstanceGraph.getNodes().forEach(this::connectUnionWithAncestors); /** * A reference of the "required" type was forced to hold an instance of the "found" type. * *

This is done after unions are inserted because we don't want forced edges to influence the * LCAs of the unions' members. LCAs are being used to determine "what types could this union * have safely been asigned to". Considering forced edges would be incorrect for that purpose. */ for (TypeMismatch mismatch : this.deferredForcedEdges) { this.connectSourceToDest( this.addInternal(mismatch.getRequired()), EdgeReason.FORCED, this.addInternal(mismatch.getFound())); } LinkedDirectedGraph temp = this.typeHoldsInstanceGraph; this.typeHoldsInstanceGraph = null; return temp; } /** * During initial lattice construction unions were only given outbound edges, here we add any * ncessary inbound ones. * *

We defer this operation because adding union-to-union and common-supertype-to-union edges, * is a hard problem. Solving it after all other types are in place makes it easier. */ private void connectUnionWithAncestors(LinkedDiGraphNode unionNode) { FlatType flatUnion = unionNode.getValue(); if (!flatUnion.hasArity(FlatType.Arity.UNION)) { return; } /** * Connect the LCAs to the union. * *

The union itself will be found in most cases, but since we don't add self-edges, that * won't matter. * *

Some of these edges may pollute the "lattice-ness" of the graph, but all the invariants we * actually care about will be maintained. If disambiguation is too slow and stricter invariants * would help, we could be more carful. */ checkState(!unionNode.getOutEdges().isEmpty()); for (FlatType lca : this.lcaFinder.findAll(flatUnion.getTypeUnion())) { this.connectSourceToDest( checkNotNull(this.typeHoldsInstanceGraph.getNode(lca)), EdgeReason.ALGEBRAIC, unionNode); } } /** Insert {@code type} and all necessary related types into the datastructures of this pass. */ private LinkedDiGraphNode addInternal(JSType type) { return this.addInternal(this.flattener.flatten(type)); } /** Insert {@code flat} and all necessary related types into the datastructures of this pass. */ private LinkedDiGraphNode addInternal(FlatType flat) { LinkedDiGraphNode flatNode = this.typeHoldsInstanceGraph.getNode(flat); if (flatNode != null) { return flatNode; } flatNode = this.typeHoldsInstanceGraph.createNode(flat); if (flat.hasArity(FlatType.Arity.UNION)) { for (FlatType alt : flat.getTypeUnion()) { this.connectSourceToDest(flatNode, EdgeReason.ALGEBRAIC, this.addInternal(alt)); } return flatNode; } if (flat.getTypeSingle().isEnumElementType()) { LinkedDiGraphNode elementNode = this.addInternal(flat.getTypeSingle().getEnumeratedTypeOfEnumElement()); this.connectSourceToDest(elementNode, EdgeReason.ENUM_ELEMENT, flatNode); return flatNode; } if (flat.getTypeSingle().isObjectType()) { ObjectType flatObjectType = flat.getTypeSingle().toMaybeObjectType(); for (ObjectType ifaceType : ownAncestorInterfacesOf(flatObjectType)) { this.connectSourceToDest(this.addInternal(ifaceType), EdgeReason.INTERFACE, flatNode); } JSType prototypeType = flatObjectType.getImplicitPrototype(); if (prototypeType == null) { this.connectSourceToDest(this.topNode, EdgeReason.ALGEBRAIC, flatNode); } else { this.connectSourceToDest(this.addInternal(prototypeType), EdgeReason.PROTOTYPE, flatNode); } /** * Make sure we include all instance types we know about. * *

We may not see all types directly on the AST when collecting types to use in this pass, * but should still account for instance types if their associated prototype is present. */ if (flatObjectType.isFunctionPrototypeType()) { FunctionType ownerFunction = flatObjectType.getOwnerFunction(); if (ownerFunction.hasInstanceType()) { /** * Don't put an edge in. * *

Recursion up this type's prototype chain will create any required edges. * Additionally, this may not actually be the instance type of `flatObjectType`. */ this.addInternal(ownerFunction.getInstanceType()); } } /** * Add all instance and prototype types when visiting a constructor. We won't necessarily see * all possible instance types that exist at runtime during an AST traversal. * *

For example, a subclass constructor may never be explicitly initialized but instead * passed to some function expecting `function(new:Parent)`. See {@link * AmbiguatePropertiesTest#testImplementsAndExtends_respectsUndeclaredProperties()} */ FunctionType flatFunctionType = flatObjectType.toMaybeFunctionType(); if (flatFunctionType != null && flatFunctionType.hasInstanceType()) { this.addInternal(flatFunctionType.getInstanceType()); } return flatNode; } throw new AssertionError("Unexpected type: " + flat); } /** * Returns the interfaces directly implemented or extended by {@code type}. * *

This is distinct from any of the methods on {@link FunctionType}. Specifically, the result * only contains: * *

    *
  • own/direct supertypes *
  • supertypes that are actually interfaces *
*/ private static ImmutableList ownAncestorInterfacesOf(ObjectType type) { FunctionType ctorType = type.getConstructor(); if (ctorType == null) { return ImmutableList.of(); } final Collection ifaceTypes; if (ctorType.isInterface()) { ifaceTypes = ctorType.getExtendedInterfaces(); } else if (ctorType.isConstructor()) { ifaceTypes = ctorType.getOwnImplementedInterfaces(); } else { throw new AssertionError(); } if (ifaceTypes.isEmpty()) { return ImmutableList.of(); } return ifaceTypes.stream() .filter((t) -> t.getConstructor() != null && t.getConstructor().isInterface()) .collect(toImmutableList()); } private void connectSourceToDest( LinkedDiGraphNode source, EdgeReason reason, LinkedDiGraphNode dest) { if (source.equals(dest) || this.typeHoldsInstanceGraph.isConnectedInDirection(source, (t) -> true, dest)) { return; } this.typeHoldsInstanceGraph.connect(source, reason, dest); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy