Skip to content

Commit 199a42f

Browse files
nshahancommit-bot@chromium.org
authored andcommitted
[dartdevc] Cache the results of subtype checks
- Save the canonical instance of a nullable or legacy types on the canonical instance of the non-nullable type. - Add additional normalization when creating nullable and legacy types. Issue: #38109 Change-Id: I1132917965db3b00f87b891a0800da46e2cc6b3d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/122061 Commit-Queue: Nicholas Shahan <nshahan@google.com> Reviewed-by: Leaf Petersen <leafp@google.com>
1 parent 2495c5c commit 199a42f

File tree

5 files changed

+206
-149
lines changed

5 files changed

+206
-149
lines changed

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

Lines changed: 71 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -202,43 +202,60 @@ anonymousJSType(String name) {
202202
return ret;
203203
}
204204

205-
/// Returns a nullable version of [type].
205+
/// A javascript Symbol used to store a canonical version of T? on T.
206+
final _cachedNullable = JS('', 'Symbol("cachedNullable")');
207+
208+
/// A javascript Symbol used to store a canonical version of T* on T.
209+
final _cachedLegacy = JS('', 'Symbol("cachedLegacy")');
210+
211+
/// Returns a nullable (question, ?) version of [type].
206212
///
207-
/// The resulting type will be normalized to avoid nesting the use of nullable
208-
/// (question) and legacy (star).
213+
/// The resulting type returned in a normalized form based on the rules from the
214+
/// normalization doc: https://github.com/dart-lang/language/pull/456
215+
// TODO(nshahan): Update after the normalization doc PR lands.
209216
@notNull
210-
DartType nullable(type) {
211-
// Normalize and / or wrap the type.
212-
// Given: T? -> T | Null
213-
// Then: T?? -> T? | Null -> T | Null | Null -> T?
214-
if (type is NullableType) return type;
215-
// Then T*? -> (T? | T)? -> T?? | T? -> T?
216-
if (type is LegacyType) return nullable(type.type);
217-
218-
return NullableType(type);
217+
Object nullable(type) {
218+
if (_isNullable(type) || _isTop(type) || _isNullType(type)) return type;
219+
if (type == never_) return unwrapType(Null);
220+
if (_isLegacy(type)) type = type.type;
221+
222+
// Check if a nullable version of this type has already been created.
223+
if (JS<bool>('!', '#.hasOwnProperty(#)', type, _cachedNullable)) {
224+
return JS<NullableType>('!', '#[#]', type, _cachedNullable);
225+
}
226+
// Cache a canonical nullable version of this type on this type.
227+
var cachedType = NullableType(type);
228+
JS('', '#[#] = #', type, _cachedNullable, cachedType);
229+
return cachedType;
219230
}
220231

221-
/// Returns a legacy version of [type].
232+
/// Returns a legacy (star, *) version of [type].
222233
///
223-
/// The resulting type will be normalized to avoid nesting the use of nullable
224-
/// (question) and legacy (star).
234+
/// The resulting type returned in a normalized form based on the rules from the
235+
/// normalization doc: https://github.com/dart-lang/language/pull/456
236+
// TODO(nshahan): Update after the normalization doc PR lands.
225237
@notNull
226238
DartType legacy(type) {
227-
// Normalize and / or wrap the type.
228-
// Given: T* -> T? | T
229-
// Then: T** -> T*? | T* -> (T? | T)? | T? | T -> T? | T -> T*
230-
// Then: T?* -> T?? | T? -> T?
231-
if (type is LegacyType || type is NullableType) return type;
239+
// TODO(nshahan) Maybe normailize never*, Null*.
240+
if (_isLegacy(type) || _isNullable(type) || _isTop(type)) return type;
232241

233-
return LegacyType(type);
242+
// Check if a legacy version of this type has already been created.
243+
if (JS<bool>('!', '#.hasOwnProperty(#)', type, _cachedLegacy)) {
244+
return JS<NullableType>('!', '#[#]', type, _cachedLegacy);
245+
}
246+
// Cache a canonical legacy version of this type on this type.
247+
var cachedType = LegacyType(type);
248+
JS('', '#[#] = #', type, _cachedLegacy, cachedType);
249+
return cachedType;
234250
}
235251

236-
// TODO(nshahan) Revisit this representation to support caching of the subtype
237-
// check results.
252+
/// A wrapper to identify a nullable (question, ?) type of the form [type]?.
238253
class NullableType extends DartType {
239254
final Type type;
240255

241-
NullableType(this.type);
256+
NullableType(this.type)
257+
: assert(type is! NullableType),
258+
assert(type is! LegacyType);
242259

243260
@override
244261
String get name => '$type?';
@@ -247,12 +264,13 @@ class NullableType extends DartType {
247264
String toString() => name;
248265
}
249266

250-
// TODO(nshahan) Revisit this representation to support caching of the subtype
251-
// check results.
267+
/// A wrapper to identify a legacy (star, *) type of the form [type]*.
252268
class LegacyType extends DartType {
253269
final Type type;
254270

255-
LegacyType(this.type);
271+
LegacyType(this.type)
272+
: assert(type is! LegacyType),
273+
assert(type is! NullableType);
256274

257275
@override
258276
String get name => '$type*';
@@ -921,19 +939,18 @@ _isFunctionSubtype(ft1, ft2) => JS('', '''(() => {
921939
bool isSubtypeOf(Object t1, Object t2) {
922940
// TODO(jmesserly): we've optimized `is`/`as`/implicit type checks, so they're
923941
// dispatched on the type. Can we optimize the subtype relation too?
924-
// Object map;
925-
// if (JS('!', '!#.hasOwnProperty(#)', t1, _subtypeCache)) {
926-
// JS('', '#[#] = # = new Map()', t1, _subtypeCache, map);
927-
// _cacheMaps.add(map);
928-
// } else {
929-
// map = JS('', '#[#]', t1, _subtypeCache);
930-
// bool result = JS('', '#.get(#)', map, t2);
931-
// if (JS('!', '# !== void 0', result)) return result;
932-
// }
933-
// TODO(nshahan) Read and write cache when it's stored on nnbd-wrapper type.
942+
Object map;
943+
if (JS('!', '!#.hasOwnProperty(#)', t1, _subtypeCache)) {
944+
JS('', '#[#] = # = new Map()', t1, _subtypeCache, map);
945+
_cacheMaps.add(map);
946+
} else {
947+
map = JS('', '#[#]', t1, _subtypeCache);
948+
bool result = JS('', '#.get(#)', map, t2);
949+
if (JS('!', '# !== void 0', result)) return result;
950+
}
934951
// TODO(nshahan): Add support for strict/weak mode.
935952
var result = _isSubtype(t1, t2);
936-
// JS('', '#.set(#, #)', map, t2, result);
953+
JS('', '#.set(#, #)', map, t2, result);
937954
return result;
938955
}
939956

@@ -946,30 +963,25 @@ bool _isBottom(type) => JS('!', '# == #', type, bottom);
946963
// TODO(nshahan): Add support for strict/weak mode.
947964
@notNull
948965
bool _isTop(type) {
949-
if (_isFutureOr(type)) {
950-
return _isTop(JS('', '#[0]', getGenericArgs(type)));
951-
}
952-
953-
if (_isNullable(type)) {
954-
if (JS('!', '# == #', type.type, Object)) {
955-
return true;
956-
}
957-
// TODO(nshahan): Revisit after deciding on normalization of NNBD top types.
958-
// TODO(nshahan): Handle Object* in a way that ensures
959-
// instanceOf(null, Object*) returns true.
960-
return _isTop(type.type);
961-
}
962-
963-
if (_isLegacy(type)) {
964-
// TODO(nshahan): Revisit after deciding on normalization of NNBD top types.
965-
return _isTop(type.type);
966-
}
966+
// TODO(nshahan): Handle Object* in a way that ensures
967+
// instanceOf(null, Object*) returns true.
968+
if (_isFutureOr(type)) return _isTop(JS('', '#[0]', getGenericArgs(type)));
969+
if (_isNullable(type)) return (JS('!', '# == #', type.type, Object));
967970

968971
return JS('!', '# == # || # == #', type, dynamic, type, void_);
969972
}
970973

971-
_isNullable(Type type) => JS<bool>('!', '$type instanceof $NullableType');
972-
_isLegacy(Type type) => JS<bool>('!', '$type instanceof $LegacyType');
974+
/// Returns `true` if [type] represents a nullable (question, ?) type.
975+
@notNull
976+
bool _isNullable(Type type) => JS<bool>('!', '$type instanceof $NullableType');
977+
978+
/// Returns `true` if [type] represents a legacy (star, *) type.
979+
@notNull
980+
bool _isLegacy(Type type) => JS<bool>('!', '$type instanceof $LegacyType');
981+
982+
/// Returns `true` if [type] is the [Null] type.
983+
@notNull
984+
bool _isNullType(Object type) => identical(type, unwrapType(Null));
973985

974986
@notNull
975987
bool _isFutureOr(type) =>
@@ -1364,8 +1376,6 @@ class _TypeInferrer {
13641376
return true;
13651377
}
13661378

1367-
bool _isNull(Object type) => identical(type, unwrapType(Null));
1368-
13691379
/// Attempts to match [subtype] as a subtype of [supertype], gathering any
13701380
/// constraints discovered in the process.
13711381
///
@@ -1398,7 +1408,7 @@ class _TypeInferrer {
13981408
if (_isTop(supertype)) return true;
13991409
// `Null` is a subtype match for any type `Q` under no constraints.
14001410
// Note that nullable types will change this.
1401-
if (_isNull(subtype)) return true;
1411+
if (_isNullType(subtype)) return true;
14021412

14031413
// Handle FutureOr<T> union type.
14041414
if (_isFutureOr(subtype)) {

tests/compiler/dartdevc_native/nnbd_subtype_test.dart

Lines changed: 3 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:_foreign_helper' show JS;
5+
// Requirements=nnbd-strong
6+
67
import 'dart:_runtime' as dart;
78
import 'dart:async';
89

9-
import 'package:expect/expect.dart';
10-
11-
// Requirements=nnbd-strong
10+
import 'runtime_utils.dart';
1211

1312
class A {}
1413

@@ -22,89 +21,6 @@ class E<T, S> {}
2221

2322
class F extends E<B, B> {}
2423

25-
// Returns sWrapped<tWrapped> as a wrapped type.
26-
Type generic1(Type sWrapped, Type tWrapped) {
27-
var s = dart.unwrapType(sWrapped);
28-
var t = dart.unwrapType(tWrapped);
29-
var sGeneric = dart.getGenericClass(s);
30-
return dart.wrapType(JS('', '#(#)', sGeneric, t));
31-
}
32-
33-
// Returns sWrapped<tWrapped, rWrapped> as a wrapped type.
34-
Type generic2(Type sWrapped, Type tWrapped, Type rWrapped) {
35-
var s = dart.unwrapType(sWrapped);
36-
var t = dart.unwrapType(tWrapped);
37-
var r = dart.unwrapType(rWrapped);
38-
var sGeneric = dart.getGenericClass(s);
39-
return dart.wrapType(JS('', '#(#, #)', sGeneric, t, r));
40-
}
41-
42-
// Returns a function type of argWrapped -> returnWrapped as a wrapped type.
43-
Type function1(Type returnWrapped, Type argWrapped) {
44-
var returnType = dart.unwrapType(returnWrapped);
45-
var argType = dart.unwrapType(argWrapped);
46-
var fun = dart.fnType(returnType, [argType]);
47-
return dart.wrapType(fun);
48-
}
49-
50-
// Returns a function type with a bounded type argument that takes no argument
51-
// and returns void as a wrapped type.
52-
Type genericFunction(Type boundWrapped) => dart.wrapType(dart.gFnType(
53-
(T) => [dart.VoidType, []], (T) => [dart.unwrapType(boundWrapped)]));
54-
55-
// Returns a function type with a bounded generic return type of
56-
// <T extends boundWrapped> argWrapped -> T as a wrapped type.
57-
Type functionGenericReturn(Type boundWrapped, Type argWrapped) =>
58-
dart.wrapType(dart.gFnType(
59-
(T) => [
60-
T,
61-
[dart.unwrapType(argWrapped)]
62-
],
63-
(T) => [dart.unwrapType(boundWrapped)]));
64-
65-
// Returns a function with a bounded generic argument type of
66-
// <T extends boundWrapped> T -> returnWrapped as a wrapped type.
67-
Type functionGenericArg(Type boundWrapped, Type returnWrapped) =>
68-
dart.wrapType(dart.gFnType(
69-
(T) => [
70-
dart.unwrapType(returnWrapped),
71-
[T]
72-
],
73-
(T) => [dart.unwrapType(boundWrapped)]));
74-
75-
void checkSubtype(Type sWrapped, Type tWrapped) {
76-
var s = dart.unwrapType(sWrapped);
77-
var t = dart.unwrapType(tWrapped);
78-
Expect.isTrue(dart.isSubtypeOf(s, t), '$s should be subtype of $t.');
79-
}
80-
81-
void checkProperSubtype(Type sWrapped, Type tWrapped) {
82-
var s = dart.unwrapType(sWrapped);
83-
var t = dart.unwrapType(tWrapped);
84-
Expect.isTrue(dart.isSubtypeOf(s, t), '$s should be subtype of $t.');
85-
checkSubtypeFailure(tWrapped, sWrapped);
86-
}
87-
88-
void checkSubtypeFailure(Type sWrapped, Type tWrapped) {
89-
var s = dart.unwrapType(sWrapped);
90-
var t = dart.unwrapType(tWrapped);
91-
Expect.isFalse(dart.isSubtypeOf(s, t), '$s should not be subtype of $t.');
92-
}
93-
94-
// Returns tWrapped? as a wrapped type.
95-
Type nullable(Type tWrapped) {
96-
var t = dart.unwrapType(tWrapped);
97-
var tNullable = dart.nullable(t);
98-
return dart.wrapType(tNullable);
99-
}
100-
101-
// Returns tWrapped* as a wrapped type.
102-
Type legacy(Type tWrapped) {
103-
var t = dart.unwrapType(tWrapped);
104-
var tLegacy = dart.legacy(t);
105-
return dart.wrapType(tLegacy);
106-
}
107-
10824
void main() {
10925
// dynamic <\: A
11026
checkSubtypeFailure(dynamic, A);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) 2019, 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+
// Requirements=nnbd-strong
6+
7+
import 'dart:_runtime' as dart;
8+
9+
import 'package:expect/expect.dart';
10+
11+
import 'runtime_utils.dart';
12+
13+
class A {}
14+
15+
void main() {
16+
// A?? == A?
17+
Expect.identical(nullable(nullable(A)), nullable(A));
18+
// A?* == A?
19+
Expect.identical(legacy(nullable(A)), nullable(A));
20+
// A*? == A?
21+
Expect.identical(nullable(legacy(A)), nullable(A));
22+
// A** == A*
23+
Expect.identical(legacy(legacy(A)), legacy(A));
24+
// Null? == Null
25+
Expect.identical(nullable(Null), Null);
26+
// Never? == Null
27+
Expect.identical(nullable(dart.wrapType(dart.never_)), Null);
28+
// dynamic? == dynamic
29+
Expect.identical(nullable(dynamic), dynamic);
30+
// void? == void
31+
Expect.identical(
32+
nullable(dart.wrapType(dart.void_)), dart.wrapType(dart.void_));
33+
// dynamic* == dynamic
34+
Expect.identical(legacy(dynamic), dynamic);
35+
// void* == void
36+
Expect.identical(
37+
legacy(dart.wrapType(dart.void_)), dart.wrapType(dart.void_));
38+
}

0 commit comments

Comments
 (0)