fix #28642, handling of top and bottom types in DDC
in particular: void, Null, and combinations with FutureOr<T>

R=leafp@google.com

Review-Url: https://codereview.chromium.org/2671113002 .
diff --git a/pkg/dev_compiler/lib/js/amd/dart_sdk.js b/pkg/dev_compiler/lib/js/amd/dart_sdk.js
index 21e2524..b612e9f 100644
--- a/pkg/dev_compiler/lib/js/amd/dart_sdk.js
+++ b/pkg/dev_compiler/lib/js/amd/dart_sdk.js
@@ -1709,10 +1709,7 @@
         return null;
       }
     }
-    if (ret2 === dart.void) return true;
-    if (ret1 === dart.void) {
-      return ret2 === dart.dynamic || ret2 === async.FutureOr;
-    }
+    if (ret1 === dart.void) return dart._isTop(ret2);
     if (!dart._isSubtype(ret1, ret2, isCovariant)) return null;
     return true;
   };
@@ -1733,13 +1730,13 @@
     };
   };
   dart._isBottom = function(type) {
-    return type == dart.bottom;
+    return type == dart.bottom || type == core.Null;
   };
   dart._isTop = function(type) {
     if (dart.getGenericClass(type) === dart.getGenericClass(async.FutureOr)) {
       return dart._isTop(dart.getGenericArgs(type)[0]);
     }
-    return type == core.Object || type == dart.dynamic;
+    return type == core.Object || type == dart.dynamic || type == dart.void;
   };
   dart._isSubtype = function(t1, t2, isCovariant) {
     if (t1 === t2) return true;
@@ -2186,6 +2183,9 @@
     return false;
   };
   dart.is = function(obj, type) {
+    if (obj == null) {
+      return type == core.Null || dart._isTop(type);
+    }
     let result = dart.strongInstanceOf(obj, type);
     if (result !== null) return result;
     let actual = dart.getReifiedType(obj);
diff --git a/pkg/dev_compiler/lib/js/common/dart_sdk.js b/pkg/dev_compiler/lib/js/common/dart_sdk.js
index 66320d1..a35267b 100644
--- a/pkg/dev_compiler/lib/js/common/dart_sdk.js
+++ b/pkg/dev_compiler/lib/js/common/dart_sdk.js
@@ -1709,10 +1709,7 @@
         return null;
       }
     }
-    if (ret2 === dart.void) return true;
-    if (ret1 === dart.void) {
-      return ret2 === dart.dynamic || ret2 === async.FutureOr;
-    }
+    if (ret1 === dart.void) return dart._isTop(ret2);
     if (!dart._isSubtype(ret1, ret2, isCovariant)) return null;
     return true;
   };
@@ -1733,13 +1730,13 @@
     };
   };
   dart._isBottom = function(type) {
-    return type == dart.bottom;
+    return type == dart.bottom || type == core.Null;
   };
   dart._isTop = function(type) {
     if (dart.getGenericClass(type) === dart.getGenericClass(async.FutureOr)) {
       return dart._isTop(dart.getGenericArgs(type)[0]);
     }
-    return type == core.Object || type == dart.dynamic;
+    return type == core.Object || type == dart.dynamic || type == dart.void;
   };
   dart._isSubtype = function(t1, t2, isCovariant) {
     if (t1 === t2) return true;
@@ -2186,6 +2183,9 @@
     return false;
   };
   dart.is = function(obj, type) {
+    if (obj == null) {
+      return type == core.Null || dart._isTop(type);
+    }
     let result = dart.strongInstanceOf(obj, type);
     if (result !== null) return result;
     let actual = dart.getReifiedType(obj);
diff --git a/pkg/dev_compiler/lib/js/es6/dart_sdk.js b/pkg/dev_compiler/lib/js/es6/dart_sdk.js
index 83266e7..a7fbad0 100644
--- a/pkg/dev_compiler/lib/js/es6/dart_sdk.js
+++ b/pkg/dev_compiler/lib/js/es6/dart_sdk.js
@@ -1707,10 +1707,7 @@
       return null;
     }
   }
-  if (ret2 === dart.void) return true;
-  if (ret1 === dart.void) {
-    return ret2 === dart.dynamic || ret2 === async.FutureOr;
-  }
+  if (ret1 === dart.void) return dart._isTop(ret2);
   if (!dart._isSubtype(ret1, ret2, isCovariant)) return null;
   return true;
 };
