blob: 47b45552c223d1bc729e568e3c72921d02badacf [file] [log] [blame] [edit]
// 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 '../common/elements.dart' show ElementEnvironment, JCommonElements;
import '../deferred_load/output_unit.dart' show OutputUnitData;
import '../elements/entities.dart';
import '../elements/types.dart';
import '../js_backend/interceptor_data.dart' show InterceptorData;
import '../js_model/js_world.dart' show JClosedWorld;
import '../universe/class_hierarchy.dart' show ClassHierarchy;
sealed class IsTestSpecialization {}
enum SimpleIsTestSpecialization implements IsTestSpecialization {
isNull,
isNotNull,
isString,
isBool,
isNum,
isInt,
isArrayTop,
}
class InstanceOfIsTestSpecialization implements IsTestSpecialization {
final InterfaceType interfaceType;
const InstanceOfIsTestSpecialization(this.interfaceType);
}
class SpecializedChecks {
static IsTestSpecialization? findIsTestSpecialization(
DartType dartType,
MemberEntity compiland,
JClosedWorld closedWorld,
) {
if (dartType is InterfaceType) {
ClassEntity element = dartType.element;
JCommonElements commonElements = closedWorld.commonElements;
if (element == commonElements.nullClass ||
element == commonElements.jsNullClass) {
return SimpleIsTestSpecialization.isNull;
}
if (element == commonElements.jsStringClass ||
element == commonElements.stringClass) {
return SimpleIsTestSpecialization.isString;
}
if (element == commonElements.jsBoolClass ||
element == commonElements.boolClass) {
return SimpleIsTestSpecialization.isBool;
}
if (element == commonElements.doubleClass ||
element == commonElements.jsNumberClass ||
element == commonElements.numClass) {
return SimpleIsTestSpecialization.isNum;
}
if (element == commonElements.jsIntClass ||
element == commonElements.intClass ||
element == commonElements.jsUInt32Class ||
element == commonElements.jsUInt31Class ||
element == commonElements.jsPositiveIntClass) {
return SimpleIsTestSpecialization.isInt;
}
DartTypes dartTypes = closedWorld.dartTypes;
// Top types should be constant folded outside the specializer. This test
// protects logic below.
if (dartTypes.isTopType(dartType)) return null;
ElementEnvironment elementEnvironment = closedWorld.elementEnvironment;
if (!dartTypes.isSubtype(
elementEnvironment.getClassInstantiationToBounds(element),
dartType,
)) {
return null;
}
if (element == commonElements.jsArrayClass) {
return SimpleIsTestSpecialization.isArrayTop;
}
if (dartType.isObject) {
assert(!dartTypes.isTopType(dartType)); // Checked above.
return SimpleIsTestSpecialization.isNotNull;
}
ClassHierarchy classHierarchy = closedWorld.classHierarchy;
InterceptorData interceptorData = closedWorld.interceptorData;
OutputUnitData outputUnitData = closedWorld.outputUnitData;
final topmost = closedWorld.getLubOfInstantiatedSubtypes(element);
// No LUB means the test is always false, and should be constant folded
// outside of this specializer.
if (topmost == null) return null;
if (classHierarchy.hasOnlySubclasses(topmost) &&
!interceptorData.isInterceptedClass(topmost) &&
outputUnitData.hasOnlyNonDeferredImportPathsToClass(
compiland,
topmost,
)) {
assert(!dartType.isObject); // Checked above.
return InstanceOfIsTestSpecialization(
elementEnvironment.getClassInstantiationToBounds(topmost),
);
}
// Two ideas for further consideration:
//
// 1. It might be profitable to know the type of the tested value - for
// example, `Pattern` and `Comparable` are both interfaces that are
// impemented by many classes and cannot be handled by any of the tricks
// above. However, if we know the tested value is a `Pattern` then `is
// Comparable` can be compiled as `is String`.
//
// 2. We could re-introduce type testing using the `$isFoo` property. The
// Rti `_is` stubs use this property, but in a polymorphic manner.
// Specialized stubs would be monomorphic in the property symbol, but they
// still need to use `getInterceptor` (although this can be specialized
// too). Using the `$isFoo` property directly in the code would be most
// beneficial when the interceptor is needed for other reasons (including
// additional checks), otherwise it is just a more verbose version of
// calling the specialized Rti stub.
}
return null;
}
static FunctionEntity? findAsCheck(
DartType dartType,
JCommonElements commonElements,
) {
if (dartType is InterfaceType) {
if (dartType.typeArguments.isNotEmpty) return null;
return _findAsCheck(dartType.element, commonElements, nullable: false);
}
if (dartType is NullableType) {
DartType baseType = dartType.baseType;
if (baseType is InterfaceType && baseType.typeArguments.isEmpty) {
return _findAsCheck(baseType.element, commonElements, nullable: true);
}
return null;
}
return null;
}
/// Finds the method that implements the specialized check for a simple type.
/// The specialized method will report a TypeError that includes a reported
/// type.
///
/// [nullable]: Find specialization for `element?`.
static FunctionEntity? _findAsCheck(
ClassEntity element,
JCommonElements commonElements, {
required bool nullable,
}) {
if (element == commonElements.jsStringClass ||
element == commonElements.stringClass) {
if (nullable) return commonElements.specializedAsStringNullable;
return commonElements.specializedAsString;
}
if (element == commonElements.jsBoolClass ||
element == commonElements.boolClass) {
if (nullable) return commonElements.specializedAsBoolNullable;
return commonElements.specializedAsBool;
}
if (element == commonElements.doubleClass) {
if (nullable) return commonElements.specializedAsDoubleNullable;
return commonElements.specializedAsDouble;
}
if (element == commonElements.jsNumberClass ||
element == commonElements.numClass) {
if (nullable) return commonElements.specializedAsNumNullable;
return commonElements.specializedAsNum;
}
if (element == commonElements.jsIntClass ||
element == commonElements.intClass ||
element == commonElements.jsUInt32Class ||
element == commonElements.jsUInt31Class ||
element == commonElements.jsPositiveIntClass) {
if (nullable) return commonElements.specializedAsIntNullable;
return commonElements.specializedAsInt;
}
if (element == commonElements.objectClass) {
if (!nullable) return commonElements.specializedAsObject;
}
if (element == commonElements.jsObjectClass) {
if (nullable) return commonElements.specializedAsJSObjectNullable;
return commonElements.specializedAsJSObject;
}
return null;
}
}