[ddc] let compiler generate instanceof and == of types directly.

Change-Id: I87cb00cc4592125001d50505e9023c2ab45d95c4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/138722
Commit-Queue: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
This commit is contained in:
Sigmund Cherem 2020-03-09 22:38:20 +00:00 committed by commit-bot@chromium.org
parent 617b78a014
commit db378a2f0d
5 changed files with 119 additions and 81 deletions

View file

@ -4755,21 +4755,35 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
if (target.isFactory) return _emitFactoryInvocation(node);
// Optimize some internal SDK calls.
if (isSdkInternalRuntime(target.enclosingLibrary) &&
node.arguments.positional.length == 1) {
var name = target.name.name;
var firstArg = node.arguments.positional[0];
if (name == 'getGenericClass' && firstArg is TypeLiteral) {
var type = firstArg.type;
if (type is InterfaceType) {
return _emitTopLevelNameNoInterop(type.classNode, suffix: '\$');
if (isSdkInternalRuntime(target.enclosingLibrary)) {
if (node.arguments.positional.length == 1) {
var name = target.name.name;
var firstArg = node.arguments.positional[0];
if (name == 'getGenericClass' && firstArg is TypeLiteral) {
var type = firstArg.type;
if (type is InterfaceType) {
return _emitTopLevelNameNoInterop(type.classNode, suffix: '\$');
}
}
if (name == 'unwrapType' && firstArg is TypeLiteral) {
return _emitType(firstArg.type);
}
if (name == 'extensionSymbol' && firstArg is StringLiteral) {
return getExtensionSymbolInternal(firstArg.value);
}
} else if (node.arguments.positional.length == 2) {
var name = target.name.name;
var firstArg = node.arguments.positional[0];
var secondArg = node.arguments.positional[1];
if (name == '_jsInstanceOf' && secondArg is TypeLiteral) {
return js.call('# instanceof #',
[_visitExpression(firstArg), _emitType(secondArg.type)]);
}
if (name == '_equalType' && secondArg is TypeLiteral) {
return js.call('# === #',
[_visitExpression(firstArg), _emitType(secondArg.type)]);
}
}
if (name == 'unwrapType' && firstArg is TypeLiteral) {
return _emitType(firstArg.type);
}
if (name == 'extensionSymbol' && firstArg is StringLiteral) {
return getExtensionSymbolInternal(firstArg.value);
}
}
if (target == _coreTypes.identicalProcedure) {

View file

@ -142,17 +142,17 @@ normalizeFutureOr(typeConstructor, setBaseClass) {
// FutureOr<dynamic|void|Object?|Object*> --> dynamic|void|Object?|Object*
if (_isTop(typeArg) ||
(_isLegacy(typeArg) &&
(_jsInstanceOf(typeArg, LegacyType) &&
JS<bool>('!', '#.type === #', typeArg, Object))) {
return typeArg;
}
// FutureOr<Never> --> Future<Never>
if (typeArg == never_) {
if (_equalType(typeArg, Never)) {
return JS('!', '#(#)', getGenericClass(Future), typeArg);
}
// FutureOr<Null> --> Future<Null>?
if (typeArg == unwrapType(Null)) {
if (_equalType(typeArg, Null)) {
return nullable(JS('!', '#(#)', getGenericClass(Future), typeArg));
}
// Otherwise, create the FutureOr<T> type as a normal generic type.
@ -286,7 +286,7 @@ bool isJsInterop(obj) {
// Note that it is still possible to call typed JS interop methods on
// extension types but the calls must be statically typed.
if (JS('!', '#[#] != null', obj, _extensionType)) return false;
return JS('!', '!($obj instanceof $Object)');
return !_jsInstanceOf(obj, Object);
}
/// Get the type of a method from a type using the stored signature

View file

@ -301,7 +301,7 @@ _checkAndCall(f, ftype, obj, typeArgs, args, named, displayName) =>
}
// Apply type arguments
if ($ftype instanceof $GenericFunctionType) {
if (${_jsInstanceOf(ftype, GenericFunctionType)}) {
let formalCount = $ftype.formalCount;
if ($typeArgs == null) {
@ -405,17 +405,27 @@ dindex(obj, index) => callMethod(obj, '_get', null, [index], null, '[]');
dsetindex(obj, index, value) =>
callMethod(obj, '_set', null, [index, value], null, '[]=');
/// General implementation of the Dart `is` operator.
///
/// Some basic cases are handled directly by the `.is` methods that are attached
/// directly on types, but any query that requires checking subtyping relations
/// is handled here.
@notNull
@JSExportName('is')
bool instanceOf(obj, type) {
if (obj == null) {
return identical(type, unwrapType(Null)) ||
return _equalType(type, Null) ||
_isTop(type) ||
_isNullable(type);
_jsInstanceOf(type, NullableType);
}
return isSubtypeOf(getReifiedType(obj), type);
}
/// General implementation of the Dart `as` operator.
///
/// Some basic cases are handled directly by the `.as` methods that are attached
/// directly on types, but any query that requires checking subtyping relations
/// is handled here.
@JSExportName('as')
cast(obj, type, @notNull bool isImplicit) {
// We hoist the common case where null is checked against another type here

View file

@ -88,7 +88,7 @@ getReifiedType(obj) {
switch (JS<String>('!', 'typeof #', obj)) {
case "object":
if (obj == null) return JS('', '#', Null);
if (JS('!', '# instanceof #', obj, Object)) {
if (_jsInstanceOf(obj, Object)) {
return JS('', '#.constructor', obj);
}
var result = JS('', '#[#]', obj, _extensionType);

View file

@ -294,19 +294,20 @@ Object nullable(type) {
Object _computeNullable(type) {
// *? normalizes to ?.
if (_isLegacy(type)) {
if (_jsInstanceOf(type, LegacyType)) {
return nullable(JS<Object>('!', '#.type', type));
}
if (_isNullable(type) ||
if (_jsInstanceOf(type, NullableType) ||
_isTop(type) ||
_isNullType(type) ||
_equalType(type, Null) ||
// Normalize FutureOr<T?>? --> FutureOr<T?>
// All other runtime FutureOr normalization is in `normalizeFutureOr()`.
((_isFutureOr(type)) &&
_isNullable(JS<Object>('!', '#[0]', getGenericArgs(type))))) {
_jsInstanceOf(
JS<Object>('!', '#[0]', getGenericArgs(type)), NullableType))) {
return type;
}
if (type == never_) return unwrapType(Null);
if (_equalType(type, Never)) return unwrapType(Null);
return NullableType(type);
}
@ -331,10 +332,10 @@ Object legacy(type) {
Object _computeLegacy(type) {
// Note: ?* normalizes to ?, so we cache type? at type?[_cachedLegacy].
if (_isLegacy(type) ||
_isNullable(type) ||
if (_jsInstanceOf(type, LegacyType) ||
_jsInstanceOf(type, NullableType) ||
_isTop(type) ||
_isNullType(type)) {
_equalType(type, Null)) {
return type;
}
return LegacyType(JS<Type>('!', '#', type));
@ -383,7 +384,7 @@ class LegacyType extends DartType {
if (obj == null) {
// Object and Never are the only legacy types that should return true if
// obj is `null`.
return JS<bool>('!', '# === # || # === #', type, Object, type, never_);
return _equalType(type, Object) || _equalType(type, Never);
}
return JS<bool>('!', '#.is(#)', type, obj);
}
@ -470,7 +471,7 @@ Type wrapType(type, [@notNull bool isNormalized = false]) {
}
var result = isNormalized
? _Type(type)
: (_isLegacy(type)
: (_jsInstanceOf(type, LegacyType)
? wrapType(JS<Object>('!', '#.type', type))
: _canonicalizeNormalizedTypeObject(type));
JS('', '#[#] = #', type, _typeObject, result);
@ -482,7 +483,7 @@ Type wrapType(type, [@notNull bool isNormalized = false]) {
/// Used for type object identity. Normalization requires us to return a
/// canonicalized version of the input with all legacy wrappers removed.
Type _canonicalizeNormalizedTypeObject(type) {
assert(!_isLegacy(type));
assert(!_jsInstanceOf(type, LegacyType));
// We don't call _canonicalizeNormalizedTypeObject recursively but call wrap
// + unwrap to handle legacy types automatically and force caching the
// canonicalized type under the _typeObject cache property directly. This
@ -491,10 +492,10 @@ Type _canonicalizeNormalizedTypeObject(type) {
Object normalizeHelper(a) => unwrapType(wrapType(a));
// GenericFunctionTypeIdentifiers are implicitly normalized.
if (JS<bool>('!', '# instanceof #', type, GenericFunctionTypeIdentifier)) {
if (_jsInstanceOf(type, GenericFunctionTypeIdentifier)) {
return wrapType(type, true);
}
if (JS<bool>('!', '# instanceof #', type, FunctionType)) {
if (_jsInstanceOf(type, FunctionType)) {
var normReturnType = normalizeHelper(type.returnType);
var normArgs = type.args.map(normalizeHelper).toList();
if (JS<bool>('!', '#.Object.keys(#).length === 0', global_, type.named) &&
@ -516,7 +517,7 @@ Type _canonicalizeNormalizedTypeObject(type) {
fnType(normReturnType, normArgs, normNamed, normRequiredNamed);
return wrapType(normType, true);
}
if (JS<bool>('!', '# instanceof #', type, GenericFunctionType)) {
if (_jsInstanceOf(type, GenericFunctionType)) {
var formals = _getCanonicalTypeFormals(type.typeFormals.length);
var normBounds =
type.instantiateTypeBounds(formals).map(normalizeHelper).toList();
@ -982,7 +983,7 @@ class GenericFunctionType extends AbstractFunctionType {
bool hasFreeFormal(t) {
// Ignore nullability wrappers.
if (_isLegacy(t) || _isNullable(t)) {
if (_jsInstanceOf(t, LegacyType) || _jsInstanceOf(t, NullableType)) {
return hasFreeFormal(JS<Object>('!', '#.type', t));
}
if (partials.containsKey(t)) return true;
@ -1111,7 +1112,7 @@ String typeName(type) => JS('', '''(() => {
if ($type === void 0) return "undefined type";
if ($type === null) return "null type";
// Non-instance types
if ($type instanceof $DartType) {
if (${_jsInstanceOf(type, DartType)}) {
return $type.toString();
}
@ -1263,27 +1264,39 @@ bool isSubtypeOf(Object t1, Object t2) {
final _subtypeCache = JS('', 'Symbol("_subtypeCache")');
@notNull
bool _isBottom(type, strictMode) => JS(
'!', '# === # || (!# && # === #)', type, never_, strictMode, type, bottom);
bool _isBottom(type, @notNull bool strictMode) =>
_equalType(type, Never) || (!strictMode && _equalType(type, Null));
@notNull
bool _isTop(type) {
if (_isNullable(type)) return JS('!', '#.type === #', type, Object);
if (_jsInstanceOf(type, NullableType))
return JS('!', '#.type === #', type, Object);
return JS('!', '# === # || # === #', type, dynamic, type, void_);
return _equalType(type, dynamic) || JS('!', '# === #', type, void_);
}
/// Returns `true` if [type] represents a nullable (question, ?) type.
/// Wraps the JavaScript `instanceof` operator returning `true` if [type] is an
/// instance of [cls].
///
/// This method is equivalent to:
///
/// JS<bool>('!', '# instanceof #', type, cls);
///
/// but the code is generated by the compiler directly (a low-tech way of
/// inlining).
@notNull
bool _isNullable(type) => JS<bool>('!', '# instanceof #', type, NullableType);
external bool _jsInstanceOf(type, cls);
/// Returns `true` if [type] represents a legacy (star, *) type.
/// Returns `true` if [type] is [cls].
///
/// This method is equivalent to:
///
/// JS<bool>('!', '# === #', type, unwrapType(cls));
///
/// but the code is generated by the compiler directly (a low-tech way of
/// inlining).
@notNull
bool _isLegacy(type) => JS<bool>('!', '# instanceof #', type, LegacyType);
/// Returns `true` if [type] is the [Null] type.
@notNull
bool _isNullType(type) => JS<bool>('!', '# === #', type, unwrapType(Null));
external bool _equalType(type, cls);
@notNull
bool _isFutureOr(type) {
@ -1297,10 +1310,10 @@ bool _isSubtype(t1, t2, bool strictMode) => JS<bool>('!', '''(() => {
if (!$strictMode) {
// Strip nullable types when performing check in weak mode.
// TODO(nshahan) Investigate stripping off legacy types as well.
if (${_isNullable(t1)}) {
if (${_jsInstanceOf(t1, NullableType)}) {
t1 = t1.type;
}
if (${_isNullable(t2)}) {
if (${_jsInstanceOf(t2, NullableType)}) {
t2 = t2.type;
}
}
@ -1314,12 +1327,12 @@ bool _isSubtype(t1, t2, bool strictMode) => JS<bool>('!', '''(() => {
}
// "Left Top".
if ($t1 == $dynamic || $t1 == $void_) {
if (${_equalType(t1, dynamic)} || $t1 === $void_) {
return $_isSubtype($nullable($Object), $t2, $strictMode);
}
// "Right Object".
if ($t2 == $Object) {
if (${_equalType(t2, Object)}) {
// TODO(nshahan) Need to handle type variables.
// https://github.com/dart-lang/sdk/issues/38816
if (${_isFutureOr(t1)}) {
@ -1327,11 +1340,11 @@ bool _isSubtype(t1, t2, bool strictMode) => JS<bool>('!', '''(() => {
return $_isSubtype(t1TypeArg, $Object, $strictMode);
}
if (${_isLegacy(t1)}) {
if (${_jsInstanceOf(t1, LegacyType)}) {
return $_isSubtype(t1.type, t2, $strictMode);
}
if (${_isNullType(t1)} || ${_isNullable(t1)}) {
if (${_equalType(t1, Null)} || ${_jsInstanceOf(t1, NullableType)}) {
// Checks for t1 is dynamic or void already performed in "Left Top" test.
return false;
}
@ -1339,7 +1352,7 @@ bool _isSubtype(t1, t2, bool strictMode) => JS<bool>('!', '''(() => {
}
// "Left Null".
if ($t1 == $Null) {
if (${_equalType(t1, Null)}) {
// TODO(nshahan) Need to handle type variables.
// https://github.com/dart-lang/sdk/issues/38816
if (${_isFutureOr(t2)}) {
@ -1347,16 +1360,17 @@ bool _isSubtype(t1, t2, bool strictMode) => JS<bool>('!', '''(() => {
return $_isSubtype($Null, t2TypeArg, $strictMode);
}
return $t2 == $Null || ${_isLegacy(t2)} || ${_isNullable(t2)};
return ${_equalType(t2, Null)} || ${_jsInstanceOf(t2, LegacyType)} ||
${_jsInstanceOf(t2, NullableType)};
}
// "Left Legacy".
if (${_isLegacy(t1)}) {
if (${_jsInstanceOf(t1, LegacyType)}) {
return $_isSubtype(t1.type, t2, $strictMode);
}
// "Right Legacy".
if (${_isLegacy(t2)}) {
if (${_jsInstanceOf(t2, LegacyType)}) {
return $_isSubtype(t1, $nullable(t2.type), $strictMode);
}
@ -1375,11 +1389,12 @@ bool _isSubtype(t1, t2, bool strictMode) => JS<bool>('!', '''(() => {
// (Future<A> | A) <: t2 iff Future<A> <: t2 and A <: t2.
let t1Future = ${getGenericClass(Future)}(t1TypeArg);
// Known to handle the case FutureOr<Null> <: Future<Null>.
return $_isSubtype(t1Future, $t2, $strictMode) && $_isSubtype(t1TypeArg, $t2, $strictMode);
return $_isSubtype(t1Future, $t2, $strictMode) &&
$_isSubtype(t1TypeArg, $t2, $strictMode);
}
// "Left Nullable".
if (${_isNullable(t1)}) {
if (${_jsInstanceOf(t1, NullableType)}) {
// TODO(nshahan) Need to handle type variables.
// https://github.com/dart-lang/sdk/issues/38816
return $_isSubtype(t1.type, t2, $strictMode) && $_isSubtype($Null, t2, $strictMode);
@ -1396,7 +1411,7 @@ bool _isSubtype(t1, t2, bool strictMode) => JS<bool>('!', '''(() => {
}
// "Right Nullable".
if (${_isNullable(t2)}) {
if (${_jsInstanceOf(t2, NullableType)}) {
// TODO(nshahan) Need to handle type variables.
// https://github.com/dart-lang/sdk/issues/38816
return $_isSubtype(t1, t2.type, $strictMode) || $_isSubtype(t1, $Null, $strictMode);
@ -1405,17 +1420,17 @@ bool _isSubtype(t1, t2, bool strictMode) => JS<bool>('!', '''(() => {
// "Traditional" name-based subtype check. Avoid passing
// function types to the class subtype checks, since we don't
// currently distinguish between generic typedefs and classes.
if (!($t2 instanceof $AbstractFunctionType)) {
if (!${_jsInstanceOf(t2, AbstractFunctionType)}) {
// t2 is an interface type.
if ($t1 instanceof $AbstractFunctionType) {
if (${_jsInstanceOf(t1, AbstractFunctionType)}) {
// Function types are only subtypes of interface types `Function` (and top
// types, handled already above).
return $t2 === $Function;
return ${_equalType(t2, Function)};
}
// All JS types are subtypes of anonymous JS types.
if ($t1 === $jsobject && $t2 instanceof $AnonymousJSType) {
if ($t1 === $jsobject && ${_jsInstanceOf(t2, AnonymousJSType)}) {
return true;
}
@ -1424,13 +1439,13 @@ bool _isSubtype(t1, t2, bool strictMode) => JS<bool>('!', '''(() => {
}
// Function subtyping.
if (!($t1 instanceof $AbstractFunctionType)) {
if (!${_jsInstanceOf(t1, AbstractFunctionType)}) {
return false;
}
// Handle generic functions.
if ($t1 instanceof $GenericFunctionType) {
if (!($t2 instanceof $GenericFunctionType)) {
if (${_jsInstanceOf(t1, GenericFunctionType)}) {
if (!${_jsInstanceOf(t2, GenericFunctionType)}) {
return false;
}
@ -1474,7 +1489,7 @@ bool _isSubtype(t1, t2, bool strictMode) => JS<bool>('!', '''(() => {
$t1 = $t1.instantiate(fresh);
$t2 = $t2.instantiate(fresh);
} else if ($t2 instanceof $GenericFunctionType) {
} else if (${_jsInstanceOf(t2, GenericFunctionType)}) {
return false;
}
@ -1485,24 +1500,24 @@ bool _isSubtype(t1, t2, bool strictMode) => JS<bool>('!', '''(() => {
bool _isInterfaceSubtype(t1, t2, strictMode) => JS('', '''(() => {
// If we have lazy JS types, unwrap them. This will effectively
// reduce to a prototype check below.
if ($t1 instanceof $LazyJSType) $t1 = $t1.rawJSTypeForCheck();
if ($t2 instanceof $LazyJSType) $t2 = $t2.rawJSTypeForCheck();
if (${_jsInstanceOf(t1, LazyJSType)}) $t1 = $t1.rawJSTypeForCheck();
if (${_jsInstanceOf(t2, LazyJSType)}) $t2 = $t2.rawJSTypeForCheck();
if ($t1 === $t2) {
return true;
}
if ($t1 === $Object) {
if (${_equalType(t1, Object)}) {
return false;
}
// Classes cannot subtype `Function` or vice versa.
if ($t1 === $Function || $t2 === $Function) {
if (${_equalType(t1, Function)} || ${_equalType(t2, Function)}) {
return false;
}
// If t1 is a JS Object, we may not hit core.Object.
if ($t1 == null) {
return $t2 == $Object || $t2 == $dynamic;
return ${_equalType(t2, Object)} || ${_equalType(t2, dynamic)};
}
// Check if t1 and t2 have the same raw type. If so, check covariance on
@ -1769,7 +1784,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 (_isNullType(subtype)) return true;
if (_equalType(subtype, Null)) return true;
// Handle FutureOr<T> union type.
if (_isFutureOr(subtype)) {
@ -1876,8 +1891,7 @@ class _TypeInferrer {
// TODO(paulberry): implement this case.
if (subtype is FunctionType) {
if (supertype is! FunctionType) {
if (identical(supertype, unwrapType(Function)) ||
identical(supertype, unwrapType(Object))) {
if (_equalType(supertype, Function) || _equalType(supertype, Object)) {
return true;
} else {
return false;
@ -1893,7 +1907,7 @@ class _TypeInferrer {
bool _isTop(Object type) =>
identical(type, _dynamic) ||
identical(type, void_) ||
identical(type, unwrapType(Object));
_equalType(type, Object);
}
/// A constraint on a type parameter that we're inferring.
@ -1943,7 +1957,7 @@ class TypeConstraint {
/// contain different generic type arguments.
Object? _getMatchingSupertype(Object? subtype, Object supertype) {
if (identical(subtype, supertype)) return supertype;
if (subtype == null || subtype == unwrapType(Object)) return null;
if (subtype == null || _equalType(subtype, Object)) return null;
var subclass = getGenericClass(subtype);
var superclass = getGenericClass(supertype);