[dart2js] Support Future/FutureOr in RTI.

Change-Id: Ib0d849947d023a421d682061920faac8e6d7d352
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107220
Reviewed-by: Stephen Adams <sra@google.com>
diff --git a/sdk/lib/_internal/js_runtime/lib/rti.dart b/sdk/lib/_internal/js_runtime/lib/rti.dart
index 17cb4d4..1c45247 100644
--- a/sdk/lib/_internal/js_runtime/lib/rti.dart
+++ b/sdk/lib/_internal/js_runtime/lib/rti.dart
@@ -468,6 +468,9 @@
   static String _canonicalRecipeOfNever() => '0&';
   static String _canonicalRecipeOfAny() => '1&';
 
+  static String _canonicalRecipeOfFutureOr(Rti baseType) =>
+      '${Rti._getCanonicalRecipe(baseType)}/';
+
   static Rti _lookupDynamicRti(universe) {
     return _lookupTerminalRti(
         universe, Rti.kindDynamic, _canonicalRecipeOfDynamic());
@@ -500,6 +503,23 @@
     return _finishRti(universe, rti);
   }
 
+  static Rti _lookupFutureOrRti(universe, Rti baseType) {
+    String canonicalRecipe = _canonicalRecipeOfFutureOr(baseType);
+    var cache = evalCache(universe);
+    var probe = _cacheGet(cache, canonicalRecipe);
+    if (probe != null) return _castToRti(probe);
+    return _createFutureOrRti(universe, baseType, canonicalRecipe);
+  }
+
+  static Rti _createFutureOrRti(
+      universe, Rti baseType, String canonicalRecipe) {
+    var rti = Rti.allocate();
+    Rti._setKind(rti, Rti.kindFutureOr);
+    Rti._setPrimary(rti, baseType);
+    Rti._setCanonicalRecipe(rti, canonicalRecipe);
+    return _finishRti(universe, rti);
+  }
+
   static String _canonicalRecipeJoin(Object arguments) {
     String s = '', sep = '';
     int length = _Utils.arrayLength(arguments);
@@ -771,6 +791,13 @@
             handleExtendedOperations(parser, stack);
             break;
 
+          case Recipe.wrapFutureOr:
+            push(
+                stack,
+                _Universe._lookupFutureOrRti(universe(parser),
+                    toType(universe(parser), environment(parser), pop(stack))));
+            break;
+
           default:
             JS('', 'throw "Bad character " + #', ch);
         }
@@ -976,7 +1003,14 @@
       // `true` because [s] <: T.
       return true;
     } else {
-      // TODO(fishythefish): Check [s] <: Future<T>.
+      // Check [s] <: Future<T>.
+      // TODO(fishythefish): Do this without allocating an RTI for Future<T>
+      // each time.
+      String futureClass = JS_GET_NAME(JsGetName.FUTURE_CLASS_TYPE_NAME);
+      var argumentsArray = JS('', '[#]', tTypeArgument);
+      var tFuture =
+          _Universe._lookupInterfaceRti(universe, futureClass, argumentsArray);
+      return _isSubtype(universe, s, sEnv, tFuture, tEnv);
     }
   }
 
diff --git a/tests/compiler/dart2js_extra/rti/subtype_test.dart b/tests/compiler/dart2js_extra/rti/subtype_test.dart
index 78753f7..ab6f2cf 100644
--- a/tests/compiler/dart2js_extra/rti/subtype_test.dart
+++ b/tests/compiler/dart2js_extra/rti/subtype_test.dart
@@ -8,6 +8,7 @@
 import "package:expect/expect.dart";
 
 final String objectName = JS_GET_NAME(JsGetName.OBJECT_CLASS_TYPE_NAME);
+final String futureName = JS_GET_NAME(JsGetName.FUTURE_CLASS_TYPE_NAME);
 final String nullName = JS_GET_NAME(JsGetName.NULL_CLASS_TYPE_NAME);
 
 const typeRulesJson = r'''
@@ -34,6 +35,12 @@
   strictSubtype('CodeUnits', 'Iterable<num>');
   strictSubtype('Iterable<int>', 'Iterable<num>');
   strictSubtype('List<int>', objectName);
+  strictSubtype('$futureName<int>', '$futureName<num>');
+  strictSubtype('int', 'int/');
+  strictSubtype('$futureName<int>', 'int/');
+  strictSubtype('int/', 'num/');
+  strictSubtype('int', 'num/');
+  strictSubtype('$futureName<int>', 'num/');
   strictSubtype(nullName, 'int');
   strictSubtype(nullName, 'Iterable<CodeUnits>');
   strictSubtype(nullName, objectName);
@@ -57,6 +64,7 @@
   equivalent('List<@>', 'List<~>');
   equivalent('List<@>', 'List<1&>');
   equivalent('List<~>', 'List<1&>');
+  equivalent('@/', '~/');
 }
 
 String reason(String s, String t) => "$s <: $t";