Skip to content

Commit 58ad941

Browse files
fishythefishcommit-bot@chromium.org
authored andcommitted
[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 <[email protected]> Reviewed-by: Stephen Adams <[email protected]>
1 parent 3d081f9 commit 58ad941

File tree

3 files changed

+125
-73
lines changed

3 files changed

+125
-73
lines changed

sdk/lib/_internal/js_runtime/lib/rti.dart

Lines changed: 81 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ class Rti {
334334
}
335335

336336
static Rti allocate() {
337-
return new Rti();
337+
return Rti();
338338
}
339339

340340
Object? _canonicalRecipe;
@@ -2974,86 +2974,94 @@ bool _isInterfaceSubtype(
29742974
String sName = Rti._getInterfaceName(s);
29752975
String tName = Rti._getInterfaceName(t);
29762976

2977-
// Interface Compositionality:
2978-
if (sName == tName) {
2979-
var sArgs = Rti._getInterfaceTypeArguments(s);
2980-
var tArgs = Rti._getInterfaceTypeArguments(t);
2981-
int length = _Utils.arrayLength(sArgs);
2982-
assert(length == _Utils.arrayLength(tArgs));
2983-
2984-
var sVariances;
2985-
bool? hasVariances;
2986-
if (JS_GET_FLAG("VARIANCE")) {
2987-
sVariances = _Universe.findTypeParameterVariances(universe, sName);
2988-
hasVariances = sVariances != null;
2989-
assert(!hasVariances || length == _Utils.arrayLength(sVariances));
2990-
}
2991-
2977+
while (sName != tName) {
2978+
// The Super-Interface rule says that if [s] has superinterfaces C0,...,Cn,
2979+
// then we need to check if for some i, Ci <: [t]. However, this requires us
2980+
// to iterate over the superinterfaces. Instead, we can perform case
2981+
// analysis on [t]. By this point, [t] can only be Never, a type variable,
2982+
// or an interface type. (Bindings do not participate in subtype checks and
2983+
// all other cases have been eliminated.) If [t] is not an interface, then
2984+
// [s] </: [t]. Therefore, the only remaining case is that [t] is an
2985+
// interface, so rather than iterating over the Ci, we can instead look up
2986+
// [t] in our ruleset.
2987+
// TODO(fishythefish): Handle variance correctly.
2988+
2989+
var rule = _Universe._findRule(universe, sName);
2990+
if (rule == null) return false;
2991+
if (_Utils.isString(rule)) {
2992+
sName = _Utils.asString(rule);
2993+
continue;
2994+
}
2995+
2996+
var recipes = TypeRule.lookupSupertype(rule, tName);
2997+
if (recipes == null) return false;
2998+
int length = _Utils.arrayLength(recipes);
2999+
Object? supertypeArgs = length > 0 ? JS('', 'new Array(#)', length) : null;
29923000
for (int i = 0; i < length; i++) {
2993-
Rti sArg = _Utils.asRti(_Utils.arrayAt(sArgs, i));
2994-
Rti tArg = _Utils.asRti(_Utils.arrayAt(tArgs, i));
2995-
if (JS_GET_FLAG("VARIANCE")) {
2996-
int sVariance = hasVariances != null
2997-
? _Utils.asInt(_Utils.arrayAt(sVariances, i))
2998-
: Variance.legacyCovariant;
2999-
switch (sVariance) {
3000-
case Variance.legacyCovariant:
3001-
case Variance.covariant:
3002-
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
3003-
return false;
3004-
}
3005-
break;
3006-
case Variance.contravariant:
3007-
if (!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
3008-
return false;
3009-
}
3010-
break;
3011-
case Variance.invariant:
3012-
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv) ||
3013-
!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
3014-
return false;
3015-
}
3016-
break;
3017-
default:
3018-
throw StateError(
3019-
"Unknown variance given for subtype check: $sVariance");
3020-
}
3021-
} else {
3022-
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
3023-
return false;
3024-
}
3025-
}
3001+
String recipe = _Utils.asString(_Utils.arrayAt(recipes, i));
3002+
Rti supertypeArg = _Universe.evalInEnvironment(universe, s, recipe);
3003+
_Utils.arraySetAt(supertypeArgs, i, supertypeArg);
30263004
}
3027-
return true;
3005+
var tArgs = Rti._getInterfaceTypeArguments(t);
3006+
return _areArgumentsSubtypes(
3007+
universe, supertypeArgs, null, sEnv, tArgs, tEnv);
30283008
}
30293009

