Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit bbe2ac2

Browse files
nshahancommit-bot@chromium.org
authored andcommitted
[dartdevc] Add runtime support for NNBD weak subtype checks
In weak mode when a subtype check fails it is performed again with more lenient rules. If the second check passes a warning is printed to the console and it is allowed to pass. If the second check fails it is treated as a failure. - Add a method to set the runtime mode. - Update test expectations for more permissive subtype checks. Issue: #38128 Change-Id: I7d55bfffad16077c50b6b2c3bf6df8367e6f7785 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/123300 Commit-Queue: Nicholas Shahan <nshahan@google.com> Reviewed-by: Leaf Petersen <leafp@google.com>
1 parent 2f6de17 commit bbe2ac2

File tree

11 files changed

+606
-90
lines changed

11 files changed

+606
-90
lines changed

sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
/// This library defines the representation of runtime types.
88
part of dart._runtime;
99

10+
/// Sets the mode of the runtime subtype checks.
11+
///
12+
/// Changing the mode after any calls to dart.isSubtype() is not supported.
13+
void strictSubtypeChecks(bool flag) {
14+
JS('', 'dart.__strictSubtypeChecks = #', flag);
15+
}
16+
1017
final metadata = JS('', 'Symbol("metadata")');
1118

1219
/// Types in dart are represented internally at runtime as follows.
@@ -904,7 +911,7 @@ String typeName(type) => JS('', '''(() => {
904911
})()''');
905912

