Skip to content

Commit

Permalink
Small refactoring in RemoveUnusedPolyfills in preparation for convert…
Browse files Browse the repository at this point in the history
…ing to TypeI.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=138416819
  • Loading branch information
dimvar authored and blickly committed Nov 8, 2016
1 parent b439d04 commit 8446fa7
Showing 1 changed file with 70 additions and 74 deletions.
144 changes: 70 additions & 74 deletions src/com/google/javascript/jscomp/RemoveUnusedPolyfills.java
Expand Up @@ -17,8 +17,8 @@
package com.google.javascript.jscomp; package com.google.javascript.jscomp;


import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.SetMultimap; import com.google.common.collect.SetMultimap;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
Expand Down Expand Up @@ -56,14 +56,10 @@ class RemoveUnusedPolyfills implements CompilerPass {


@Override @Override
public void process(Node externs, Node root) { public void process(Node externs, Node root) {
Traverser traverser = new Traverser(); CollectUnusedPolyfills collector = new CollectUnusedPolyfills();
NodeTraversal.traverseEs6(compiler, root, traverser); NodeTraversal.traverseEs6(compiler, root, collector);
boolean changed = false; for (Node node : collector.removableNodes()) {
for (Node node : traverser.removableNodes()) {
NodeUtil.removeChild(node.getParent(), node); NodeUtil.removeChild(node.getParent(), node);
changed = true;
}
if (changed) {
compiler.reportCodeChange(); compiler.reportCodeChange();
} }
} }
Expand All @@ -73,77 +69,90 @@ public void process(Node externs, Node root) {
"Number", "number", "Number", "number",
"String", "string"); "String", "string");