3030-
// The Super-Interface rule says that if [s] has superinterfaces C0,...,Cn,
3031-
// then we need to check if for some i, Ci <: [t]. However, this requires us
3032-
// to iterate over the superinterfaces. Instead, we can perform case
3033-
// analysis on [t]. By this point, [t] can only be Never, a type variable,
3034-
// or an interface type. (Bindings do not participate in subtype checks and
3035-
// all other cases have been eliminated.) If [t] is not an interface, then
3036-
// [s] </: [t]. Therefore, the only remaining case is that [t] is an
3037-
// interface, so rather than iterating over the Ci, we can instead look up
3038-
// [t] in our ruleset.
3039-
// TODO(fishythefish): Handle variance correctly.
3040-
3041-
// We don't list Object explicitly as a supertype of each interface, so check
3042-
// this trivial case first.
3043-
if (isObjectType(t)) return true;
3044-
var rule = _Universe.findRule(universe, sName);
3045-
if (rule == null) return false;
3046-
var supertypeArgs = TypeRule.lookupSupertype(rule, tName);
3047-
if (supertypeArgs == null) return false;
3048-
int length = _Utils.arrayLength(supertypeArgs);
3010+
// Interface Compositionality:
3011+
assert(sName == tName);
3012+
var sArgs = Rti._getInterfaceTypeArguments(s);
30493013
var tArgs = Rti._getInterfaceTypeArguments(t);
3014+
var sVariances;
3015+
if (JS_GET_FLAG("VARIANCE")) {
3016+
sVariances = _Universe.findTypeParameterVariances(universe, sName);
3017+
}
3018+
return _areArgumentsSubtypes(universe, sArgs, sVariances, sEnv, tArgs, tEnv);
3019+
}
3020+
3021+
bool _areArgumentsSubtypes(Object? universe, Object? sArgs, Object? sVariances,
3022+
Object? sEnv, Object? tArgs, Object? tEnv) {
3023+
int length = sArgs != null ? _Utils.arrayLength(sArgs) : 0;
30503024
assert(length == _Utils.arrayLength(tArgs));
3025+
bool hasVariances = sVariances != null;
3026+
if (JS_GET_FLAG("VARIANCE")) {
3027+
assert(!hasVariances || length == _Utils.arrayLength(sVariances));
3028+
} else {
3029+
assert(!hasVariances);
3030+
}
3031+
30513032
for (int i = 0; i < length; i++) {
3052-
String recipe = _Utils.asString(_Utils.arrayAt(supertypeArgs, i));
3053-
Rti supertypeArg = _Universe.evalInEnvironment(universe, s, recipe);
3033+
Rti sArg = _Utils.asRti(_Utils.arrayAt(sArgs, i));
30543034
Rti tArg = _Utils.asRti(_Utils.arrayAt(tArgs, i));
3055-
if (!_isSubtype(universe, supertypeArg, sEnv, tArg, tEnv)) {
3056-
return false;
3035+
if (JS_GET_FLAG("VARIANCE")) {
3036+
int sVariance = hasVariances
3037+
? _Utils.asInt(_Utils.arrayAt(sVariances, i))
3038+
: Variance.legacyCovariant;
3039+
switch (sVariance) {
3040+
case Variance.legacyCovariant:
3041+
case Variance.covariant:
3042+
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
3043+
return false;
3044+
}
3045+
break;
3046+
case Variance.contravariant:
3047+
if (!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
3048+
return false;
3049+
}
3050+
break;
3051+
case Variance.invariant:
3052+
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv) ||
3053+
!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
3054+
return false;
3055+
}
3056+
break;
3057+
default:
3058+
throw StateError(
3059+
"Unknown variance given for subtype check: $sVariance");
3060+
}
3061+
} else {
3062+
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
3063+
return false;
3064+
}
30573065
}
30583066
}
30593067
return true;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:_interceptors';
6+
7+
import 'package:expect/expect.dart';
8+
import 'package:js/js.dart';
9+
10+
@JS()
11+
class JSClass {}
12+
13+
@JS()
14+
external void eval(String code);
15+
16+
void main() {
17+
eval(r'''
18+
function JSClass() {}
19+
''');
20+
Expect.type<JavaScriptObject>(JSClass());
21+
Expect.type<List<JavaScriptObject>>(<JSClass>[]);
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:_interceptors';
6+
7+
import 'package:expect/expect.dart';
8+
import 'package:js/js.dart';
9+
10+
@JS()
11+
class JSClass {}
12+
13+
@JS()
14+
external void eval(String code);
15+
16+
void main() {
17+
eval(r'''
18+
function JSClass() {}
19+
''');
20+
Expect.type<JavaScriptObject>(JSClass());
21+
Expect.type<List<JavaScriptObject>>(<JSClass>[]);
22+
}

0 commit comments

Comments
 (0)