[dart2js] Fix interface subtyping bug.

Previously, when checking S <: T, we only checked if S and T had the
same interface name once. However, it's possible that redirections
eventually cause them to become equal. For example, if S is an interop
type and T is JavaScriptObject, after following the redirection from S
to JavaScriptObject, we end up checking JavaScriptObject <:
JavaScriptObject. Therefore, we need to recheck after following each
redirection.

Change-Id: Ie3eb9530627a0e48a5ea704fd4078ae238a65c78
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/204744
Commit-Queue: Mayank Patke <fishythefish@google.com>
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 96fc0a8..694bc1d 100644
--- a/sdk/lib/_internal/js_runtime/lib/rti.dart
+++ b/sdk/lib/_internal/js_runtime/lib/rti.dart
@@ -334,7 +334,7 @@
   }
 
   static Rti allocate() {
-    return new Rti();
+    return Rti();
   }
 
   Object? _canonicalRecipe;
@@ -2974,86 +2974,94 @@
   String sName = Rti._getInterfaceName(s);
   String tName = Rti._getInterfaceName(t);
 
-  // Interface Compositionality:
-  if (sName == tName) {
-    var sArgs = Rti._getInterfaceTypeArguments(s);
-    var tArgs = Rti._getInterfaceTypeArguments(t);
-    int length = _Utils.arrayLength(sArgs);
-    assert(length == _Utils.arrayLength(tArgs));
+  while (sName != tName) {
+    // The Super-Interface rule says that if [s] has superinterfaces C0,...,Cn,
+    // then we need to check if for some i, Ci <: [t]. However, this requires us
+    // to iterate over the superinterfaces. Instead, we can perform case
+    // analysis on [t]. By this point, [t] can only be Never, a type variable,
+    // or an interface type. (Bindings do not participate in subtype checks and
+    // all other cases have been eliminated.) If [t] is not an interface, then
+    // [s] </: [t]. Therefore, the only remaining case is that [t] is an
+    // interface, so rather than iterating over the Ci, we can instead look up
+    // [t] in our ruleset.
+    // TODO(fishythefish): Handle variance correctly.
 
-    var sVariances;
-    bool? hasVariances;
-    if (JS_GET_FLAG("VARIANCE")) {
-      sVariances = _Universe.findTypeParameterVariances(universe, sName);
-      hasVariances = sVariances != null;
-      assert(!hasVariances || length == _Utils.arrayLength(sVariances));
+    var rule = _Universe._findRule(universe, sName);
+    if (rule == null) return false;
+    if (_Utils.isString(rule)) {
+      sName = _Utils.asString(rule);
+      continue;
     }
 
+    var recipes = TypeRule.lookupSupertype(rule, tName);
+    if (recipes == null) return false;
+    int length = _Utils.arrayLength(recipes);
+    Object? supertypeArgs = length > 0 ? JS('', 'new Array(#)', length) : null;
     for (int i = 0; i < length; i++) {
-      Rti sArg = _Utils.asRti(_Utils.arrayAt(sArgs, i));
-      Rti tArg = _Utils.asRti(_Utils.arrayAt(tArgs, i));
-      if (JS_GET_FLAG("VARIANCE")) {
-        int sVariance = hasVariances != null
-            ? _Utils.asInt(_Utils.arrayAt(sVariances, i))
-            : Variance.legacyCovariant;
-        switch (sVariance) {
-          case Variance.legacyCovariant:
-          case Variance.covariant:
-            if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
-              return false;
-            }
-            break;
-          case Variance.contravariant:
-            if (!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
-              return false;
-            }
-            break;
-          case Variance.invariant:
-            if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv) ||
-                !_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
-              return false;
-            }
-            break;
-          default:
-            throw StateError(
-                "Unknown variance given for subtype check: $sVariance");
-        }
-      } else {
-        if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
-          return false;
-        }
-      }
+      String recipe = _Utils.asString(_Utils.arrayAt(recipes, i));
+      Rti supertypeArg = _Universe.evalInEnvironment(universe, s, recipe);
+      _Utils.arraySetAt(supertypeArgs, i, supertypeArg);
     }
-    return true;
+    var tArgs = Rti._getInterfaceTypeArguments(t);
+    return _areArgumentsSubtypes(
+        universe, supertypeArgs, null, sEnv, tArgs, tEnv);
   }
 