@@ -1731,13 +1728,13 @@
   };
 };
 dart._isBottom = function(type) {
-  return type == dart.bottom;
+  return type == dart.bottom || type == core.Null;
 };
 dart._isTop = function(type) {
   if (dart.getGenericClass(type) === dart.getGenericClass(async.FutureOr)) {
     return dart._isTop(dart.getGenericArgs(type)[0]);
   }
-  return type == core.Object || type == dart.dynamic;
+  return type == core.Object || type == dart.dynamic || type == dart.void;
 };
 dart._isSubtype = function(t1, t2, isCovariant) {
   if (t1 === t2) return true;
@@ -2184,6 +2181,9 @@
   return false;
 };
 dart.is = function(obj, type) {
+  if (obj == null) {
+    return type == core.Null || dart._isTop(type);
+  }
   let result = dart.strongInstanceOf(obj, type);
   if (result !== null) return result;
   let actual = dart.getReifiedType(obj);
diff --git a/pkg/dev_compiler/lib/js/legacy/dart_sdk.js b/pkg/dev_compiler/lib/js/legacy/dart_sdk.js
index 5ed7333..b313c6a 100644
--- a/pkg/dev_compiler/lib/js/legacy/dart_sdk.js
+++ b/pkg/dev_compiler/lib/js/legacy/dart_sdk.js
@@ -1710,10 +1710,7 @@
         return null;
       }
     }
-    if (ret2 === dart.void) return true;
-    if (ret1 === dart.void) {
-      return ret2 === dart.dynamic || ret2 === async.FutureOr;
-    }
+    if (ret1 === dart.void) return dart._isTop(ret2);
     if (!dart._isSubtype(ret1, ret2, isCovariant)) return null;
     return true;
   };
