[dart2js] Add Rti specializer for `is FutureOr<T>`.

In particular, this fixes some type tests involving records.
`(1, true) as FutureOr<(int, bool)>` was failing because the Rti for the
record literal was _Record_2 rather than +(int, bool). We want to avoid
constructing the full record Rti, so instead we can have the specializer
for FutureOr directly delegate to its union components.

Fixes: #51910
Change-Id: I3716882aab0a519ab35803d87fabe570b7e27655
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/292242
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Mayank Patke <fishythefish@google.com>
diff --git a/sdk/lib/_internal/js_shared/lib/rti.dart b/sdk/lib/_internal/js_shared/lib/rti.dart
index 30876a3..88cce4b 100644
--- a/sdk/lib/_internal/js_shared/lib/rti.dart
+++ b/sdk/lib/_internal/js_shared/lib/rti.dart
@@ -946,6 +946,10 @@
   if (isTopType(testRti)) {
     return _finishIsFn(testRti, object, RAW_DART_FUNCTION_REF(_isTop));
   }
+  if (Rti._getKind(testRti) == Rti.kindQuestion) {
+    return _finishIsFn(testRti, object,
+        RAW_DART_FUNCTION_REF(_generalNullableIsTestImplementation));
+  }
 
   // `o is T*` generally behaves like `o is T`.
   // The exceptions are `Object*` (handled above) and `Never*`
@@ -960,6 +964,10 @@
       ? Rti._getStarArgument(testRti)
       : testRti;
 
+  if (Rti._getKind(unstarred) == Rti.kindFutureOr) {
+    return _finishIsFn(testRti, object, RAW_DART_FUNCTION_REF(_isFutureOr));
+  }
+
   var isFn = _simpleSpecializedIsTest(unstarred);
   if (isFn != null) {
     return _finishIsFn(testRti, object, isFn);
@@ -987,9 +995,6 @@
           testRti, object, RAW_DART_FUNCTION_REF(_isTestViaProperty));
     }
     // fall through to general implementation.
-  } else if (Rti._getKind(testRti) == Rti.kindQuestion) {
-    return _finishIsFn(testRti, object,
-        RAW_DART_FUNCTION_REF(_generalNullableIsTestImplementation));
   } else if (Rti._getKind(unstarred) == Rti.kindRecord) {
     isFn = _recordSpecializedIsTest(unstarred);
     return _finishIsFn(testRti, object, isFn);
@@ -1212,6 +1217,14 @@
 // Specializations can be placed on Rti objects as the _as and _is
 // 'methods'. They can also be called directly called from generated code.
 
+/// Specialization for `is FutureOr<T>`.
+/// Called from generated code via Rti `_is` method.
+bool _isFutureOr(Object? object) {
+  Rti testRti = _Utils.asRti(JS('', 'this'));
+  return Rti._isCheck(Rti._getFutureOrArgument(testRti), object) ||
+      Rti._isCheck(Rti._getFutureFromFutureOr(_theUniverse(), testRti), object);
+}
+
 /// Specialization for 'is Object'.
 /// Called from generated code via Rti `_is` method.
 bool _isObject(Object? object) {
@@ -3080,6 +3093,9 @@
     return true;
   }
 
+  // Record Type/Record:
+  if (sKind == Rti.kindRecord && isRecordInterfaceType(t)) return true;
+
   // Positional Function Types + Named Function Types:
   // TODO(fishythefish): Disallow JavaScriptFunction as a subtype of function
   // types using features inaccessible from JavaScript.
@@ -3120,15 +3136,7 @@
     return _isInterfaceSubtype(universe, s, sEnv, t, tEnv);
   }
 
-  // Records
-  //
-  // TODO(50081): Reference rules to updated specification
-  // https://github.com/dart-lang/language/blob/master/resources/type-system/subtyping.md#rules
-
-  // Record Type/Record:
-  if (sKind == Rti.kindRecord && isRecordInterfaceType(t)) return true;
-
-  // Record Type/Record Type:
+  // Record Types:
   if (sKind == Rti.kindRecord && tKind == Rti.kindRecord) {
     return _isRecordSubtype(universe, s, sEnv, t, tEnv);
   }