906913
/// Returns true if [ft1] <: [ft2].
907-
_isFunctionSubtype(ft1, ft2) => JS('', '''(() => {
914+
_isFunctionSubtype(ft1, ft2, bool strictMode) => JS('', '''(() => {
908915
let ret1 = $ft1.returnType;
909916
let ret2 = $ft2.returnType;
910917
@@ -916,7 +923,7 @@ _isFunctionSubtype(ft1, ft2) => JS('', '''(() => {
916923
}
917924
918925
for (let i = 0; i < args1.length; ++i) {
919-
if (!$_isSubtype(args2[i], args1[i])) {
926+
if (!$_isSubtype(args2[i], args1[i], strictMode)) {
920927
return false;
921928
}
922929
}
@@ -930,13 +937,13 @@ _isFunctionSubtype(ft1, ft2) => JS('', '''(() => {
930937
931938
let j = 0;
932939
for (let i = args1.length; i < args2.length; ++i, ++j) {
933-
if (!$_isSubtype(args2[i], optionals1[j])) {
940+
if (!$_isSubtype(args2[i], optionals1[j], strictMode)) {
934941
return false;
935942
}
936943
}
937944
938945
for (let i = 0; i < optionals2.length; ++i, ++j) {
939-
if (!$_isSubtype(optionals2[i], optionals1[j])) {
946+
if (!$_isSubtype(optionals2[i], optionals1[j], strictMode)) {
940947
return false;
941948
}
942949
}
@@ -966,7 +973,7 @@ _isFunctionSubtype(ft1, ft2) => JS('', '''(() => {
966973
if (n1 === void 0) {
967974
return false;
968975
}
969-
if (!$_isSubtype(n2, n1)) {
976+
if (!$_isSubtype(n2, n1, strictMode)) {
970977
return false;
971978
}
972979
}
@@ -983,7 +990,7 @@ _isFunctionSubtype(ft1, ft2) => JS('', '''(() => {
983990
}
984991
}
985992
986-
return $_isSubtype(ret1, ret2);
993+
return $_isSubtype(ret1, ret2, strictMode);
987994
})()''');
988995

989996
/// Returns true if [t1] <: [t2].
@@ -1000,17 +1007,27 @@ bool isSubtypeOf(Object t1, Object t2) {
10001007
bool result = JS('', '#.get(#)', map, t2);
10011008
if (JS('!', '# !== void 0', result)) return result;
10021009
}
1003-
// TODO(nshahan): Add support for strict/weak mode.
1004-
var result = _isSubtype(t1, t2);
1005-
JS('', '#.set(#, #)', map, t2, result);
1006-
return result;
1010+
var validSubtype = _isSubtype(t1, t2, true);
1011+
1012+
if (!validSubtype && !JS<bool>('!', 'dart.__strictSubtypeChecks')) {
1013+
validSubtype = _isSubtype(t1, t2, false);
1014+
if (validSubtype) {
1015+
// TODO(nshahan) Need more information to be helpful here.
1016+
// File and line number that caused the subtype check?
1017+
// Possibly break into debuger?
1018+
_warn("$t1 is not a subtype of $t2.\n"
1019+
"This will be a runtime failure when strict mode is enabled.");
1020+
}
1021+
}
1022+
JS('', '#.set(#, #)', map, t2, validSubtype);
1023+
return validSubtype;
10071024
}
10081025

10091026
final _subtypeCache = JS('', 'Symbol("_subtypeCache")');
10101027

1011-
// TODO(nshahan): Add support for strict/weak mode.
10121028
@notNull
1013-
bool _isBottom(type) => JS('!', '# == #', type, bottom);
1029+
bool _isBottom(type, strictMode) =>
1030+
JS('!', '# == # || (!# && #)', type, bottom, strictMode, _isNullType(type));
10141031

10151032
// TODO(nshahan): Add support for strict/weak mode.
10161033
@notNull
@@ -1039,19 +1056,29 @@ bool _isNullType(Object type) => identical(type, unwrapType(Null));
10391056
bool _isFutureOr(type) =>
10401057
identical(getGenericClass(type), getGenericClass(FutureOr));
10411058

1042-
bool _isSubtype(t1, t2) => JS('bool', '''(() => {
1059+
bool _isSubtype(t1, t2, bool strictMode) => JS('bool', '''(() => {
1060+
if (!$strictMode) {
1061+
// Strip nullable types when performing check in weak mode.
1062+
// TODO(nshahan) Investigate stripping off legacy types as well.
1063+
if (${_isNullable(t1)}) {
1064+
t1 = t1.type;
1065+
}
1066+
if (${_isNullable(t2)}) {
1067+
t2 = t2.type;
1068+
}
1069+
}
10431070
if ($t1 === $t2) {
10441071
return true;
10451072
}
10461073
10471074
// Trivially true, "Right Top" or "Left Bottom".
1048-
if (${_isTop(t2)} || ${_isBottom(t1)}) {
1075+
if (${_isTop(t2)} || ${_isBottom(t1, strictMode)}) {
10491076
return true;
10501077
}
10511078
10521079
// "Left Top".
10531080
if ($t1 == $dynamic || $t1 == $void_) {
1054-
return $_isSubtype($nullable($Object), $t2);
1081+
return $_isSubtype($nullable($Object), $t2, $strictMode);
10551082
}
10561083
10571084
// "Right Object".
@@ -1060,15 +1087,15 @@ bool _isSubtype(t1, t2) => JS('bool', '''(() => {
10601087
// https://github.com/dart-lang/sdk/issues/38816
10611088
if (${_isFutureOr(t1)}) {
10621089
let t1TypeArg = ${getGenericArgs(t1)}[0];
1063-
return $_isSubtype(t1TypeArg, $Object);
1090+
return $_isSubtype(t1TypeArg, $Object, $strictMode);
10641091
}
10651092
10661093
if (${_isLegacy(t1)}) {
1067-
return $_isSubtype(t1.type, t2);
1094+
return $_isSubtype(t1.type, t2, $strictMode);
10681095
}
10691096
1070-
if ($t1 == $dynamic || $t1 == $void_ || $t1 == $Null
1071-
|| ${_isNullable(t1)}) {
1097+
if (${_isNullType(t1)} || ${_isNullable(t1)}) {
1098+
// Checks for t1 is dynamic or void already performed in "Left Top" test.
10721099
return false;
10731100
}
10741101
return true;
@@ -1080,20 +1107,20 @@ bool _isSubtype(t1, t2) => JS('bool', '''(() => {
10801107
// https://github.com/dart-lang/sdk/issues/38816
10811108
if (${_isFutureOr(t2)}) {
10821109
let t2TypeArg = ${getGenericArgs(t2)}[0];
1083-
return $_isSubtype($Null, t2TypeArg);
1110+
return $_isSubtype($Null, t2TypeArg, $strictMode);
10841111
}
10851112
10861113
return $t2 == $Null || ${_isLegacy(t2)} || ${_isNullable(t2)};
10871114
}
10881115
10891116
// "Left Legacy".
10901117
if (${_isLegacy(t1)}) {
1091-
return $_isSubtype(t1.type, t2);
1118+
return $_isSubtype(t1.type, t2, $strictMode);
10921119
}
10931120
10941121
// "Right Legacy".
10951122
if (${_isLegacy(t2)}) {
1096-
return $_isSubtype(t1, $nullable(t2.type));
1123+
return $_isSubtype(t1, $nullable(t2.type), $strictMode);
10971124
}
10981125
10991126
// Handle FutureOr<T> union type.
@@ -1104,21 +1131,21 @@ bool _isSubtype(t1, t2) => JS('bool', '''(() => {
11041131
// FutureOr<A> <: FutureOr<B> iff A <: B
11051132
// TODO(nshahan): Proven to not actually be true and needs cleanup.
11061133
// https://github.com/dart-lang/sdk/issues/38818
1107-
return $_isSubtype(t1TypeArg, t2TypeArg);
1134+
return $_isSubtype(t1TypeArg, t2TypeArg, $strictMode);
11081135
}
11091136
11101137
// given t1 is Future<A> | A, then:
11111138
// (Future<A> | A) <: t2 iff Future<A> <: t2 and A <: t2.
11121139
let t1Future = ${getGenericClass(Future)}(t1TypeArg);
11131140
// Known to handle the case FutureOr<Null> <: Future<Null>.
1114-
return $_isSubtype(t1Future, $t2) && $_isSubtype(t1TypeArg, $t2);
1141+
return $_isSubtype(t1Future, $t2, $strictMode) && $_isSubtype(t1TypeArg, $t2, $strictMode);
11151142
}
11161143
11171144
// "Left Nullable".
11181145
if (${_isNullable(t1)}) {
11191146
// TODO(nshahan) Need to handle type variables.
11201147
// https://github.com/dart-lang/sdk/issues/38816
1121-
return $_isSubtype(t1.type, t2) && $_isSubtype($Null, t2);
1148+
return $_isSubtype(t1.type, t2, $strictMode) && $_isSubtype($Null, t2, $strictMode);
11221149
}
11231150
11241151
if ($_isFutureOr($t2)) {
@@ -1128,14 +1155,14 @@ bool _isSubtype(t1, t2) => JS('bool', '''(() => {
11281155
let t2Future = ${getGenericClass(Future)}(t2TypeArg);
11291156
// TODO(nshahan) Need to handle type variables on the left.
11301157
// https://github.com/dart-lang/sdk/issues/38816
1131-
return $_isSubtype($t1, t2Future) || $_isSubtype($t1, t2TypeArg);
1158+
return $_isSubtype($t1, t2Future, $strictMode) || $_isSubtype($t1, t2TypeArg, $strictMode);
11321159
}
11331160
11341161
// "Right Nullable".
11351162
if (${_isNullable(t2)}) {
11361163
// TODO(nshahan) Need to handle type variables.
11371164
// https://github.com/dart-lang/sdk/issues/38816
1138-
return $_isSubtype(t1, t2.type) || $_isSubtype(t1, $Null);
1165+
return $_isSubtype(t1, t2.type, $strictMode) || $_isSubtype(t1, $Null, $strictMode);
11391166
}
11401167
11411168
// "Traditional" name-based subtype check. Avoid passing
@@ -1156,7 +1183,7 @@ bool _isSubtype(t1, t2) => JS('bool', '''(() => {
11561183
}
11571184
11581185
// Compare two interface types.
1159-
return ${_isInterfaceSubtype(t1, t2)};
1186+
return ${_isInterfaceSubtype(t1, t2, strictMode)};
11601187
}
11611188
11621189
// Function subtyping.
@@ -1214,10 +1241,10 @@ bool _isSubtype(t1, t2) => JS('bool', '''(() => {
12141241
}
12151242
12161243
// Handle non-generic functions.
1217-
return ${_isFunctionSubtype(t1, t2)};
1244+
return ${_isFunctionSubtype(t1, t2, strictMode)};
12181245
})()''');
12191246

1220-
bool _isInterfaceSubtype(t1, t2) => JS('', '''(() => {
1247+
bool _isInterfaceSubtype(t1, t2, strictMode) => JS('', '''(() => {
12211248
// If we have lazy JS types, unwrap them. This will effectively
12221249
// reduce to a prototype check below.
12231250
if ($t1 instanceof $LazyJSType) $t1 = $t1.rawJSTypeForCheck();
@@ -1255,38 +1282,38 @@ bool _isInterfaceSubtype(t1, t2) => JS('', '''(() => {
12551282
// When using implicit variance, variances will be undefined and
12561283
// considered covariant.
12571284
if (variances === void 0 || variances[i] == ${Variance.covariant}) {
1258-
if (!$_isSubtype(typeArguments1[i], typeArguments2[i])) {
1285+
if (!$_isSubtype(typeArguments1[i], typeArguments2[i], $strictMode)) {
12591286
return false;
12601287
}
12611288
} else if (variances[i] == ${Variance.contravariant}) {
1262-
if (!$_isSubtype(typeArguments2[i], typeArguments1[i])) {
1289+
if (!$_isSubtype(typeArguments2[i], typeArguments1[i], $strictMode)) {
12631290
return false;
12641291
}
12651292
} else if (variances[i] == ${Variance.invariant}) {
1266-
if (!$_isSubtype(typeArguments1[i], typeArguments2[i]) ||
1267-
!$_isSubtype(typeArguments2[i], typeArguments1[i])) {
1293+
if (!$_isSubtype(typeArguments1[i], typeArguments2[i], $strictMode) ||
1294+
!$_isSubtype(typeArguments2[i], typeArguments1[i], $strictMode)) {
12681295
return false;
12691296
}
12701297
}
12711298
}
12721299
return true;
12731300
}
12741301
1275-
if ($_isInterfaceSubtype(t1.__proto__, $t2)) {
1302+
if ($_isInterfaceSubtype(t1.__proto__, $t2, $strictMode)) {
12761303
return true;
12771304
}
12781305
12791306
// Check mixin.
12801307
let m1 = $getMixin($t1);
1281-
if (m1 != null && $_isInterfaceSubtype(m1, $t2)) {
1308+
if (m1 != null && $_isInterfaceSubtype(m1, $t2, $strictMode)) {
12821309
return true;
12831310
}
12841311
12851312
// Check interfaces.
12861313
let getInterfaces = $getImplements($t1);
12871314
if (getInterfaces) {
12881315
for (let i1 of getInterfaces()) {
1289-
if ($_isInterfaceSubtype(i1, $t2)) {
1316+
if ($_isInterfaceSubtype(i1, $t2, $strictMode)) {
12901317
return true;
12911318
}
12921319
}

tests/compiler/dartdevc/modular/nnbd_strong_subtype/main.dart

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
// Requirements=nnbd-strong
88

9-
import 'dart:_runtime' as dart;
109
import 'dart:async';
1110

1211
import 'runtime_utils.dart';
@@ -25,6 +24,9 @@ class E<T, S> {}
2524
class F extends E<B, B> {}
2625

2726
void main() {
27+
// Run tests with strict subtype checks.
28+
strictSubtypeChecks(true);
29+
2830
// Top type symmetry.
2931
// Object? <: dynamic
3032
checkSubtype(nullable(Object), dynamic);
@@ -111,6 +113,14 @@ void main() {
111113
checkProperSubtype(Object, generic1(FutureOr, nullable(Object)));
112114
// Object? <: FutureOr<Object?>
113115
checkSubtype(nullable(Object), generic1(FutureOr, nullable(Object)));
116+
// Object <: FutureOr<Object>
117+
checkSubtype(Object, generic1(FutureOr, Object));
118+
// FutureOr<Object> <: Object
119+
checkSubtype(generic1(FutureOr, Object), Object);
120+
// Object <: FutureOr<dynamic>
121+
checkProperSubtype(Object, generic1(FutureOr, dynamic));
122+
// Object <: FutureOr<void>
123+
checkProperSubtype(Object, generic1(FutureOr, voidType));
114124
// Future<Object> <: FutureOr<Object?>
115125
checkProperSubtype(
116126
generic1(Future, Object), generic1(FutureOr, nullable(Object)));
@@ -257,28 +267,34 @@ void main() {
257267
checkSubtypeFailure(nullable(A), legacy(B));
258268

259269
// Allowed in weak mode.
260-
// dynamic <: Object
270+
// dynamic <\: Object
261271
checkSubtypeFailure(dynamic, Object);
262-
// void <: Object
272+
// void <\: Object
263273
checkSubtypeFailure(voidType, Object);
264-
// Object? <: Object
274+
// Object? <\: Object
265275
checkSubtypeFailure(nullable(Object), Object);
266-
// A? <: Object
276+
// A? <\: Object
267277
checkSubtypeFailure(nullable(A), Object);
268-
// A? <: A
278+
// A? <\: A
269279
checkSubtypeFailure(nullable(A), A);
270-
// Null <: never
280+
// Null <\: never
271281
checkSubtypeFailure(Null, neverType);
272-
// Null <: Object
282+
// Null <\: Object
273283
checkSubtypeFailure(Null, Object);
274-
// Null <: A
284+
// Null <\: A
275285
checkSubtypeFailure(Null, A);
276-
// Null <: FutureOr<A>
286+
// Null <\: FutureOr<A>
277287
checkSubtypeFailure(Null, generic1(FutureOr, A));
278-
// Null <: Future<A>
288+
// Null <\: Future<A>
279289
checkSubtypeFailure(Null, generic1(Future, A));
280-
// FutureOr<Null> <: Future<Null>
290+
// FutureOr<Null> <\: Future<Null>
281291
checkSubtypeFailure(generic1(FutureOr, Null), generic1(Future, Null));
282-
// Null <: Future<A?>
292+
// Null <\: Future<A?>
283293
checkSubtypeFailure(Null, generic1(Future, nullable(A)));
294+
// FutureOr<Object?> <\: Object
295+
checkSubtypeFailure(generic1(FutureOr, nullable(Object)), Object);
296+
// FutureOr<dynamic> <\: Object
297+
checkSubtypeFailure(generic1(FutureOr, dynamic), Object);
298+
// FutureOr<void> <\: Object
299+
checkSubtypeFailure(generic1(FutureOr, voidType), Object);
284300
}

tests/compiler/dartdevc/modular/nnbd_strong_subtype/runtime_utils_nnbd.dart

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,23 @@
66

77
import 'dart:_runtime' as dart;
88

9-
// The runtime representation of the never type.
9+
/// The runtime representation of the never type.
1010
final neverType = dart.wrapType(dart.never_);
1111

12-
// Returns tWrapped? as a wrapped type.
12+
/// Sets the mode of the runtime subtype checks.
13+
///
14+
/// In tests the mode should be set only once at the very beginning of the test.
15+
/// Changing the mode after any calls to dart.isSubtype() is not supported.
16+
void strictSubtypeChecks(bool flag) => dart.strictSubtypeChecks(flag);
17+
18+
/// Returns tWrapped? as a wrapped type.
1319
Type nullable(Type tWrapped) {
1420
var t = dart.unwrapType(tWrapped);
1521
var tNullable = dart.nullable(t);
1622
return dart.wrapType(tNullable);
1723
}
1824

19-
// Returns tWrapped* as a wrapped type.
25+
/// Returns tWrapped* as a wrapped type.
2026
Type legacy(Type tWrapped) {
2127
var t = dart.unwrapType(tWrapped);
2228
var tLegacy = dart.legacy(t);

0 commit comments

Comments
 (0)