@@ -1734,13 +1731,13 @@
     };
   };
   dart._isBottom = function(type) {
-    return type == dart.bottom;
+    return type == dart.bottom || type == core.Null;
   };
   dart._isTop = function(type) {
     if (dart.getGenericClass(type) === dart.getGenericClass(async.FutureOr)) {
       return dart._isTop(dart.getGenericArgs(type)[0]);
     }
-    return type == core.Object || type == dart.dynamic;
+    return type == core.Object || type == dart.dynamic || type == dart.void;
   };
   dart._isSubtype = function(t1, t2, isCovariant) {
     if (t1 === t2) return true;
@@ -2187,6 +2184,9 @@
     return false;
   };
   dart.is = function(obj, type) {
+    if (obj == null) {
+      return type == core.Null || dart._isTop(type);
+    }
     let result = dart.strongInstanceOf(obj, type);
     if (result !== null) return result;
     let actual = dart.getReifiedType(obj);
diff --git a/pkg/dev_compiler/test/browser/runtime_tests.js b/pkg/dev_compiler/test/browser/runtime_tests.js
index 39d9a53..3cb562a 100644
--- a/pkg/dev_compiler/test/browser/runtime_tests.js
+++ b/pkg/dev_compiler/test/browser/runtime_tests.js
@@ -4,6 +4,7 @@
 
 define(['dart_sdk'], function(dart_sdk) {
   const assert = chai.assert;
+  const async = dart_sdk.async;
   const core = dart_sdk.core;
   const collection = dart_sdk.collection;
   const dart = dart_sdk.dart;
@@ -211,10 +212,13 @@
 
     function checkType(x, type, expectedTrue, strongOnly) {
       if (expectedTrue === undefined) expectedTrue = true;
-      if (strongOnly == undefined) strongOnly = false;
+      if (strongOnly === undefined) strongOnly = false;
       if (!strongOnly) {
         assert.doesNotThrow(() => instanceOf(x, type));
-        expect(instanceOf(x, type), expectedTrue);
+        expect(instanceOf(x, type), expectedTrue,
+          '"' + x + '" ' +
+          (expectedTrue ? 'should' : 'should not') +
+          ' be an instance of "' + dart.typeName(type) + '"');
       } else {
         assert.throws(() => instanceOf(x, type), dart.StrongModeError);
         expect(expectedTrue, false);
@@ -801,25 +805,34 @@
     let dyn = dart.dynamic;
 
     function always(t1, t2) {
-      assert.equal(isSubtype(t1, t2), true);
+      assert.equal(isSubtype(t1, t2), true,
+          dart.toString(t1) +
+          " should always be a subtype of " +
+          dart.toString(t2));
     }
     function never(t1, t2) {
-      assert.equal(isSubtype(t1, t2), false);
+      assert.equal(isSubtype(t1, t2), false,
+          dart.toString(t1) +
+          " should never be a subtype of " +
+          dart.toString(t2));
     }
     function maybe(t1, t2) {
-      assert.equal(isSubtype(t1, t2), null);
+      assert.equal(isSubtype(t1, t2), null,
+          dart.toString(t1) +
+          " should maybe be a subtype of " +
+          dart.toString(t2));
     }
 
     function always2(t1, t2) {
-      assert.equal(isSubtype(t1, t2), true);
+      always(t1, t2);
       always(functionType(t1, [t2]), functionType(t2, [t1]));
     }
     function never2(t1, t2) {
-      assert.equal(isSubtype(t1, t2), false);
+      never(t1, t2);
       maybe(functionType(t1, [t2]), functionType(t2, [t1]));
     }
     function maybe2(t1, t2) {
-      assert.equal(isSubtype(t1, t2), null);
+      maybe(t1, t2);
       maybe(functionType(t1, [t2]), functionType(t2, [t1]));
     }
 
@@ -919,6 +932,44 @@
       run_test(func1, func2, func2opt, func1extra, func2extra);
     });
 
+    test('top and bottom types', () => {
+      let FutureOr = async.FutureOr$;
+      let tops = [
+        dart.dynamic,
+        core.Object,
+        dart.void,
+        FutureOr(dart.dynamic),
+        FutureOr(core.Object),
+        FutureOr(dart.void),
+        FutureOr(FutureOr(core.Object)),
+        // ... skip the (infinite) rest of the top types :D
+      ];
+      let bottoms = [dart.bottom, core.Null];
+
+      for (let top of tops) {
+        for (let bottom of bottoms) {
+          always(bottom, top);
+          always(
+              definiteFunctionType(bottom, [top]),
+              definiteFunctionType(top, [bottom]));
+        }
+      }
+
+      for (let equalTypes of [tops, bottoms]) {
+        for (let t1 of equalTypes) {
+          for (let t2 of equalTypes) {
+            always(t1, t2);
+            always(t2, t1);
+
+            let t11 = definiteFunctionType(t1, [t1]);
+            let t22 = definiteFunctionType(t2, [t2]);
+            always(t11, t22);
+            always(t22, t11);
+          }
+        }
+      }
+    });
+
     test('basic typedefs', () => {
       function func1(S) {
         return dart.typedef('Func1', () => functionType(S, []))
@@ -975,7 +1026,6 @@
       always(functionType(dyn, [], [dyn]), functionType(dyn, [dyn]));
       always(functionType(dyn, [], [dyn]), functionType(dyn, []));
       always(functionType(dyn, [dyn], {extra: dyn}), functionType(dyn, [dyn]));
-
     });
 
     test('void function types', () => {
@@ -1020,7 +1070,6 @@
       never(functionType(dart.void, [], [int]), functionType(int, [int]));
       never(functionType(dart.void, [], [int]), functionType(int, []));
       never(functionType(dart.void, [int], {extra: int}), functionType(int, [int]));
-
     });
 
     test('higher-order typedef', () => {
@@ -1060,10 +1109,7 @@
       maybe(AA$(functionType(dyn, [dyn])), AA$(functionType(int, [int])));
       maybe(AA$(functionType(core.Object, [core.Object])),
             AA$(functionType(int, [int])));
-
-
     });
-
   });
 
   suite('canonicalization', function() {
diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart
index 5975ddc..69b01a5 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart
@@ -456,6 +456,9 @@
 instanceOf(obj, type) => JS(
     '',
     '''(() => {
+  if ($obj == null) {
+    return $type == $Null || $_isTop($type);
+  }
   let result = $strongInstanceOf($obj, $type);
   if (result !== null) return result;
   let actual = $getReifiedType($obj);
diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart
index abc476d..e64daf7 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart
@@ -656,15 +656,9 @@
   // Check return type last, so that arity mismatched functions can be
   // definitively rejected.
 
-  // We allow any type to subtype a void return type, but not vice versa
-  if (ret2 === $_void) return true;
-  // Dart allows void functions to subtype dynamic functions, but not
-  // other functions.
-  // TODO(jmesserly): this check does not match our compile time subtype
-  // implementation. Reconcile.
-  if (ret1 === $_void) {
-    return ret2 === $dynamic || ret2 === $FutureOr;
-  }
+  // For `void` we will give the same answer as the VM, so don't return null.
+  if (ret1 === $_void) return $_isTop(ret2);
+
   if (!$_isSubtype(ret1, ret2, $isCovariant)) return null;
   return true;
 })()''');
@@ -698,13 +692,14 @@
 final isSubtype = JS(
     '', '$_subtypeMemo((t1, t2) => (t1 === t2) || $_isSubtype(t1, t2, true))');
 
-_isBottom(type) => JS('bool', '# == #', type, bottom);
+_isBottom(type) => JS('bool', '# == # || # == #', type, bottom, type, Null);
 
 _isTop(type) {
   if (JS('bool', '# === #', getGenericClass(type), getGenericClass(FutureOr))) {
     return _isTop(JS('', '#[0]', getGenericArgs(type)));
   }
-  return JS('bool', '# == # || # == #', type, Object, type, dynamic);
+  return JS('bool', '# == # || # == # || # == #',
+      type, Object, type, dynamic, type, _void);
 }
 
 _isSubtype(t1, t2, isCovariant) => JS(