[dart2js] global type analysis for unreachable closure calls

The return value of a closure call on `null` or bottom should be
bottom.

Change-Id: I4520a8d6b13575172cf5407b0fbcbaa0b6be5e63
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/162384
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
diff --git a/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart b/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart
index 4aa804c..365500e 100644
--- a/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart
+++ b/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart
@@ -1502,7 +1502,20 @@
   }
 
   @override
-  AbstractValue computeType(InferrerEngine inferrer) => safeType(inferrer);
+  AbstractValue computeType(InferrerEngine inferrer) {
+    AbstractValueDomain abstractValueDomain = inferrer.abstractValueDomain;
+    AbstractValue closureType = closure.type;
+    // We are not tracking closure calls, but if the receiver is not callable,
+    // the call will fail. The abstract value domain does not have a convenient
+    // method for detecting callable types, but we know `null` and unreachable
+    // code have no result type.  This is helpful for propagating
+    // unreachability, i.e. tree-shaking.
+    if (abstractValueDomain.isEmpty(closureType).isDefinitelyTrue ||
+        abstractValueDomain.isNull(closureType).isDefinitelyTrue) {
+      return abstractValueDomain.emptyType;
+    }
+    return safeType(inferrer);
+  }
 
   @override
   Iterable<MemberEntity> get callees {
diff --git a/pkg/compiler/lib/src/inferrer/types.dart b/pkg/compiler/lib/src/inferrer/types.dart
index 25305e4..bafd5cc3 100644
--- a/pkg/compiler/lib/src/inferrer/types.dart
+++ b/pkg/compiler/lib/src/inferrer/types.dart
@@ -310,22 +310,29 @@
   @override
   AbstractValue resultTypeOfSelector(
       Selector selector, AbstractValue receiver) {
-    // Bailout for closure calls. We're not tracking types of
-    // closures.
-    if (selector.isClosureCall)
-      return closedWorld.abstractValueDomain.dynamicType;
+    AbstractValueDomain abstractValueDomain = closedWorld.abstractValueDomain;
+
+    // Bailout for closure calls. We're not tracking types of closures.
+    if (selector.isClosureCall) {
+      // But if the receiver is not callable, the call will fail.
+      if (abstractValueDomain.isEmpty(receiver).isDefinitelyTrue ||
+          abstractValueDomain.isNull(receiver).isDefinitelyTrue) {
+        return abstractValueDomain.emptyType;
+      }
+      return abstractValueDomain.dynamicType;
+    }
     if (selector.isSetter || selector.isIndexSet) {
-      return closedWorld.abstractValueDomain.dynamicType;
+      return abstractValueDomain.dynamicType;
     }
     if (returnsListElementType(selector, receiver)) {
-      return closedWorld.abstractValueDomain.getContainerElementType(receiver);
+      return abstractValueDomain.getContainerElementType(receiver);
     }
     if (returnsMapValueType(selector, receiver)) {
-      return closedWorld.abstractValueDomain.getMapValueType(receiver);
+      return abstractValueDomain.getMapValueType(receiver);
     }
 
     if (closedWorld.includesClosureCall(selector, receiver)) {
-      return closedWorld.abstractValueDomain.dynamicType;
+      return abstractValueDomain.dynamicType;
     } else {
       Iterable<MemberEntity> elements =
           closedWorld.locateMembers(selector, receiver);
@@ -334,7 +341,7 @@
         AbstractValue type = typeOfMemberWithSelector(element, selector);
         types.add(type);
       }
-      return closedWorld.abstractValueDomain.unionOfMany(types);
+      return abstractValueDomain.unionOfMany(types);
     }
   }
 
diff --git a/pkg/compiler/test/inference/data/static.dart b/pkg/compiler/test/inference/data/static.dart
index f641d1a..d34d4fc 100644
--- a/pkg/compiler/test/inference/data/static.dart
+++ b/pkg/compiler/test/inference/data/static.dart
@@ -181,7 +181,7 @@
 /*member: _field1:[null]*/
 dynamic _field1;
 
-/*member: invokeStaticFieldUninitialized:[null|subclass=Object]*/
+/*member: invokeStaticFieldUninitialized:[empty]*/
 invokeStaticFieldUninitialized() => _field1();
 
 ////////////////////////////////////////////////////////////////////////////////