[dart2js] Add subtyping rules to new RTI.

Change-Id: I9b70e2ccfc2dbac768fdccf4449b0f551a4fb5cc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106486
Commit-Queue: Mayank Patke <fishythefish@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index 2ae2052..c2b4798 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -1920,7 +1920,7 @@
     }
 
     initField(RtiUniverseFieldNames.evalCache, 'new Map()');
-    initField(RtiUniverseFieldNames.unprocessedRules, '[]');
+    initField(RtiUniverseFieldNames.typeRules, '{}');
     initField(RtiUniverseFieldNames.sharedEmptyArray, '[]');
 
     return js.ObjectInitializer(universeFields);
diff --git a/sdk/lib/_internal/js_runtime/lib/rti.dart b/sdk/lib/_internal/js_runtime/lib/rti.dart
index 0f6aa69..fe11d73 100644
--- a/sdk/lib/_internal/js_runtime/lib/rti.dart
+++ b/sdk/lib/_internal/js_runtime/lib/rti.dart
@@ -158,6 +158,11 @@
     return JS('JSUnmodifiableArray', '#', _getRest(rti));
   }
 
+  static Rti _getFutureOrArgument(Rti rti) {
+    assert(_getKind(rti) == kindFutureOr);
+    return _castToRti(_getPrimary(rti));
+  }
+
   /// On [Rti]s that are type environments*, derived types are cached on the
   /// environment to ensure fast canonicalization. Ground-term types (i.e. not
   /// dependent on class or function type parameters) are cached in the
@@ -334,16 +339,16 @@
   @pragma('dart2js:noInline')
   static Object create() {
     // This needs to be kept in sync with `FragmentEmitter.createRtiUniverse` in
-    // `fragment_emtter.dart`.
+    // `fragment_emitter.dart`.
     return JS(
         '',
         '{'
             '#: new Map(),'
-            '#: [],'
+            '#: {},'
             '#: [],' // shared empty array.
             '}',
         RtiUniverseFieldNames.evalCache,
-        RtiUniverseFieldNames.unprocessedRules,
+        RtiUniverseFieldNames.typeRules,
         RtiUniverseFieldNames.sharedEmptyArray);
   }
 
@@ -352,9 +357,12 @@
   static evalCache(universe) =>
       JS('', '#.#', universe, RtiUniverseFieldNames.evalCache);
 
-  static void addRules(universe, String rules) {
-    JS('', '#.#.push(#)', universe, RtiUniverseFieldNames.unprocessedRules,
-        rules);
+  static findRule(universe, String targetType) =>
+      JS('', '#.#.#', universe, RtiUniverseFieldNames.typeRules, targetType);
+
+  static void addRule(universe, String targetType, rule) {
+    JS('', '#.#.# = #', universe, RtiUniverseFieldNames.typeRules, targetType,
+        rule);
   }
 
   static Object sharedEmptyArray(universe) =>
@@ -921,18 +929,139 @@
   static const int $TILDE = 0x7E;
 }
 
+/// Represents the set of supertypes and type variable bindings for a given
+/// target type. The target type itself is not stored on the [TypeRule].
+class TypeRule {
+  TypeRule._() {
+    throw UnimplementedError("TypeRule is static methods only.");
+  }
+
+  // TODO(fishythefish): Create whole rule at once rather than adding individual
+  // supertypes/type variables.
+
+  @pragma('dart2js:noInline')
+  static Object create() {
+    return JS('=Object', '{}');
+  }
+
+  static void addTypeVariable(rule, String typeVariable, String binding) {
+    JS('', '#.# = #', rule, typeVariable, binding);
+  }
+
+  static String lookupTypeVariable(rule, String typeVariable) =>
+      JS('String|Null', '#.#', rule, typeVariable);
+
+  static void addSupertype(rule, String supertype, Object supertypeArgs) {
+    JS('', '#.# = #', rule, supertype, supertypeArgs);
+  }
+
+  static JSArray lookupSupertype(rule, String supertype) =>
+      JS('JSArray|Null', '#.#', rule, supertype);
+}
+
 // -------- Subtype tests ------------------------------------------------------
 
 // Future entry point from compiled code.