private class Traverser extends AbstractPostOrderCallback { private class CollectUnusedPolyfills extends AbstractPostOrderCallback {


final SetMultimap<String, PrototypeMethod> methodsByName = HashMultimap.create(); final SetMultimap<String, PrototypeMethod> methodsByName = HashMultimap.create();
final Map<PrototypeMethod, Node> methodPolyfills = new HashMap<>(); // These maps map polyfill names to their definitions in the AST.
final Map<String, Node> staticPolyfills = new HashMap<>(); // Each polyfill is considered unused by default, and if we find uses of it we
// remove it from these maps.
final Map<PrototypeMethod, Node> unusedMethodPolyfills = new HashMap<>();
final Map<String, Node> unusedStaticPolyfills = new HashMap<>();


Iterable<Node> removableNodes() { Iterable<Node> removableNodes() {
return Iterables.concat(methodPolyfills.values(), staticPolyfills.values()); return Iterables.concat(unusedMethodPolyfills.values(), unusedStaticPolyfills.values());
}

@Override
public void visit(NodeTraversal traversal, Node n, Node parent) {
if (NodeUtil.isExprCall(n)) {
Node call = n.getFirstChild();
Node callee = call.getFirstChild();
String originalName = callee.getOriginalQualifiedName();
if ("$jscomp.polyfill".equals(originalName)) {
// A polyfill definition looks like this:
// $jscomp.polyfill('Array.prototype.includes', ...);
String polyfillName = call.getSecondChild().getString();
visitPolyfillDefinition(n, polyfillName);
}
} else if (n.isGetProp() || n.isQualifiedName()) {
visitPossiblePolyfillUse(n);
}
} }


void visitPolyfillDefinition(Node node, String polyfill) { void visitPolyfillDefinition(Node n, String polyfillName) {
// Find the $jscomp.polyfill calls and add them to the table. // Find the $jscomp.polyfill calls and add them to the table.
PrototypeMethod method = PrototypeMethod.split(polyfill); PrototypeMethod method = PrototypeMethod.split(polyfillName);
if (method != null) { if (method != null) {
if (methodPolyfills.put(method, node) != null) { if (unusedMethodPolyfills.put(method, n) != null) {
throw new RuntimeException(method + " polyfilled multiple times."); throw new RuntimeException(method + " polyfilled multiple times.");
} }
methodsByName.put(method.method, method); methodsByName.put(method.method, method);
} else { } else {
if (staticPolyfills.put(polyfill, node) != null) { if (unusedStaticPolyfills.put(polyfillName, n) != null) {
throw new RuntimeException(polyfill + " polyfilled multiple times."); throw new RuntimeException(polyfillName + " polyfilled multiple times.");
} }
} }
} }


void visitPossiblePolyfillUse(Node node) { void visitPossiblePolyfillUse(Node n) {
// Remove anything from the table that could possibly be needed. // Remove anything from the table that could possibly be needed.

if (n.isQualifiedName()) {
if (node.isQualifiedName()) {
// First remove anything with an exact qualified name match. // First remove anything with an exact qualified name match.
String qname = node.getQualifiedName(); String qname = n.getQualifiedName();
qname = qname.replaceAll("^(goog\\.global\\.|window\\.)", ""); qname = qname.replaceAll("^(goog\\.global\\.|window\\.)", "");
staticPolyfills.remove(qname); unusedStaticPolyfills.remove(qname);
methodPolyfills.remove(PrototypeMethod.split(qname)); unusedMethodPolyfills.remove(PrototypeMethod.split(qname));
} }

if (!n.isGetProp()) {
if (node.isGetProp()) { return;
// Now look at the method name and possible target types. }
String methodName = node.getLastChild().getString(); // Now look at the method name and possible target types.
Node target = node.getFirstChild(); String methodName = n.getLastChild().getString();

Set<PrototypeMethod> methods = methodsByName.get(methodName);
Set<PrototypeMethod> methods = methodsByName.get(methodName); if (methods.isEmpty()) {

return;
if (methods.isEmpty()) { }
return; JSType receiverType = n.getFirstChild().getJSType();
} if (receiverType == null) {

// TODO(sdh): When does this happen? If it means incomplete type information, then
JSType targetType = target.getJSType(); // we need to remove all the potential methods. If not, we can just return.
if (targetType == null) { unusedMethodPolyfills.keySet().removeAll(methods);
// TODO(sdh): When does this happen? If it means incomplete type information, then return;
// we need to remove all the potential methods. If not, we can just return. }
methodPolyfills.keySet().removeAll(methods); receiverType = receiverType.restrictByNotNullOrUndefined();
return; TypeIRegistry registry = compiler.getTypeIRegistry();
} if (receiverType.isUnknownType()
targetType = targetType.restrictByNotNullOrUndefined(); || receiverType.isEmptyType()

|| receiverType.isAllType()
TypeIRegistry registry = compiler.getTypeIRegistry(); || receiverType.isEquivalentTo(
if (targetType.isUnknownType() registry.getNativeType(JSTypeNative.OBJECT_TYPE))) {
|| targetType.isEmptyType() unusedMethodPolyfills.keySet().removeAll(methods);
|| targetType.isAllType() }
|| targetType.isEquivalentTo( for (PrototypeMethod method : ImmutableSet.copyOf(methods)) {
registry.getNativeType(JSTypeNative.OBJECT_TYPE))) { checkType(receiverType, registry, method, method.type);
methodPolyfills.keySet().removeAll(methods); String primitiveType = PRIMITIVE_WRAPPERS.get(method.type);
} if (primitiveType != null) {

checkType(receiverType, registry, method, primitiveType);
for (PrototypeMethod method : ImmutableList.copyOf(methods)) {
checkType(targetType, registry, method, method.type);
String primitiveType = PRIMITIVE_WRAPPERS.get(method.type);
if (primitiveType != null) {
checkType(targetType, registry, method, primitiveType);
}
} }
} }
} }
Expand All @@ -155,21 +164,8 @@ private void checkType(
throw new RuntimeException("Missing built-in type: " + typeName); throw new RuntimeException("Missing built-in type: " + typeName);
} }
if (!targetType.getGreatestSubtype(type).isBottom()) { if (!targetType.getGreatestSubtype(type).isBottom()) {
methodPolyfills.remove(method); unusedMethodPolyfills.remove(method);
}
}

@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
if (NodeUtil.isExprCall(node)) {
Node call = node.getFirstChild();
Node name = call.getFirstChild();
String originalName = name.getOriginalQualifiedName();
if ("$jscomp.polyfill".equals(originalName)) {
visitPolyfillDefinition(node, name.getNext().getString());
}
} }
visitPossiblePolyfillUse(node);
} }
} }


Expand All @@ -190,7 +186,7 @@ private static class PrototypeMethod {
return Objects.hash(type, method); return Objects.hash(type, method);
} }
@Override public String toString() { @Override public String toString() {
return type + ".prototype." + method; return type + PROTOTYPE + method;
} }
static PrototypeMethod split(String name) { static PrototypeMethod split(String name) {
int index = name.indexOf(PROTOTYPE); int index = name.indexOf(PROTOTYPE);
Expand Down

0 comments on commit 8446fa7

Please sign in to comment.