-  // The Super-Interface rule says that if [s] has superinterfaces C0,...,Cn,
-  // then we need to check if for some i, Ci <: [t]. However, this requires us
-  // to iterate over the superinterfaces. Instead, we can perform case
-  // analysis on [t]. By this point, [t] can only be Never, a type variable,
-  // or an interface type. (Bindings do not participate in subtype checks and
-  // all other cases have been eliminated.) If [t] is not an interface, then
-  // [s] </: [t]. Therefore, the only remaining case is that [t] is an
-  // interface, so rather than iterating over the Ci, we can instead look up
-  // [t] in our ruleset.
-  // TODO(fishythefish): Handle variance correctly.
-
-  // We don't list Object explicitly as a supertype of each interface, so check
-  // this trivial case first.
-  if (isObjectType(t)) return true;
-  var rule = _Universe.findRule(universe, sName);
-  if (rule == null) return false;
-  var supertypeArgs = TypeRule.lookupSupertype(rule, tName);
-  if (supertypeArgs == null) return false;
-  int length = _Utils.arrayLength(supertypeArgs);
+  // Interface Compositionality:
+  assert(sName == tName);
+  var sArgs = Rti._getInterfaceTypeArguments(s);
   var tArgs = Rti._getInterfaceTypeArguments(t);
+  var sVariances;
+  if (JS_GET_FLAG("VARIANCE")) {
+    sVariances = _Universe.findTypeParameterVariances(universe, sName);
+  }
+  return _areArgumentsSubtypes(universe, sArgs, sVariances, sEnv, tArgs, tEnv);
+}
+
+bool _areArgumentsSubtypes(Object? universe, Object? sArgs, Object? sVariances,
+    Object? sEnv, Object? tArgs, Object? tEnv) {
+  int length = sArgs != null ? _Utils.arrayLength(sArgs) : 0;
   assert(length == _Utils.arrayLength(tArgs));
+  bool hasVariances = sVariances != null;
+  if (JS_GET_FLAG("VARIANCE")) {
+    assert(!hasVariances || length == _Utils.arrayLength(sVariances));
+  } else {
+    assert(!hasVariances);
+  }
+
   for (int i = 0; i < length; i++) {
-    String recipe = _Utils.asString(_Utils.arrayAt(supertypeArgs, i));
-    Rti supertypeArg = _Universe.evalInEnvironment(universe, s, recipe);
+    Rti sArg = _Utils.asRti(_Utils.arrayAt(sArgs, i));
     Rti tArg = _Utils.asRti(_Utils.arrayAt(tArgs, i));
-    if (!_isSubtype(universe, supertypeArg, sEnv, tArg, tEnv)) {
-      return false;
+    if (JS_GET_FLAG("VARIANCE")) {
+      int sVariance = hasVariances
+          ? _Utils.asInt(_Utils.arrayAt(sVariances, i))
+          : Variance.legacyCovariant;
+      switch (sVariance) {
+        case Variance.legacyCovariant:
+        case Variance.covariant:
+          if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
+            return false;
+          }
+          break;
+        case Variance.contravariant:
+          if (!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
+            return false;
+          }
+          break;
+        case Variance.invariant:
+          if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv) ||
+              !_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
+            return false;
+          }
+          break;
+        default:
+          throw StateError(
+              "Unknown variance given for subtype check: $sVariance");
+      }
+    } else {
+      if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
+        return false;
+      }
     }
   }
   return true;
diff --git a/tests/web/internal/rti/js_interop_subtype_test.dart b/tests/web/internal/rti/js_interop_subtype_test.dart
new file mode 100644
index 0000000..66010da
--- /dev/null
+++ b/tests/web/internal/rti/js_interop_subtype_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, 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:_interceptors';
+
+import 'package:expect/expect.dart';
+import 'package:js/js.dart';
+
+@JS()
+class JSClass {}
+
+@JS()
+external void eval(String code);
+
+void main() {
+  eval(r'''
+      function JSClass() {}
+      ''');
+  Expect.type<JavaScriptObject>(JSClass());
+  Expect.type<List<JavaScriptObject>>(<JSClass>[]);
+}
diff --git a/tests/web_2/internal/rti/js_interop_subtype_test.dart b/tests/web_2/internal/rti/js_interop_subtype_test.dart
new file mode 100644
index 0000000..66010da
--- /dev/null
+++ b/tests/web_2/internal/rti/js_interop_subtype_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, 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:_interceptors';
+
+import 'package:expect/expect.dart';
+import 'package:js/js.dart';
+
+@JS()
+class JSClass {}
+
+@JS()
+external void eval(String code);
+
+void main() {
+  eval(r'''
+      function JSClass() {}
+      ''');
+  Expect.type<JavaScriptObject>(JSClass());
+  Expect.type<List<JavaScriptObject>>(<JSClass>[]);
+}