diff --git a/src/com/google/javascript/jscomp/RemoveUnusedPolyfills.java b/src/com/google/javascript/jscomp/RemoveUnusedPolyfills.java index 36343be2cc6..63f818acfce 100644 --- a/src/com/google/javascript/jscomp/RemoveUnusedPolyfills.java +++ b/src/com/google/javascript/jscomp/RemoveUnusedPolyfills.java @@ -23,8 +23,8 @@ import com.google.common.collect.SetMultimap; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Node; +import com.google.javascript.rhino.TypeI; import com.google.javascript.rhino.TypeIRegistry; -import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.JSTypeNative; import java.util.HashMap; import java.util.Map; @@ -132,7 +132,13 @@ void visitPossiblePolyfillUse(Node n) { if (methods.isEmpty()) { return; } - JSType receiverType = n.getFirstChild().getJSType(); + TypeI receiverType = n.getFirstChild().getTypeI(); + if (NodeUtil.isPrototypeProperty(n)) { + TypeI maybeCtor = n.getFirstFirstChild().getTypeI(); + if (maybeCtor != null && maybeCtor.isConstructor()) { + receiverType = maybeCtor.toMaybeFunctionType().getInstanceType(); + } + } if (receiverType == null) { // TODO(sdh): When does this happen? If it means incomplete type information, then // we need to remove all the potential methods. If not, we can just return. @@ -142,8 +148,8 @@ void visitPossiblePolyfillUse(Node n) { receiverType = receiverType.restrictByNotNullOrUndefined(); TypeIRegistry registry = compiler.getTypeIRegistry(); if (receiverType.isUnknownType() - || receiverType.isEmptyType() - || receiverType.isAllType() + || receiverType.isBottom() + || receiverType.isTop() || receiverType.isEquivalentTo( registry.getNativeType(JSTypeNative.OBJECT_TYPE))) { unusedMethodPolyfills.keySet().removeAll(methods); @@ -158,12 +164,12 @@ void visitPossiblePolyfillUse(Node n) { } private void checkType( - JSType targetType, TypeIRegistry registry, PrototypeMethod method, String typeName) { - JSType type = registry.getType(typeName); + TypeI receiverType, TypeIRegistry registry, PrototypeMethod method, String typeName) { + TypeI type = registry.getType(typeName); if (type == null) { throw new RuntimeException("Missing built-in type: " + typeName); } - if (!targetType.getGreatestSubtype(type).isBottom()) { + if (!receiverType.meetWith(type).isBottom()) { unusedMethodPolyfills.remove(method); } } diff --git a/src/com/google/javascript/jscomp/newtypes/JSType.java b/src/com/google/javascript/jscomp/newtypes/JSType.java index 5bb3c57b052..45e0a6df024 100644 --- a/src/com/google/javascript/jscomp/newtypes/JSType.java +++ b/src/com/google/javascript/jscomp/newtypes/JSType.java @@ -1625,6 +1625,11 @@ public ObjectTypeI autoboxAndGetObject() { return this.autobox().restrictByNotNullOrUndefined().toMaybeObjectType(); } + @Override + public TypeI meetWith(TypeI other) { + return meet(this, (JSType) other); + } + @Override public boolean equals(Object o) { if (o == null) { diff --git a/src/com/google/javascript/rhino/TypeI.java b/src/com/google/javascript/rhino/TypeI.java index 57054300690..3f5f0aec472 100644 --- a/src/com/google/javascript/rhino/TypeI.java +++ b/src/com/google/javascript/rhino/TypeI.java @@ -118,5 +118,7 @@ public interface TypeI { */ Iterable getUnionMembers(); + TypeI meetWith(TypeI other); + String getDisplayName(); } diff --git a/src/com/google/javascript/rhino/jstype/JSType.java b/src/com/google/javascript/rhino/jstype/JSType.java index 7f371e31b03..98e30edaa4c 100644 --- a/src/com/google/javascript/rhino/jstype/JSType.java +++ b/src/com/google/javascript/rhino/jstype/JSType.java @@ -1047,6 +1047,11 @@ public JSType getGreatestSubtype(JSType that) { return getGreatestSubtype(this, that); } + @Override + public TypeI meetWith(TypeI that) { + return getGreatestSubtype(this, (JSType) that); + } + /** * A generic implementation meant to be used as a helper for common * getGreatestSubtype implementations. diff --git a/test/com/google/javascript/jscomp/RemoveUnusedPolyfillsTest.java b/test/com/google/javascript/jscomp/RemoveUnusedPolyfillsTest.java index 35a92315470..54b52e0ccee 100644 --- a/test/com/google/javascript/jscomp/RemoveUnusedPolyfillsTest.java +++ b/test/com/google/javascript/jscomp/RemoveUnusedPolyfillsTest.java @@ -17,14 +17,14 @@ package com.google.javascript.jscomp; /** Unit tests for the RemoveUnusedPolyfills compiler pass. */ -public final class RemoveUnusedPolyfillsTest extends CompilerTestCase { +public final class RemoveUnusedPolyfillsTest extends TypeICompilerTestCase { private static final String EXTERNS = LINE_JOINER.join( + DEFAULT_EXTERNS, // Polyfill "var $jscomp = {};", "$jscomp.polyfill = function(name, func, from, to) {};", // Methods - "Function.prototype.call = function(ctx) {};", "Array.prototype.includes = function() {};", "String.prototype.includes = function() {};", "/** @constructor */ function Foo() {}", @@ -62,7 +62,6 @@ public final class RemoveUnusedPolyfillsTest extends CompilerTestCase { public RemoveUnusedPolyfillsTest() { super(EXTERNS); - enableTypeCheck(); } @Override @@ -70,6 +69,14 @@ protected CompilerPass getProcessor(final Compiler compiler) { return new RemoveUnusedPolyfills(compiler); } + @Override + protected CompilerOptions getOptions() { + CompilerOptions options = super.getOptions(); + // NTI warns about property accesses on * + options.setWarningLevel(DiagnosticGroups.NEW_CHECK_TYPES_EXTRA_CHECKS, CheckLevel.OFF); + return options; + } + public void testRemovesPolyfillInstanceMethods() { test(STRING_INCLUDES, ""); test(STRING_INCLUDES + ARRAY_INCLUDES, ""); @@ -104,6 +111,8 @@ public void testDoesNotRemoveMethodsCalledOnAllType() { public void testDoesNotRemoveMethodsCalledOnObject() { testSame(STRING_INCLUDES + "obj.includes();"); testSame(BOTH_INCLUDES + "obj.includes();"); + testSame(ARRAY_INCLUDES + + "function f(/** !Object */ x) { x.includes = function() {}; x.includes(); }"); } public void testDoesNotRemoveMethodsCalledOnCorrectTypes() {