@@ -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
226238DartType 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] ?.
238253class 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] *.
252268class 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('', '''(() => {
921939bool 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
948965bool _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
975987bool _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)) {
0 commit comments