mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:09:49 +00:00
[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>
This commit is contained in:
parent
2495c5ca9f
commit
199a42f3c1
|
@ -202,43 +202,60 @@ anonymousJSType(String name) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
/// Returns a nullable version of [type].
|
||||
///
|
||||
/// The resulting type will be normalized to avoid nesting the use of nullable
|
||||
/// (question) and legacy (star).
|
||||
@notNull
|
||||
DartType nullable(type) {
|
||||
// Normalize and / or wrap the type.
|
||||
// Given: T? -> T | Null
|
||||
// Then: T?? -> T? | Null -> T | Null | Null -> T?
|
||||
if (type is NullableType) return type;
|
||||
// Then T*? -> (T? | T)? -> T?? | T? -> T?
|
||||
if (type is LegacyType) return nullable(type.type);
|
||||
/// A javascript Symbol used to store a canonical version of T? on T.
|
||||
final _cachedNullable = JS('', 'Symbol("cachedNullable")');
|
||||
|
||||
return NullableType(type);
|
||||
/// A javascript Symbol used to store a canonical version of T* on T.
|
||||
final _cachedLegacy = JS('', 'Symbol("cachedLegacy")');
|
||||
|
||||
/// Returns a nullable (question, ?) version of [type].
|
||||
///
|
||||
/// The resulting type returned in a normalized form based on the rules from the
|
||||
/// normalization doc: https://github.com/dart-lang/language/pull/456
|
||||
// TODO(nshahan): Update after the normalization doc PR lands.
|
||||
@notNull
|
||||
Object nullable(type) {
|
||||
if (_isNullable(type) || _isTop(type) || _isNullType(type)) return type;
|
||||
if (type == never_) return unwrapType(Null);
|
||||
if (_isLegacy(type)) type = type.type;
|
||||
|
||||
// Check if a nullable version of this type has already been created.
|
||||
if (JS<bool>('!', '#.hasOwnProperty(#)', type, _cachedNullable)) {
|
||||
return JS<NullableType>('!', '#[#]', type, _cachedNullable);
|
||||
}
|
||||
// Cache a canonical nullable version of this type on this type.
|
||||
var cachedType = NullableType(type);
|
||||
JS('', '#[#] = #', type, _cachedNullable, cachedType);
|
||||
return cachedType;
|
||||
}
|
||||
|
||||
/// Returns a legacy version of [type].
|
||||
/// Returns a legacy (star, *) version of [type].
|
||||
///
|
||||
/// The resulting type will be normalized to avoid nesting the use of nullable
|
||||
/// (question) and legacy (star).
|
||||
/// The resulting type returned in a normalized form based on the rules from the
|
||||
/// normalization doc: https://github.com/dart-lang/language/pull/456
|
||||
// TODO(nshahan): Update after the normalization doc PR lands.
|
||||
@notNull
|
||||
DartType legacy(type) {
|
||||
// Normalize and / or wrap the type.
|
||||
// Given: T* -> T? | T
|
||||
// Then: T** -> T*? | T* -> (T? | T)? | T? | T -> T? | T -> T*
|
||||
// Then: T?* -> T?? | T? -> T?
|
||||
if (type is LegacyType || type is NullableType) return type;
|
||||
// TODO(nshahan) Maybe normailize never*, Null*.
|
||||
if (_isLegacy(type) || _isNullable(type) || _isTop(type)) return type;
|
||||
|
||||
return LegacyType(type);
|
||||
// Check if a legacy version of this type has already been created.
|
||||
if (JS<bool>('!', '#.hasOwnProperty(#)', type, _cachedLegacy)) {
|
||||
return JS<NullableType>('!', '#[#]', type, _cachedLegacy);
|
||||
}
|
||||
// Cache a canonical legacy version of this type on this type.
|
||||
var cachedType = LegacyType(type);
|
||||
JS('', '#[#] = #', type, _cachedLegacy, cachedType);
|
||||
return cachedType;
|
||||
}
|
||||
|
||||
// TODO(nshahan) Revisit this representation to support caching of the subtype
|
||||
// check results.
|
||||
/// A wrapper to identify a nullable (question, ?) type of the form [type]?.
|
||||
class NullableType extends DartType {
|
||||
final Type type;
|
||||
|
||||
NullableType(this.type);
|
||||
NullableType(this.type)
|
||||
: assert(type is! NullableType),
|
||||
assert(type is! LegacyType);
|
||||
|
||||
@override
|
||||
String get name => '$type?';
|
||||
|
@ -247,12 +264,13 @@ class NullableType extends DartType {
|
|||
String toString() => name;
|
||||
}
|
||||
|
||||
// TODO(nshahan) Revisit this representation to support caching of the subtype
|
||||
// check results.
|
||||
/// A wrapper to identify a legacy (star, *) type of the form [type]*.
|
||||
class LegacyType extends DartType {
|
||||
final Type type;
|
||||
|
||||
LegacyType(this.type);
|
||||
LegacyType(this.type)
|
||||
: assert(type is! LegacyType),
|
||||
assert(type is! NullableType);
|
||||
|
||||
@override
|
||||
String get name => '$type*';
|
||||
|
@ -921,19 +939,18 @@ _isFunctionSubtype(ft1, ft2) => JS('', '''(() => {
|
|||
bool isSubtypeOf(Object t1, Object t2) {
|
||||
// TODO(jmesserly): we've optimized `is`/`as`/implicit type checks, so they're
|
||||
// dispatched on the type. Can we optimize the subtype relation too?
|
||||
// Object map;
|
||||
// if (JS('!', '!#.hasOwnProperty(#)', t1, _subtypeCache)) {
|
||||
// JS('', '#[#] = # = new Map()', t1, _subtypeCache, map);
|
||||
// _cacheMaps.add(map);
|
||||
// } else {
|
||||
// map = JS('', '#[#]', t1, _subtypeCache);
|
||||
// bool result = JS('', '#.get(#)', map, t2);
|
||||
// if (JS('!', '# !== void 0', result)) return result;
|
||||
// }
|
||||
// TODO(nshahan) Read and write cache when it's stored on nnbd-wrapper type.
|
||||
Object map;
|
||||
if (JS('!', '!#.hasOwnProperty(#)', t1, _subtypeCache)) {
|
||||
JS('', '#[#] = # = new Map()', t1, _subtypeCache, map);
|
||||
_cacheMaps.add(map);
|
||||
} else {
|
||||
map = JS('', '#[#]', t1, _subtypeCache);
|
||||
bool result = JS('', '#.get(#)', map, t2);
|
||||
if (JS('!', '# !== void 0', result)) return result;
|
||||
}
|
||||
// TODO(nshahan): Add support for strict/weak mode.
|
||||
var result = _isSubtype(t1, t2);
|
||||
// JS('', '#.set(#, #)', map, t2, result);
|
||||
JS('', '#.set(#, #)', map, t2, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -946,30 +963,25 @@ bool _isBottom(type) => JS('!', '# == #', type, bottom);
|
|||
// TODO(nshahan): Add support for strict/weak mode.
|
||||
@notNull
|
||||
bool _isTop(type) {
|
||||
if (_isFutureOr(type)) {
|
||||
return _isTop(JS('', '#[0]', getGenericArgs(type)));
|
||||
}
|
||||
|
||||
if (_isNullable(type)) {
|
||||
if (JS('!', '# == #', type.type, Object)) {
|
||||
return true;
|
||||
}
|
||||
// TODO(nshahan): Revisit after deciding on normalization of NNBD top types.
|
||||
// TODO(nshahan): Handle Object* in a way that ensures
|
||||
// instanceOf(null, Object*) returns true.
|
||||
return _isTop(type.type);
|
||||
}
|
||||
|
||||
if (_isLegacy(type)) {
|
||||
// TODO(nshahan): Revisit after deciding on normalization of NNBD top types.
|
||||
return _isTop(type.type);
|
||||
}
|
||||
// TODO(nshahan): Handle Object* in a way that ensures
|
||||
// instanceOf(null, Object*) returns true.
|
||||
if (_isFutureOr(type)) return _isTop(JS('', '#[0]', getGenericArgs(type)));
|
||||
if (_isNullable(type)) return (JS('!', '# == #', type.type, Object));
|
||||
|
||||
return JS('!', '# == # || # == #', type, dynamic, type, void_);
|
||||
}
|
||||
|
||||
_isNullable(Type type) => JS<bool>('!', '$type instanceof $NullableType');
|
||||
_isLegacy(Type type) => JS<bool>('!', '$type instanceof $LegacyType');
|
||||
/// Returns `true` if [type] represents a nullable (question, ?) type.
|
||||
@notNull
|
||||
bool _isNullable(Type type) => JS<bool>('!', '$type instanceof $NullableType');
|
||||
|
||||
/// Returns `true` if [type] represents a legacy (star, *) type.
|
||||
@notNull
|
||||
bool _isLegacy(Type type) => JS<bool>('!', '$type instanceof $LegacyType');
|
||||
|
||||
/// Returns `true` if [type] is the [Null] type.
|
||||
@notNull
|
||||
bool _isNullType(Object type) => identical(type, unwrapType(Null));
|
||||
|
||||
@notNull
|
||||
bool _isFutureOr(type) =>
|
||||
|
@ -1364,8 +1376,6 @@ class _TypeInferrer {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _isNull(Object type) => identical(type, unwrapType(Null));
|
||||
|
||||
/// Attempts to match [subtype] as a subtype of [supertype], gathering any
|
||||
/// constraints discovered in the process.
|
||||
///
|
||||
|
@ -1398,7 +1408,7 @@ class _TypeInferrer {
|
|||
if (_isTop(supertype)) return true;
|
||||
// `Null` is a subtype match for any type `Q` under no constraints.
|
||||
// Note that nullable types will change this.
|
||||
if (_isNull(subtype)) return true;
|
||||
if (_isNullType(subtype)) return true;
|
||||
|
||||
// Handle FutureOr<T> union type.
|
||||
if (_isFutureOr(subtype)) {
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
// 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:_foreign_helper' show JS;
|
||||
// Requirements=nnbd-strong
|
||||
|
||||
import 'dart:_runtime' as dart;
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
// Requirements=nnbd-strong
|
||||
import 'runtime_utils.dart';
|
||||
|
||||
class A {}
|
||||
|
||||
|
@ -22,89 +21,6 @@ class E<T, S> {}
|
|||
|
||||
class F extends E<B, B> {}
|
||||
|
||||
// Returns sWrapped<tWrapped> as a wrapped type.
|
||||
Type generic1(Type sWrapped, Type tWrapped) {
|
||||
var s = dart.unwrapType(sWrapped);
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
var sGeneric = dart.getGenericClass(s);
|
||||
return dart.wrapType(JS('', '#(#)', sGeneric, t));
|
||||
}
|
||||
|
||||
// Returns sWrapped<tWrapped, rWrapped> as a wrapped type.
|
||||
Type generic2(Type sWrapped, Type tWrapped, Type rWrapped) {
|
||||
var s = dart.unwrapType(sWrapped);
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
var r = dart.unwrapType(rWrapped);
|
||||
var sGeneric = dart.getGenericClass(s);
|
||||
return dart.wrapType(JS('', '#(#, #)', sGeneric, t, r));
|
||||
}
|
||||
|
||||
// Returns a function type of argWrapped -> returnWrapped as a wrapped type.
|
||||
Type function1(Type returnWrapped, Type argWrapped) {
|
||||
var returnType = dart.unwrapType(returnWrapped);
|
||||
var argType = dart.unwrapType(argWrapped);
|
||||
var fun = dart.fnType(returnType, [argType]);
|
||||
return dart.wrapType(fun);
|
||||
}
|
||||
|
||||
// Returns a function type with a bounded type argument that takes no argument
|
||||
// and returns void as a wrapped type.
|
||||
Type genericFunction(Type boundWrapped) => dart.wrapType(dart.gFnType(
|
||||
(T) => [dart.VoidType, []], (T) => [dart.unwrapType(boundWrapped)]));
|
||||
|
||||
// Returns a function type with a bounded generic return type of
|
||||
// <T extends boundWrapped> argWrapped -> T as a wrapped type.
|
||||
Type functionGenericReturn(Type boundWrapped, Type argWrapped) =>
|
||||
dart.wrapType(dart.gFnType(
|
||||
(T) => [
|
||||
T,
|
||||
[dart.unwrapType(argWrapped)]
|
||||
],
|
||||
(T) => [dart.unwrapType(boundWrapped)]));
|
||||
|
||||
// Returns a function with a bounded generic argument type of
|
||||
// <T extends boundWrapped> T -> returnWrapped as a wrapped type.
|
||||
Type functionGenericArg(Type boundWrapped, Type returnWrapped) =>
|
||||
dart.wrapType(dart.gFnType(
|
||||
(T) => [
|
||||
dart.unwrapType(returnWrapped),
|
||||
[T]
|
||||
],
|
||||
(T) => [dart.unwrapType(boundWrapped)]));
|
||||
|
||||
void checkSubtype(Type sWrapped, Type tWrapped) {
|
||||
var s = dart.unwrapType(sWrapped);
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
Expect.isTrue(dart.isSubtypeOf(s, t), '$s should be subtype of $t.');
|
||||
}
|
||||
|
||||
void checkProperSubtype(Type sWrapped, Type tWrapped) {
|
||||
var s = dart.unwrapType(sWrapped);
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
Expect.isTrue(dart.isSubtypeOf(s, t), '$s should be subtype of $t.');
|
||||
checkSubtypeFailure(tWrapped, sWrapped);
|
||||
}
|
||||
|
||||
void checkSubtypeFailure(Type sWrapped, Type tWrapped) {
|
||||
var s = dart.unwrapType(sWrapped);
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
Expect.isFalse(dart.isSubtypeOf(s, t), '$s should not be subtype of $t.');
|
||||
}
|
||||
|
||||
// Returns tWrapped? as a wrapped type.
|
||||
Type nullable(Type tWrapped) {
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
var tNullable = dart.nullable(t);
|
||||
return dart.wrapType(tNullable);
|
||||
}
|
||||
|
||||
// Returns tWrapped* as a wrapped type.
|
||||
Type legacy(Type tWrapped) {
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
var tLegacy = dart.legacy(t);
|
||||
return dart.wrapType(tLegacy);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// dynamic <\: A
|
||||
checkSubtypeFailure(dynamic, A);
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2019, 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.
|
||||
|
||||
// Requirements=nnbd-strong
|
||||
|
||||
import 'dart:_runtime' as dart;
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'runtime_utils.dart';
|
||||
|
||||
class A {}
|
||||
|
||||
void main() {
|
||||
// A?? == A?
|
||||
Expect.identical(nullable(nullable(A)), nullable(A));
|
||||
// A?* == A?
|
||||
Expect.identical(legacy(nullable(A)), nullable(A));
|
||||
// A*? == A?
|
||||
Expect.identical(nullable(legacy(A)), nullable(A));
|
||||
// A** == A*
|
||||
Expect.identical(legacy(legacy(A)), legacy(A));
|
||||
// Null? == Null
|
||||
Expect.identical(nullable(Null), Null);
|
||||
// Never? == Null
|
||||
Expect.identical(nullable(dart.wrapType(dart.never_)), Null);
|
||||
// dynamic? == dynamic
|
||||
Expect.identical(nullable(dynamic), dynamic);
|
||||
// void? == void
|
||||
Expect.identical(
|
||||
nullable(dart.wrapType(dart.void_)), dart.wrapType(dart.void_));
|
||||
// dynamic* == dynamic
|
||||
Expect.identical(legacy(dynamic), dynamic);
|
||||
// void* == void
|
||||
Expect.identical(
|
||||
legacy(dart.wrapType(dart.void_)), dart.wrapType(dart.void_));
|
||||
}
|
91
tests/compiler/dartdevc_native/runtime_utils.dart
Normal file
91
tests/compiler/dartdevc_native/runtime_utils.dart
Normal file
|
@ -0,0 +1,91 @@
|
|||
// Copyright (c) 2019, 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:_foreign_helper' show JS;
|
||||
import 'dart:_runtime' as dart;
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
// Returns sWrapped<tWrapped> as a wrapped type.
|
||||
Type generic1(Type sWrapped, Type tWrapped) {
|
||||
var s = dart.unwrapType(sWrapped);
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
var sGeneric = dart.getGenericClass(s);
|
||||
return dart.wrapType(JS('', '#(#)', sGeneric, t));
|
||||
}
|
||||
|
||||
// Returns sWrapped<tWrapped, rWrapped> as a wrapped type.
|
||||
Type generic2(Type sWrapped, Type tWrapped, Type rWrapped) {
|
||||
var s = dart.unwrapType(sWrapped);
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
var r = dart.unwrapType(rWrapped);
|
||||
var sGeneric = dart.getGenericClass(s);
|
||||
return dart.wrapType(JS('', '#(#, #)', sGeneric, t, r));
|
||||
}
|
||||
|
||||
// Returns a function type of argWrapped -> returnWrapped as a wrapped type.
|
||||
Type function1(Type returnWrapped, Type argWrapped) {
|
||||
var returnType = dart.unwrapType(returnWrapped);
|
||||
var argType = dart.unwrapType(argWrapped);
|
||||
var fun = dart.fnType(returnType, [argType]);
|
||||
return dart.wrapType(fun);
|
||||
}
|
||||
|
||||
// Returns a function type with a bounded type argument that takes no argument
|
||||
// and returns void as a wrapped type.
|
||||
Type genericFunction(Type boundWrapped) => dart.wrapType(dart.gFnType(
|
||||
(T) => [dart.VoidType, []], (T) => [dart.unwrapType(boundWrapped)]));
|
||||
|
||||
// Returns a function type with a bounded generic return type of
|
||||
// <T extends boundWrapped> argWrapped -> T as a wrapped type.
|
||||
Type functionGenericReturn(Type boundWrapped, Type argWrapped) =>
|
||||
dart.wrapType(dart.gFnType(
|
||||
(T) => [
|
||||
T,
|
||||
[dart.unwrapType(argWrapped)]
|
||||
],
|
||||
(T) => [dart.unwrapType(boundWrapped)]));
|
||||
|
||||
// Returns a function with a bounded generic argument type of
|
||||
// <T extends boundWrapped> T -> returnWrapped as a wrapped type.
|
||||
Type functionGenericArg(Type boundWrapped, Type returnWrapped) =>
|
||||
dart.wrapType(dart.gFnType(
|
||||
(T) => [
|
||||
dart.unwrapType(returnWrapped),
|
||||
[T]
|
||||
],
|
||||
(T) => [dart.unwrapType(boundWrapped)]));
|
||||
|
||||
void checkSubtype(Type sWrapped, Type tWrapped) {
|
||||
var s = dart.unwrapType(sWrapped);
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
Expect.isTrue(dart.isSubtypeOf(s, t), '$s should be subtype of $t.');
|
||||
}
|
||||
|
||||
void checkProperSubtype(Type sWrapped, Type tWrapped) {
|
||||
var s = dart.unwrapType(sWrapped);
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
Expect.isTrue(dart.isSubtypeOf(s, t), '$s should be subtype of $t.');
|
||||
checkSubtypeFailure(tWrapped, sWrapped);
|
||||
}
|
||||
|
||||
void checkSubtypeFailure(Type sWrapped, Type tWrapped) {
|
||||
var s = dart.unwrapType(sWrapped);
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
Expect.isFalse(dart.isSubtypeOf(s, t), '$s should not be subtype of $t.');
|
||||
}
|
||||
|
||||
// Returns tWrapped? as a wrapped type.
|
||||
Type nullable(Type tWrapped) {
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
var tNullable = dart.nullable(t);
|
||||
return dart.wrapType(tNullable);
|
||||
}
|
||||
|
||||
// Returns tWrapped* as a wrapped type.
|
||||
Type legacy(Type tWrapped) {
|
||||
var t = dart.unwrapType(tWrapped);
|
||||
var tLegacy = dart.legacy(t);
|
||||
return dart.wrapType(tLegacy);
|
||||
}
|
|
@ -493,7 +493,9 @@
|
|||
"options": {
|
||||
"checked": true,
|
||||
"use-sdk": true,
|
||||
"enable-experiment": ["non-nullable"]
|
||||
"enable-experiment": ["non-nullable"],
|
||||
"enable-asserts": true
|
||||
|
||||
}},
|
||||
"fasta-(linux|mac|win)": { },
|
||||
"analyzer-(linux|mac|win)": {
|
||||
|
|
Loading…
Reference in a new issue