-bool isSubtype(Rti s, Rti t) {
-  return _isSubtype(s, null, t, null);
+bool isSubtype(universe, Rti s, Rti t) {
+  return _isSubtype(universe, s, null, t, null);
 }
 
-bool _isSubtype(Rti s, var sEnv, Rti t, var tEnv) {
+bool _isSubtype(universe, Rti s, var sEnv, Rti t, var tEnv) {
+  // TODO(fishythefish): Update for NNBD. See
+  // https://github.com/dart-lang/language/blob/master/resources/type-system/subtyping.md#rules
+
+  // Subtyping is reflexive.
   if (_Utils.isIdentical(s, t)) return true;
-  int tKind = Rti._getKind(t);
-  if (tKind == Rti.kindDynamic) return true;
-  if (tKind == Rti.kindNever) return false;
+
+  if (isTopType(t)) return true;
+
+  if (isJsInteropType(s)) return true;
+
+  if (isTopType(s)) {
+    if (isGenericFunctionTypeParameter(t)) return false;
+    if (isFutureOrType(t)) {
+      // [t] is FutureOr<T>. Check [s] <: T.
+      var tTypeArgument = Rti._getFutureOrArgument(t);
+      return _isSubtype(universe, s, sEnv, tTypeArgument, tEnv);
+    }
+    return false;
+  }
+
+  // Generic function type parameters must match exactly, which would have
+  // exited earlier. The de Bruijn indexing ensures the representation as a
+  // small number can be used for type comparison.
+  // TODO(fishythefish): Use the bound of the type variable.
+  if (isGenericFunctionTypeParameter(s)) return false;
+  if (isGenericFunctionTypeParameter(t)) return false;
+
+  if (isNullType(s)) return true;
+
+  if (isFunctionType(t)) {
+    // TODO(fishythefish): Check if s is a function subtype of t.
+    throw UnimplementedError("isFunctionType(t)");
+  }
+
+  if (isFunctionType(s)) {
+    // TODO(fishythefish): Check if t is Function.
+    throw UnimplementedError("isFunctionType(s)");
+  }
+
+  if (isFutureOrType(t)) {
+    // [t] is FutureOr<T>.
+    var tTypeArgument = Rti._getFutureOrArgument(t);
+    if (isFutureOrType(s)) {
+      // [s] is FutureOr<S>. Check S <: T.
+      var sTypeArgument = Rti._getFutureOrArgument(s);
+      return _isSubtype(universe, sTypeArgument, sEnv, tTypeArgument, tEnv);
+    } else if (_isSubtype(universe, s, sEnv, tTypeArgument, tEnv)) {
+      // `true` because [s] <: T.
+      return true;
+    } else {
+      // TODO(fishythefish): Check [s] <: Future<T>.
+    }
+  }
+
+  assert(Rti._getKind(s) == Rti.kindInterface);
+  assert(Rti._getKind(t) == Rti.kindInterface);
+  String sName = Rti._getInterfaceName(s);
+  String tName = Rti._getInterfaceName(t);
+  // TODO(fishythefish): Handle identical names.
+
+  var rule = _Universe.findRule(universe, sName);
+  if (rule == null) return false;
+  var supertypeArgs = TypeRule.lookupSupertype(rule, tName);
+  if (supertypeArgs == null) return false;
+  var tArgs = Rti._getInterfaceTypeArguments(t);
+  int length = _Utils.arrayLength(supertypeArgs);
+  assert(length == _Utils.arrayLength(tArgs));
+  for (int i = 0; i < length; i++) {
+    String recipe = _Utils.arrayAt(supertypeArgs, i);
+    Rti supertypeArg = _Universe.evalInEnvironment(universe, s, recipe);
+    Rti tArg = _castToRti(_Utils.arrayAt(tArgs, i));
+    if (!isSubtype(universe, supertypeArg, tArg)) return false;
+  }
+
+  return true;
+}
+
+bool isTopType(Rti t) =>
+    isDynamicType(t) || isVoidType(t) || isObjectType(t) || isJsInteropType(t);
+
+bool isDynamicType(Rti t) => Rti._getKind(t) == Rti.kindDynamic;
+bool isVoidType(Rti t) => Rti._getKind(t) == Rti.kindVoid;
+bool isJsInteropType(Rti t) => Rti._getKind(t) == Rti.kindAny;
+bool isFutureOrType(Rti t) => Rti._getKind(t) == Rti.kindFutureOr;
+bool isFunctionType(Rti t) => Rti._getKind(t) == Rti.kindFunction;
+bool isGenericFunctionTypeParameter(Rti t) =>
+    Rti._getKind(t) == Rti.kindGenericFunctionParameter;
+
+bool isObjectType(Rti t) {
+  // TODO(fishythefish): Look up Object in universe and compare.
+  return false;
+}
+
+bool isNullType(Rti t) {
+  // TODO(fishythefish): Look up Null in universe and compare.
   return false;
 }
 
@@ -990,12 +1119,24 @@
   return _Universe.create();
 }
 
-Object testingAddRules(universe, String rules) {
-  _Universe.addRules(universe, rules);
+Object testingCreateRule() {
+  return TypeRule.create();
 }
 
-bool testingIsSubtype(rti1, rti2) {
-  return isSubtype(_castToRti(rti1), _castToRti(rti2));
+void testingAddTypeVariable(rule, String typeVariable, String binding) {
+  TypeRule.addTypeVariable(rule, typeVariable, binding);
+}
+
+void testingAddSupertype(rule, String supertype, Object supertypeArgs) {
+  TypeRule.addSupertype(rule, supertype, supertypeArgs);
+}
+
+void testingAddRule(universe, String targetType, rule) {
+  _Universe.addRule(universe, targetType, rule);
+}
+
+bool testingIsSubtype(universe, rti1, rti2) {
+  return isSubtype(universe, _castToRti(rti1), _castToRti(rti2));
 }
 
 Object testingUniverseEval(universe, String recipe) {
diff --git a/sdk/lib/_internal/js_runtime/lib/shared/embedded_names.dart b/sdk/lib/_internal/js_runtime/lib/shared/embedded_names.dart
index 28658e4..95231a0 100644
--- a/sdk/lib/_internal/js_runtime/lib/shared/embedded_names.dart
+++ b/sdk/lib/_internal/js_runtime/lib/shared/embedded_names.dart
@@ -420,6 +420,6 @@
 /// Names of fields of the Rti Universe object.
 class RtiUniverseFieldNames {
   static String evalCache = 'eC';
-  static String unprocessedRules = 'uR';
+  static String typeRules = 'tR';
   static String sharedEmptyArray = 'sEA';
 }
diff --git a/tests/compiler/dart2js_extra/rti/subtype_test.dart b/tests/compiler/dart2js_extra/rti/subtype_test.dart
new file mode 100644
index 0000000..91fb5b5
--- /dev/null
+++ b/tests/compiler/dart2js_extra/rti/subtype_test.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:_rti' as rti;
+import "package:expect/expect.dart";
+
+main() {
+  testCodeUnits();
+}
+
+void testCodeUnits() {
+  var universe = rti.testingCreateUniverse();
+
+  var intRule = rti.testingCreateRule();
+  rti.testingAddSupertype(intRule, 'num', []);
+
+  var listRule = rti.testingCreateRule();
+  rti.testingAddSupertype(listRule, 'Iterable', ['1']);
+
+  var codeUnitsRule = rti.testingCreateRule();
+  rti.testingAddSupertype(codeUnitsRule, 'List', ['int']);
+
+  rti.testingAddRule(universe, 'int', intRule);
+  rti.testingAddRule(universe, 'List', listRule);
+  rti.testingAddRule(universe, 'CodeUnits', codeUnitsRule);
+
+  var rti1 = rti.testingUniverseEval(universe, 'List<CodeUnits>');
+  var rti2 = rti.testingUniverseEval(universe, 'Iterable<List<int>>');
+
+  Expect.isTrue(rti.testingIsSubtype(universe, rti1, rti2));
+  Expect.isFalse(rti.testingIsSubtype(universe, rti2, rti1));
+}