[dart2js] Improve type inference for createSentinel.

Treating createSentinel as returning a value of type Never caused issues
whenever its result flowed into a union, since the union would just
simplify to its other component. This meant that if a late field started
out uninitialized and was later initialized to a constant, we would see
it as only ever having the constant value. This caused isSentinel to
always return false even if the field hadn't been initialized yet.

Instead, we support a late sentinel value in the abstract value domain
and use this value as the return type of createSentinel during
inference. Additionally, inference treats _lateReadCheck as a simple
narrowing to exclude the late sentinel value.

Change-Id: I086bfd77576930e3ca2a4cfc9bd63476b6636685
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/210646
Reviewed-by: Stephen Adams <sra@google.com>
This commit is contained in:
Mayank Patke
2021-09-13 21:59:20 +00:00
committed by commit-bot@chromium.org
parent 93164ae2a6
commit f5b8cc2c76
27 changed files with 684 additions and 213 deletions
+37 -10
View File
@@ -531,6 +531,22 @@ abstract class CommonElements {
/// Most foreign helpers are located in the `dart:_foreign_helper` library.
bool isForeignHelper(MemberEntity member);
/// Returns `true` if [member] is the `createJsSentinel` function defined in
/// dart:_foreign_helper.
bool isCreateJsSentinel(MemberEntity member);
/// Returns `true` if [member] is the `isJsSentinel` function defined in
/// dart:_foreign_helper.
bool isIsJsSentinel(MemberEntity member);
/// Returns `true` if [member] is the `_lateReadCheck` function defined in
/// dart:_internal.
bool isLateReadCheck(MemberEntity member);
/// Returns `true` if [member] is the `createSentinel` function defined in
/// dart:_internal.
bool isCreateSentinel(MemberEntity member);
ClassEntity getDefaultSuperclass(
ClassEntity cls, NativeBasicData nativeBasicData);
@@ -644,10 +660,6 @@ abstract class JCommonElements implements CommonElements {
bool isForeign(MemberEntity element);
/// Returns `true` if [member] is the `createSentinel` function defined in
/// dart:_internal.
bool isCreateSentinel(MemberEntity element);
/// Returns `true` if the implementation of the 'operator ==' [function] is
/// known to handle `null` as argument.
bool operatorEqHandlesNullArgument(FunctionEntity function);
@@ -2117,13 +2129,28 @@ class CommonElementsImpl
isCreateInvocationMirrorHelper(member);
}
bool _isTopLevelFunctionNamed(String name, MemberEntity member) =>
member.name == name && member.isFunction && member.isTopLevel;
@override
bool isCreateSentinel(MemberEntity member) {
return member.isTopLevel &&
member.isFunction &&
member.library == internalLibrary &&
member.name == 'createSentinel';
}
bool isCreateJsSentinel(MemberEntity member) =>
member.library == foreignLibrary &&
_isTopLevelFunctionNamed('createJsSentinel', member);
@override
bool isIsJsSentinel(MemberEntity member) =>
member.library == foreignLibrary &&
_isTopLevelFunctionNamed('isJsSentinel', member);
@override
bool isLateReadCheck(MemberEntity member) =>
member.library == lateHelperLibrary &&
_isTopLevelFunctionNamed('_lateReadCheck', member);
@override
bool isCreateSentinel(MemberEntity member) =>
member.library == internalLibrary &&
_isTopLevelFunctionNamed('createSentinel', member);
@override
bool operatorEqHandlesNullArgument(FunctionEntity function) {
+1 -1
View File
@@ -912,7 +912,7 @@ class LateSentinelConstantValue extends ConstantValue {
}
@override
DartType getType(CommonElements types) => types.dynamicType;
DartType getType(CommonElements types) => types.dartTypes.neverType();
@override
ConstantValueKind get kind => ConstantValueKind.LATE_SENTINEL;
@@ -104,7 +104,11 @@ class AbstractValueWithPrecision {
/// A system that implements an abstraction over runtime values.
abstract class AbstractValueDomain {
/// The [AbstractValue] that represents an unknown runtime value.
/// The [AbstractValue] that represents an unknown runtime value. This
/// includes values internal to the implementation, such as late sentinels.
AbstractValue get internalTopType;
/// The [AbstractValue] that represents an unknown runtime Dart value.
AbstractValue get dynamicType;
/// The [AbstractValue] that represents a non-null subtype of `Type` at
@@ -153,6 +157,9 @@ abstract class AbstractValueDomain {
/// The [AbstractValue] that represents the `null` at runtime.
AbstractValue get nullType;
/// The [AbstractValue] that represents a late sentinel value at runtime.
AbstractValue get lateSentinelType;
/// The [AbstractValue] that represents a non-null growable JavaScript array
/// at runtime.
AbstractValue get growableListType;
@@ -273,6 +280,14 @@ abstract class AbstractValueDomain {
/// Returns the version of the abstract [value] that includes `null`.
AbstractValue includeNull(covariant AbstractValue value);
/// Returns the version of the abstract [value] that excludes the late
/// sentinel.
AbstractValue excludeLateSentinel(covariant AbstractValue value);
/// Returns the version of the abstract [value] that includes the late
/// sentinel.
AbstractValue includeLateSentinel(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] contains
/// instances of [cls] at runtime.
AbstractBool containsType(covariant AbstractValue value, ClassEntity cls);
@@ -307,6 +322,10 @@ abstract class AbstractValueDomain {
/// runtime.
AbstractBool isNull(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a sentinel for
/// an uninitialized late variable at runtime.
AbstractBool isLateSentinel(covariant AbstractValue value);
/// Returns an [AbstractBool] that describes whether [value] is a JavaScript
/// bool, number, string, array or `null` at runtime.
AbstractBool isPrimitive(covariant AbstractValue value);
@@ -1509,6 +1509,10 @@ class KernelTypeGraphBuilder extends ir.Visitor<TypeInformation>
return _inferrer.typeOfNativeBehavior(nativeBehavior);
} else if (name == Identifiers.JS_STRING_CONCAT) {
return _types.stringType;
} else if (_closedWorld.commonElements.isCreateJsSentinel(function)) {
return _types.lateSentinelType;
} else if (_closedWorld.commonElements.isIsJsSentinel(function)) {
return _types.boolType;
} else {
_sideEffectsBuilder.setAllSideEffects();
return _types.dynamicType;
@@ -1522,29 +1526,18 @@ class KernelTypeGraphBuilder extends ir.Visitor<TypeInformation>
Selector selector = _elementMap.getSelector(node);
if (_closedWorld.commonElements.isForeign(member)) {
return handleForeignInvoke(node, member, arguments, selector);
} else if (!_options.useLegacySubtyping &&
_closedWorld.commonElements.isCreateSentinel(member)) {
// TODO(fishythefish): Support this for --no-sound-null-safety too.
// `T createSentinel<T>()` ostensibly returns a `T` based on its static
// type. However, we need to handle this specially for a couple of
// reasons:
// 1. We do not currently handle type arguments during type inference and
// in the abstract value domain. Without additional tracing, this means
// that we lose all call-site sensitivity and `createSentinel` is seen
// as returning `Object?`, which widens the inferred types of late
// fields, resulting in poor codegen.
// 2. The sentinel isn't a real Dart value and doesn't really inhabit any
// Dart type. Nevertheless, we must view it as inhabiting every Dart
// type for the signature of `createSentinel` to make sense, making it
// a bottom value (similar to an expression of type `Never`). This
// matches the expectation that reading an uninitialized late field
// (that is, one initialized with the sentinel value) throws.
// Note that this currently breaks if `--experiment-unreachable-throw` is
// used. We'll be able to do something more precise here when more of the
// lowering is deferred to SSA and the abstract value domain can better
// track sentinel values.
} else if (_closedWorld.commonElements.isLateReadCheck(member)) {
// `_lateReadCheck` is essentially a narrowing to exclude the sentinel
// value. In order to avoid poor inference resulting from a large
// fan-in/fan-out, we perform the narrowing directly instead of creating a
// [TypeInformation] for this member.
handleStaticInvoke(node, selector, member, arguments);
return _types.nonNullEmptyType;
return _types.narrowType(arguments.positional[0],
_elementMap.getDartType(node.arguments.types.single),
excludeLateSentinel: true);
} else if (_closedWorld.commonElements.isCreateSentinel(member)) {
handleStaticInvoke(node, selector, member, arguments);
return _types.lateSentinelType;
} else if (member.isConstructor) {
return handleConstructorInvoke(
node, node.arguments, selector, member, arguments);
@@ -2384,8 +2377,8 @@ class LocalState {
{isCast: true,
excludeNull: false}) {
assert(type != null);
type = inferrer.types
.narrowType(type, staticType, isCast: isCast, excludeNull: excludeNull);
type = inferrer.types.narrowType(type, staticType,
isCast: isCast, excludeNull: excludeNull, excludeLateSentinel: true);
FieldEntity field = capturedAndBoxed[local];
if (field != null) {
@@ -203,6 +203,8 @@ class PowersetBitsDomain {
// TODO(coam): We could be more precise if we implement a visitor to
// ConstantValue
// TODO(fishythefish): Naively calling `getType` on
// [LateSentinelConstantValue] will produce Never.
return createFromStaticType(value.getType(commonElements), nullable: false);
}
@@ -298,6 +300,9 @@ class PowersetBitsDomain {
? AbstractBool.True
: (isPotentiallyNull(value) ? AbstractBool.Maybe : AbstractBool.False);
// TODO(fishythefish): Support tracking late sentinels in the powerset domain.
AbstractBool isLateSentinel(int value) => AbstractBool.Maybe;
AbstractBool isExact(int value) => AbstractBool.Maybe;
AbstractBool isEmpty(int value) {
@@ -329,6 +334,12 @@ class PowersetBitsDomain {
return value & ~nullValue;
}
// TODO(fishythefish): Support tracking late sentinels in the powerset domain.
int includeLateSentinel(int value) => value;
// TODO(fishythefish): Support tracking late sentinels in the powerset domain.
int excludeLateSentinel(int value) => value;
AbstractBool couldBeTypedArray(int value) => isOther(value);
AbstractBool isTypedArray(int value) => AbstractBool.Maybe;
@@ -551,6 +562,8 @@ class PowersetBitsDomain {
return finish(dynamicType, false);
}
int get internalTopType => powersetTop;
int get dynamicType => powersetTop;
int get asyncStarStreamType => powersetTop;
@@ -587,6 +600,9 @@ class PowersetBitsDomain {
int get nonNullType => powersetTop & ~nullValue;
// TODO(fishythefish): Support tracking late sentinels in the powerset domain.
int get lateSentinelType => powersetBottom;
int _mapType;
int get mapType =>
_mapType ??= createNonNullSubtype(commonElements.mapLiteralClass);
@@ -63,10 +63,13 @@ class PowersetDomain implements AbstractValueDomain {
PowersetBitsDomain get powersetBitsDomain => _powersetBitsDomain;
@override
AbstractValue get dynamicType {
AbstractValue abstractValue = _abstractValueDomain.dynamicType;
return PowersetValue(abstractValue, _powersetBitsDomain.powersetTop);
}
AbstractValue get internalTopType => PowersetValue(
_abstractValueDomain.internalTopType,
_powersetBitsDomain.internalTopType);
@override
AbstractValue get dynamicType => PowersetValue(
_abstractValueDomain.dynamicType, _powersetBitsDomain.dynamicType);
//TODO(coam)
@override
@@ -550,6 +553,12 @@ class PowersetDomain implements AbstractValueDomain {
_powersetBitsDomain.isNull(value._powersetBits),
_abstractValueDomain.isNull(value._abstractValue));
@override
AbstractBool isLateSentinel(covariant PowersetValue value) =>
AbstractBool.strengthen(
_powersetBitsDomain.isLateSentinel(value._powersetBits),
_abstractValueDomain.isLateSentinel(value._abstractValue));
@override
ClassEntity getExactClass(covariant PowersetValue value) =>
_abstractValueDomain.getExactClass(value._abstractValue);
@@ -606,6 +615,24 @@ class PowersetDomain implements AbstractValueDomain {
return PowersetValue(abstractValue, powersetBits);
}
@override
AbstractValue includeLateSentinel(covariant PowersetValue value) {
int powersetBits =
_powersetBitsDomain.includeLateSentinel(value._powersetBits);
AbstractValue abstractValue =
_abstractValueDomain.includeLateSentinel(value._abstractValue);
return PowersetValue(abstractValue, powersetBits);
}
@override
AbstractValue excludeLateSentinel(covariant PowersetValue value) {
int powersetBits =
_powersetBitsDomain.excludeLateSentinel(value._powersetBits);
AbstractValue abstractValue =
_abstractValueDomain.excludeLateSentinel(value._abstractValue);
return PowersetValue(abstractValue, powersetBits);
}
@override
AbstractBool couldBeTypedArray(covariant PowersetValue value) =>
AbstractBool.strengthen(
@@ -734,6 +761,11 @@ class PowersetDomain implements AbstractValueDomain {
AbstractValue get nonNullType => PowersetValue(
_abstractValueDomain.nonNullType, _powersetBitsDomain.nonNullType);
@override
AbstractValue get lateSentinelType => PowersetValue(
_abstractValueDomain.lateSentinelType,
_powersetBitsDomain.lateSentinelType);
@override
AbstractValue get mapType =>
PowersetValue(_abstractValueDomain.mapType, _powersetBitsDomain.mapType);
@@ -48,6 +48,10 @@ class WrappedAbstractValueDomain implements AbstractValueDomain {
final AbstractValueDomain _abstractValueDomain;
const WrappedAbstractValueDomain(this._abstractValueDomain);
@override
AbstractValue get internalTopType =>
WrappedAbstractValue(_abstractValueDomain.internalTopType);
@override
AbstractValue get dynamicType =>
WrappedAbstractValue(_abstractValueDomain.dynamicType);
@@ -398,6 +402,10 @@ class WrappedAbstractValueDomain implements AbstractValueDomain {
AbstractBool isNull(covariant WrappedAbstractValue value) =>
_abstractValueDomain.isNull(value._abstractValue);
@override
AbstractBool isLateSentinel(covariant WrappedAbstractValue value) =>
_abstractValueDomain.isLateSentinel(value._abstractValue);
@override
ClassEntity getExactClass(covariant WrappedAbstractValue value) =>
_abstractValueDomain.getExactClass(value._abstractValue);
@@ -440,6 +448,16 @@ class WrappedAbstractValueDomain implements AbstractValueDomain {
WrappedAbstractValue(
_abstractValueDomain.excludeNull(value._abstractValue));
@override
AbstractValue includeLateSentinel(covariant WrappedAbstractValue value) =>
WrappedAbstractValue(
_abstractValueDomain.includeLateSentinel(value._abstractValue));
@override
AbstractValue excludeLateSentinel(covariant WrappedAbstractValue value) =>
WrappedAbstractValue(
_abstractValueDomain.excludeLateSentinel(value._abstractValue));
@override
AbstractBool couldBeTypedArray(covariant WrappedAbstractValue value) =>
_abstractValueDomain.couldBeTypedArray(value._abstractValue);
@@ -537,6 +555,10 @@ class WrappedAbstractValueDomain implements AbstractValueDomain {
AbstractValue get nonNullType =>
WrappedAbstractValue(_abstractValueDomain.nonNullType);
@override
AbstractValue get lateSentinelType =>
WrappedAbstractValue(_abstractValueDomain.lateSentinelType);
@override
AbstractValue get mapType =>
WrappedAbstractValue(_abstractValueDomain.mapType);
@@ -24,6 +24,9 @@ class TrivialAbstractValue implements AbstractValue {
class TrivialAbstractValueDomain implements AbstractValueDomain {
const TrivialAbstractValueDomain();
@override
AbstractValue get internalTopType => const TrivialAbstractValue();
@override
AbstractValue get dynamicType => const TrivialAbstractValue();
@@ -286,6 +289,9 @@ class TrivialAbstractValueDomain implements AbstractValueDomain {
@override
AbstractBool isNull(AbstractValue value) => AbstractBool.Maybe;
@override
AbstractBool isLateSentinel(AbstractValue value) => AbstractBool.Maybe;
@override
ClassEntity getExactClass(AbstractValue value) => null;
@@ -319,6 +325,14 @@ class TrivialAbstractValueDomain implements AbstractValueDomain {
AbstractValue excludeNull(AbstractValue value) =>
const TrivialAbstractValue();
@override
AbstractValue includeLateSentinel(AbstractValue value) =>
const TrivialAbstractValue();
@override
AbstractValue excludeLateSentinel(AbstractValue value) =>
const TrivialAbstractValue();
@override
AbstractBool couldBeTypedArray(AbstractValue value) => AbstractBool.Maybe;
@@ -398,6 +412,9 @@ class TrivialAbstractValueDomain implements AbstractValueDomain {
@override
AbstractValue get nonNullType => const TrivialAbstractValue();
@override
AbstractValue get lateSentinelType => const TrivialAbstractValue();
@override
AbstractValue get mapType => const TrivialAbstractValue();
@@ -466,6 +466,18 @@ abstract class MemberTypeInformation extends ElementTypeInformation
inferrer.closedWorld.nativeData.getNativeMethodBehavior(function))
.type;
}
if (inferrer.commonElements.isIsJsSentinel(function)) {
giveUp(inferrer);
return inferrer.abstractValueDomain.boolType;
}
if (inferrer.commonElements.isCreateSentinel(function) ||
inferrer.commonElements.isCreateJsSentinel(function)) {
giveUp(inferrer);
return inferrer.abstractValueDomain.lateSentinelType;
}
return null;
}
@@ -2276,9 +2288,11 @@ AbstractValue _narrowType(
if (isNullable) {
otherType = abstractValueDomain.includeNull(otherType);
}
return type == null
? otherType
: abstractValueDomain.intersection(type, otherType);
if (type == null) return otherType;
AbstractValue newType = abstractValueDomain.intersection(type, otherType);
return abstractValueDomain.isLateSentinel(type).isPotentiallyTrue
? abstractValueDomain.includeLateSentinel(newType)
: newType;
}
// TODO(joshualitt): FutureOrType, TypeVariableType, and FunctionTypeVariable
+28 -9
View File
@@ -278,6 +278,10 @@ class TypeSystem {
getConcreteTypeFor(_abstractValueDomain.asyncStarStreamType);
}
TypeInformation _lateSentinelType;
TypeInformation get lateSentinelType => _lateSentinelType ??=
getConcreteTypeFor(_abstractValueDomain.lateSentinelType);
TypeInformation nonNullEmptyType;
TypeInformation stringLiteralType(String value) {
@@ -337,8 +341,13 @@ class TypeSystem {
///
/// If [excludeNull] is true, the intersection excludes `null` even if the
/// Dart type implies `null`.
///
/// [narrowType] will not exclude the late sentinel value by default, only if
/// [excludeLateSentinel] is `true`.
TypeInformation narrowType(TypeInformation type, DartType annotation,
{bool isCast: true, bool excludeNull: false}) {
{bool isCast: true,
bool excludeNull: false,
bool excludeLateSentinel: false}) {
// Avoid refining an input with an exact type. It we are almost always
// adding a narrowing to a subtype of the same class or a superclass.
if (_abstractValueDomain.isExact(type.type).isDefinitelyTrue) return type;
@@ -351,6 +360,9 @@ class TypeSystem {
if (excludeNull) {
abstractValue = _abstractValueDomain.excludeNull(abstractValue);
}
if (!excludeLateSentinel) {
abstractValue = _abstractValueDomain.includeLateSentinel(abstractValue);
}
if (_abstractValueDomain.containsAll(abstractValue).isPotentiallyTrue) {
// Top, or non-nullable Top.
@@ -643,25 +655,29 @@ class TypeSystem {
}
AbstractValue joinTypeMasks(Iterable<AbstractValue> masks) {
var dynamicType = _abstractValueDomain.dynamicType;
var topType = _abstractValueDomain.internalTopType;
// Optimization: we are iterating over masks twice, but because `masks` is a
// mapped iterable, we save the intermediate results to avoid computing them
// again.
var list = [];
bool isDynamicIgnoringNull = false;
bool isTopIgnoringFlags = false;
bool mayBeNull = false;
bool mayBeLateSentinel = false;
for (AbstractValue mask in masks) {
// Don't do any work on computing unions if we know that after all that
// work the result will be `dynamic`.
// TODO(sigmund): change to `mask == dynamicType` so we can continue to
// track the non-nullable bit.
// TODO(sigmund): change to `mask == internalTopType` so we can continue
// to track the non-nullable and late sentinel bits.
if (_abstractValueDomain.containsAll(mask).isPotentiallyTrue) {
isDynamicIgnoringNull = true;
isTopIgnoringFlags = true;
}
if (_abstractValueDomain.isNull(mask).isPotentiallyTrue) {
mayBeNull = true;
}
if (isDynamicIgnoringNull && mayBeNull) return dynamicType;
if (_abstractValueDomain.isLateSentinel(mask).isPotentiallyTrue) {
mayBeLateSentinel = true;
}
if (isTopIgnoringFlags && mayBeNull && mayBeLateSentinel) return topType;
list.add(mask);
}
@@ -671,12 +687,15 @@ class TypeSystem {
newType == null ? mask : _abstractValueDomain.union(newType, mask);
// Likewise - stop early if we already reach dynamic.
if (_abstractValueDomain.containsAll(newType).isPotentiallyTrue) {
isDynamicIgnoringNull = true;
isTopIgnoringFlags = true;
}
if (_abstractValueDomain.isNull(newType).isPotentiallyTrue) {
mayBeNull = true;
}
if (isDynamicIgnoringNull && mayBeNull) return dynamicType;
if (_abstractValueDomain.isLateSentinel(newType).isPotentiallyTrue) {
mayBeLateSentinel = true;
}
if (isTopIgnoringFlags && mayBeNull && mayBeLateSentinel) return topType;
}
return newType ?? _abstractValueDomain.emptyType;
@@ -57,7 +57,7 @@ class ConstantValueTypeMasks
@override
TypeMask visitLateSentinel(
LateSentinelConstantValue constant, JClosedWorld closedWorld) =>
_abstractValueDomain.dynamicType;
_abstractValueDomain.lateSentinelType;
@override
TypeMask visitUnreachable(
@@ -58,13 +58,20 @@ class ContainerTypeMask extends AllocationTypeMask {
}
@override
ContainerTypeMask withFlags({bool isNullable}) {
ContainerTypeMask withFlags({bool isNullable, bool hasLateSentinel}) {
isNullable ??= this.isNullable;
if (isNullable == this.isNullable) {
hasLateSentinel ??= this.hasLateSentinel;
if (isNullable == this.isNullable &&
hasLateSentinel == this.hasLateSentinel) {
return this;
}
return ContainerTypeMask(forwardTo.withFlags(isNullable: isNullable),
allocationNode, allocationElement, elementType, length);
return ContainerTypeMask(
forwardTo.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel),
allocationNode,
allocationElement,
elementType,
length);
}
@override
@@ -74,8 +81,9 @@ class ContainerTypeMask extends AllocationTypeMask {
@override
TypeMask _unionSpecialCases(TypeMask other, CommonMasks domain,
{bool isNullable}) {
{bool isNullable, bool hasLateSentinel}) {
assert(isNullable != null);
assert(hasLateSentinel != null);
if (other is ContainerTypeMask &&
elementType != null &&
other.elementType != null) {
@@ -60,13 +60,21 @@ class DictionaryTypeMask extends MapTypeMask {
}
@override
DictionaryTypeMask withFlags({bool isNullable}) {
DictionaryTypeMask withFlags({bool isNullable, bool hasLateSentinel}) {
isNullable ??= this.isNullable;
if (isNullable == this.isNullable) {
hasLateSentinel ??= this.hasLateSentinel;
if (isNullable == this.isNullable &&
hasLateSentinel == this.hasLateSentinel) {
return this;
}
return DictionaryTypeMask(forwardTo.withFlags(isNullable: isNullable),
allocationNode, allocationElement, keyType, valueType, _typeMap);
return DictionaryTypeMask(
forwardTo.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel),
allocationNode,
allocationElement,
keyType,
valueType,
_typeMap);
}
@override
@@ -80,8 +88,9 @@ class DictionaryTypeMask extends MapTypeMask {
@override
TypeMask _unionSpecialCases(TypeMask other, CommonMasks domain,
{bool isNullable}) {
{bool isNullable, bool hasLateSentinel}) {
assert(isNullable != null);
assert(hasLateSentinel != null);
if (other is DictionaryTypeMask) {
TypeMask newForwardTo = forwardTo.union(other.forwardTo, domain);
TypeMask newKeyType = keyType.union(other.keyType, domain);
@@ -14,18 +14,22 @@ class FlatTypeMask extends TypeMask {
static const String tag = 'flat-type-mask';
static const int _NULL_INDEX = 0;
static const int _USED_INDICES = 1;
static const int _LATE_SENTINEL_INDEX = 1;
static const int _USED_INDICES = 2;
static const int _NONE_MASK = 0;
static const int _NULL_MASK = 1 << _NULL_INDEX;
static const int _LATE_SENTINEL_MASK = 1 << _LATE_SENTINEL_INDEX;
static const int _ALL_MASK = (1 << _USED_INDICES) - 1;
final ClassEntity base;
final int flags;
static int _computeFlags(_FlatTypeMaskKind kind, {bool hasNull: false}) {
static int _computeFlags(_FlatTypeMaskKind kind,
{bool isNullable: false, bool hasLateSentinel: false}) {
int mask = _NONE_MASK;
if (hasNull) mask |= _NULL_MASK;
if (isNullable) mask |= _NULL_MASK;
if (hasLateSentinel) mask |= _LATE_SENTINEL_MASK;
return _computeFlagsRaw(kind.index, mask);
}
@@ -35,36 +39,56 @@ class FlatTypeMask extends TypeMask {
static _FlatTypeMaskKind _lookupKind(int flags) =>
_FlatTypeMaskKind.values[flags >> _USED_INDICES];
static bool _hasNullFlag(int flags) => flags & _NULL_MASK != _NONE_MASK;
static bool _hasNullableFlag(int flags) => flags & _NULL_MASK != _NONE_MASK;
factory FlatTypeMask.exact(ClassEntity base, JClosedWorld world) =>
static bool _hasLateSentinelFlag(int flags) =>
flags & _LATE_SENTINEL_MASK != _NONE_MASK;
factory FlatTypeMask.exact(ClassEntity base, JClosedWorld world,
{bool hasLateSentinel: false}) =>
FlatTypeMask._canonicalize(base, _FlatTypeMaskKind.exact, world,
isNullable: true);
factory FlatTypeMask.subclass(ClassEntity base, JClosedWorld world) =>
isNullable: true, hasLateSentinel: hasLateSentinel);
factory FlatTypeMask.subclass(ClassEntity base, JClosedWorld world,
{bool hasLateSentinel: false}) =>
FlatTypeMask._canonicalize(base, _FlatTypeMaskKind.subclass, world,
isNullable: true);
factory FlatTypeMask.subtype(ClassEntity base, JClosedWorld world) =>
isNullable: true, hasLateSentinel: hasLateSentinel);
factory FlatTypeMask.subtype(ClassEntity base, JClosedWorld world,
{bool hasLateSentinel: false}) =>
FlatTypeMask._canonicalize(base, _FlatTypeMaskKind.subtype, world,
isNullable: true);
isNullable: true, hasLateSentinel: hasLateSentinel);
factory FlatTypeMask.nonNullEmpty() => const FlatTypeMask._(null, _NONE_MASK);
factory FlatTypeMask.nonNullEmpty({bool hasLateSentinel: false}) =>
hasLateSentinel
? const FlatTypeMask._(null, _LATE_SENTINEL_MASK)
: const FlatTypeMask._(null, _NONE_MASK);
factory FlatTypeMask.empty() => const FlatTypeMask._(null, _NULL_MASK);
factory FlatTypeMask.empty({bool hasLateSentinel: false}) => hasLateSentinel
? const FlatTypeMask._(null, _NULL_MASK | _LATE_SENTINEL_MASK)
: const FlatTypeMask._(null, _NULL_MASK);
factory FlatTypeMask.nonNullExact(ClassEntity base, JClosedWorld world) =>
FlatTypeMask._canonicalize(base, _FlatTypeMaskKind.exact, world);
factory FlatTypeMask.nonNullSubclass(ClassEntity base, JClosedWorld world) =>
FlatTypeMask._canonicalize(base, _FlatTypeMaskKind.subclass, world);
factory FlatTypeMask.nonNullSubtype(ClassEntity base, JClosedWorld world) =>
FlatTypeMask._canonicalize(base, _FlatTypeMaskKind.subtype, world);
factory FlatTypeMask.nonNullExact(ClassEntity base, JClosedWorld world,
{bool hasLateSentinel: false}) =>
FlatTypeMask._canonicalize(base, _FlatTypeMaskKind.exact, world,
hasLateSentinel: hasLateSentinel);
factory FlatTypeMask.nonNullSubclass(ClassEntity base, JClosedWorld world,
{bool hasLateSentinel: false}) =>
FlatTypeMask._canonicalize(base, _FlatTypeMaskKind.subclass, world,
hasLateSentinel: hasLateSentinel);
factory FlatTypeMask.nonNullSubtype(ClassEntity base, JClosedWorld world,
{bool hasLateSentinel: false}) =>
FlatTypeMask._canonicalize(base, _FlatTypeMaskKind.subtype, world,
hasLateSentinel: hasLateSentinel);
factory FlatTypeMask._canonicalize(
ClassEntity base, _FlatTypeMaskKind kind, JClosedWorld world,
{bool isNullable: false}) {
{bool isNullable: false, bool hasLateSentinel: false}) {
if (base == world.commonElements.nullClass) {
return FlatTypeMask.empty();
return FlatTypeMask.empty(hasLateSentinel: hasLateSentinel);
}
return FlatTypeMask._(base, _computeFlags(kind, hasNull: isNullable));
return FlatTypeMask._(
base,
_computeFlags(kind,
isNullable: isNullable, hasLateSentinel: hasLateSentinel));
}
const FlatTypeMask._(this.base, this.flags);
@@ -73,9 +97,10 @@ class FlatTypeMask extends TypeMask {
/// [TypeMask.assertIsNormalized] with the factory's result returns `true`.
factory FlatTypeMask.normalized(
ClassEntity base, int flags, CommonMasks domain) {
bool isNullable = _hasNullFlag(flags);
bool isNullable = _hasNullableFlag(flags);
bool hasLateSentinel = _hasLateSentinelFlag(flags);
if (base == domain.commonElements.nullClass) {
return FlatTypeMask.empty();
return FlatTypeMask.empty(hasLateSentinel: hasLateSentinel);
}
_FlatTypeMaskKind kind = _lookupKind(flags);
if (kind == _FlatTypeMaskKind.empty || kind == _FlatTypeMaskKind.exact) {
@@ -84,12 +109,14 @@ class FlatTypeMask extends TypeMask {
if (kind == _FlatTypeMaskKind.subtype) {
if (!domain._closedWorld.classHierarchy.hasAnyStrictSubtype(base) ||
domain._closedWorld.classHierarchy.hasOnlySubclasses(base)) {
flags = _computeFlags(_FlatTypeMaskKind.subclass, hasNull: isNullable);
flags = _computeFlags(_FlatTypeMaskKind.subclass,
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
}
if (kind == _FlatTypeMaskKind.subclass &&
!domain._closedWorld.classHierarchy.hasAnyStrictSubclass(base)) {
flags = _computeFlags(_FlatTypeMaskKind.exact, hasNull: isNullable);
flags = _computeFlags(_FlatTypeMaskKind.exact,
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
return domain.getCachedMask(base, flags, () => FlatTypeMask._(base, flags));
}
@@ -131,7 +158,17 @@ class FlatTypeMask extends TypeMask {
@override
bool get isExact => _kind == _FlatTypeMaskKind.exact;
@override
bool get isNullable => _hasNullFlag(flags);
bool get isNullable => _hasNullableFlag(flags);
@override
bool get hasLateSentinel => _hasLateSentinelFlag(flags);
@override
AbstractBool get isLateSentinel {
if (!hasLateSentinel) return AbstractBool.False;
if (isEmptyOrFlagged && _mask == _LATE_SENTINEL_MASK) {
return AbstractBool.True;
}
return AbstractBool.Maybe;
}
@override
bool get isUnion => false;
@@ -155,8 +192,10 @@ class FlatTypeMask extends TypeMask {
bool get isSubtype => _kind == _FlatTypeMaskKind.subtype;
@override
FlatTypeMask withFlags({bool isNullable}) {
int newFlags = _computeFlags(_kind, hasNull: isNullable ?? this.isNullable);
FlatTypeMask withFlags({bool isNullable, bool hasLateSentinel}) {
int newFlags = _computeFlags(_kind,
isNullable: isNullable ?? this.isNullable,
hasLateSentinel: hasLateSentinel ?? this.hasLateSentinel);
if (newFlags == flags) return this;
return FlatTypeMask._(base, newFlags);
}
@@ -206,6 +245,9 @@ class FlatTypeMask extends TypeMask {
bool isInMask(TypeMask other, JClosedWorld closedWorld) {
// Quick check whether to handle null.
if (isNullable && !other.isNullable) return false;
if (hasLateSentinel && !other.hasLateSentinel) {
return false;
}
// The empty type contains no classes.
if (isEmptyOrFlagged) return true;
if (other.isEmptyOrFlagged) return false;
@@ -288,6 +330,7 @@ class FlatTypeMask extends TypeMask {
ClassEntity singleClass(JClosedWorld closedWorld) {
if (isEmptyOrFlagged) return null;
if (isNullable) return null; // It is Null and some other class.
if (hasLateSentinel) return null;
if (isExact) {
return base;
} else if (isSubclass) {
@@ -315,10 +358,13 @@ class FlatTypeMask extends TypeMask {
if (other is! FlatTypeMask) return other.union(this, domain);
FlatTypeMask flatOther = other;
bool isNullable = this.isNullable || flatOther.isNullable;
bool hasLateSentinel = this.hasLateSentinel || flatOther.hasLateSentinel;
if (isEmptyOrFlagged) {
return flatOther.withFlags(isNullable: isNullable);
return flatOther.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
} else if (flatOther.isEmptyOrFlagged) {
return withFlags(isNullable: isNullable);
return withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
} else if (base == flatOther.base) {
return unionSame(flatOther, domain);
} else if (closedWorld.classHierarchy.isSubclassOf(flatOther.base, base)) {
@@ -332,7 +378,7 @@ class FlatTypeMask extends TypeMask {
} else {
return UnionTypeMask._internal(
<FlatTypeMask>[withoutFlags(), flatOther.withoutFlags()],
isNullable: isNullable);
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
}
@@ -411,11 +457,14 @@ class FlatTypeMask extends TypeMask {
ClassEntity otherBase = flatOther.base;
bool includeNull = isNullable && flatOther.isNullable;
bool includeLateSentinel = hasLateSentinel && flatOther.hasLateSentinel;
if (isEmptyOrFlagged) {
return withFlags(isNullable: includeNull);
return withFlags(
isNullable: includeNull, hasLateSentinel: includeLateSentinel);
} else if (flatOther.isEmptyOrFlagged) {
return other.withFlags(isNullable: includeNull);
return other.withFlags(
isNullable: includeNull, hasLateSentinel: includeLateSentinel);
}
SubclassResult result = domain._closedWorld.classHierarchy
@@ -423,43 +472,58 @@ class FlatTypeMask extends TypeMask {
switch (result.kind) {
case SubclassResultKind.EMPTY:
return includeNull ? domain.nullType : domain.emptyType;
return includeNull
? TypeMask.empty(hasLateSentinel: includeLateSentinel)
: TypeMask.nonNullEmpty(hasLateSentinel: includeLateSentinel);
case SubclassResultKind.EXACT1:
assert(isExact);
return includeNull ? this : nonNullable();
return withFlags(
isNullable: includeNull, hasLateSentinel: includeLateSentinel);
case SubclassResultKind.EXACT2:
assert(other.isExact);
return includeNull ? other : other.nonNullable();
return other.withFlags(
isNullable: includeNull, hasLateSentinel: includeLateSentinel);
case SubclassResultKind.SUBCLASS1:
assert(isSubclass);
return includeNull ? this : nonNullable();
return withFlags(
isNullable: includeNull, hasLateSentinel: includeLateSentinel);
case SubclassResultKind.SUBCLASS2:
assert(flatOther.isSubclass);
return includeNull ? other : other.nonNullable();
return other.withFlags(
isNullable: includeNull, hasLateSentinel: includeLateSentinel);
case SubclassResultKind.SUBTYPE1:
assert(isSubtype);
return includeNull ? this : nonNullable();
return withFlags(
isNullable: includeNull, hasLateSentinel: includeLateSentinel);
case SubclassResultKind.SUBTYPE2:
assert(flatOther.isSubtype);
return includeNull ? other : other.nonNullable();
return other.withFlags(
isNullable: includeNull, hasLateSentinel: includeLateSentinel);
case SubclassResultKind.SET:
default:
if (result.classes.isEmpty) {
return includeNull ? domain.nullType : domain.emptyType;
return includeNull
? TypeMask.empty(hasLateSentinel: includeLateSentinel)
: TypeMask.nonNullEmpty(hasLateSentinel: includeLateSentinel);
} else if (result.classes.length == 1) {
ClassEntity cls = result.classes.first;
return includeNull
? TypeMask.subclass(cls, domain._closedWorld)
: TypeMask.nonNullSubclass(cls, domain._closedWorld);
? TypeMask.subclass(cls, domain._closedWorld,
hasLateSentinel: includeLateSentinel)
: TypeMask.nonNullSubclass(cls, domain._closedWorld,
hasLateSentinel: includeLateSentinel);
}
List<FlatTypeMask> masks = List.from(result.classes.map(
(ClassEntity cls) =>
TypeMask.nonNullSubclass(cls, domain._closedWorld)));
if (masks.length > UnionTypeMask.MAX_UNION_LENGTH) {
return UnionTypeMask.flatten(masks, domain, includeNull: includeNull);
return UnionTypeMask.flatten(masks, domain,
includeNull: includeNull,
includeLateSentinel: includeLateSentinel);
}
return UnionTypeMask._internal(masks, isNullable: includeNull);
return UnionTypeMask._internal(masks,
isNullable: includeNull, hasLateSentinel: includeLateSentinel);
}
}
@@ -469,6 +533,7 @@ class FlatTypeMask extends TypeMask {
FlatTypeMask flatOther = other;
if (isNullable && flatOther.isNullable) return false;
if (hasLateSentinel && flatOther.hasLateSentinel) return false;
if (isEmptyOrFlagged || flatOther.isEmptyOrFlagged) return true;
if (base == flatOther.base) return false;
if (isExact && flatOther.isExact) return true;
@@ -548,7 +613,10 @@ class FlatTypeMask extends TypeMask {
TypeMask intersectionEmpty(FlatTypeMask other) {
bool isNullable = this.isNullable && other.isNullable;
return isNullable ? TypeMask.empty() : TypeMask.nonNullEmpty();
bool hasLateSentinel = this.hasLateSentinel && other.hasLateSentinel;
return isNullable
? TypeMask.empty(hasLateSentinel: hasLateSentinel)
: TypeMask.nonNullEmpty(hasLateSentinel: hasLateSentinel);
}
@override
@@ -651,6 +719,7 @@ class FlatTypeMask extends TypeMask {
buffer.writeAll([
if (isEmpty) 'empty',
if (isNullable) 'null',
if (hasLateSentinel) 'sentinel',
if (isExact) 'exact=${base.name}',
if (isSubclass) 'subclass=${base.name}',
if (isSubtype) 'subtype=${base.name}',
@@ -20,6 +20,10 @@ abstract class ForwardingTypeMask extends TypeMask {
@override
bool get isNull => forwardTo.isNull;
@override
bool get hasLateSentinel => forwardTo.hasLateSentinel;
@override
AbstractBool get isLateSentinel => forwardTo.isLateSentinel;
@override
bool get isExact => forwardTo.isExact;
@override
@@ -98,18 +102,22 @@ abstract class ForwardingTypeMask extends TypeMask {
return this;
}
bool isNullable = this.isNullable || other.isNullable;
bool hasLateSentinel = this.hasLateSentinel || other.hasLateSentinel;
if (isEmptyOrFlagged) {
return other.withFlags(isNullable: isNullable);
return other.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
if (other.isEmptyOrFlagged) {
return withFlags(isNullable: isNullable);
return withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
return _unionSpecialCases(other, domain, isNullable: isNullable) ??
return _unionSpecialCases(other, domain,
isNullable: isNullable, hasLateSentinel: hasLateSentinel) ??
forwardTo.union(other, domain);
}
TypeMask _unionSpecialCases(TypeMask other, CommonMasks domain,
{bool isNullable}) =>
{bool isNullable, bool hasLateSentinel}) =>
null;
@override
@@ -121,7 +129,9 @@ abstract class ForwardingTypeMask extends TypeMask {
TypeMask intersection(TypeMask other, CommonMasks domain) {
TypeMask forwardIntersection = forwardTo.intersection(other, domain);
if (forwardIntersection.isEmptyOrFlagged) return forwardIntersection;
return withFlags(isNullable: forwardIntersection.isNullable);
return withFlags(
isNullable: forwardIntersection.isNullable,
hasLateSentinel: forwardIntersection.hasLateSentinel);
}
@override
@@ -58,13 +58,20 @@ class MapTypeMask extends AllocationTypeMask {
}
@override
MapTypeMask withFlags({bool isNullable}) {
MapTypeMask withFlags({bool isNullable, bool hasLateSentinel}) {
isNullable ??= this.isNullable;
if (isNullable == this.isNullable) {
hasLateSentinel ??= this.hasLateSentinel;
if (isNullable == this.isNullable &&
hasLateSentinel == this.hasLateSentinel) {
return this;
}
return MapTypeMask(forwardTo.withFlags(isNullable: isNullable),
allocationNode, allocationElement, keyType, valueType);
return MapTypeMask(
forwardTo.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel),
allocationNode,
allocationElement,
keyType,
valueType);
}
@override
@@ -76,8 +83,9 @@ class MapTypeMask extends AllocationTypeMask {
@override
TypeMask _unionSpecialCases(TypeMask other, CommonMasks domain,
{bool isNullable}) {
{bool isNullable, bool hasLateSentinel}) {
assert(isNullable != null);
assert(hasLateSentinel != null);
if (other is MapTypeMask &&
keyType != null &&
other.keyType != null &&
@@ -45,6 +45,7 @@ class CommonMasks implements AbstractValueDomain {
CommonElements get commonElements => _closedWorld.commonElements;
DartTypes get dartTypes => _closedWorld.dartTypes;
TypeMask _internalTopType;
TypeMask _dynamicType;
TypeMask _nonNullType;
TypeMask _nullType;
@@ -88,6 +89,11 @@ class CommonMasks implements AbstractValueDomain {
return cachedMasks.putIfAbsent(base, createMask);
}
@override
TypeMask get internalTopType => _internalTopType ??= TypeMask.subclass(
_closedWorld.commonElements.objectClass, _closedWorld,
hasLateSentinel: true);
@override
TypeMask get dynamicType => _dynamicType ??=
TypeMask.subclass(_closedWorld.commonElements.objectClass, _closedWorld);
@@ -184,6 +190,9 @@ class CommonMasks implements AbstractValueDomain {
@override
TypeMask get nullType => _nullType ??= TypeMask.empty();
@override
TypeMask get lateSentinelType => TypeMask.nonNullEmpty(hasLateSentinel: true);
@override
TypeMask get emptyType => TypeMask.nonNullEmpty();
@@ -389,6 +398,14 @@ class CommonMasks implements AbstractValueDomain {
@override
TypeMask includeNull(TypeMask mask) => mask.nullable();
@override
TypeMask excludeLateSentinel(TypeMask mask) =>
mask.withFlags(hasLateSentinel: false);
@override
TypeMask includeLateSentinel(TypeMask mask) =>
mask.withFlags(hasLateSentinel: true);
@override
AbstractBool containsType(TypeMask typeMask, ClassEntity cls) {
return AbstractBool.trueOrFalse(_containsType(typeMask, cls));
@@ -438,8 +455,8 @@ class CommonMasks implements AbstractValueDomain {
AbstractBool.trueOrMaybe(value.isEmpty);
@override
AbstractBool isExact(TypeMask value) =>
AbstractBool.trueOrMaybe(value.isExact && !value.isNullable);
AbstractBool isExact(TypeMask value) => AbstractBool.trueOrMaybe(
value.isExact && !value.isNullable && !value.hasLateSentinel);
@override
ClassEntity getExactClass(TypeMask mask) {
@@ -475,6 +492,9 @@ class CommonMasks implements AbstractValueDomain {
}
}
@override
AbstractBool isLateSentinel(TypeMask value) => value.isLateSentinel;
@override
AbstractBool isPrimitive(TypeMask value) {
return AbstractBool.maybeOrFalse(_canBePrimitiveNumber(value) ||
@@ -565,25 +585,29 @@ class CommonMasks implements AbstractValueDomain {
@override
AbstractBool isInteger(TypeMask value) {
return AbstractBool.trueOrMaybe(
value.containsOnlyInt(_closedWorld) && !value.isNullable);
return AbstractBool.trueOrMaybe(value.containsOnlyInt(_closedWorld) &&
!value.isNullable &&
!value.hasLateSentinel);
}
@override
AbstractBool isUInt32(TypeMask value) {
return AbstractBool.trueOrMaybe(!value.isNullable &&
!value.hasLateSentinel &&
_isInstanceOfOrNull(value, commonElements.jsUInt32Class));
}
@override
AbstractBool isUInt31(TypeMask value) {
return AbstractBool.trueOrMaybe(!value.isNullable &&
!value.hasLateSentinel &&
_isInstanceOfOrNull(value, commonElements.jsUInt31Class));
}
@override
AbstractBool isPositiveInteger(TypeMask value) {
return AbstractBool.trueOrMaybe(!value.isNullable &&
!value.hasLateSentinel &&
_isInstanceOfOrNull(value, commonElements.jsPositiveIntClass));
}
@@ -600,8 +624,9 @@ class CommonMasks implements AbstractValueDomain {
@override
AbstractBool isNumber(TypeMask value) {
return AbstractBool.trueOrMaybe(
value.containsOnlyNum(_closedWorld) && !value.isNullable);
return AbstractBool.trueOrMaybe(value.containsOnlyNum(_closedWorld) &&
!value.isNullable &&
!value.hasLateSentinel);
}
@override
@@ -614,8 +639,9 @@ class CommonMasks implements AbstractValueDomain {
@override
AbstractBool isBoolean(TypeMask value) {
return AbstractBool.trueOrMaybe(
value.containsOnlyBool(_closedWorld) && !value.isNullable);
return AbstractBool.trueOrMaybe(value.containsOnlyBool(_closedWorld) &&
!value.isNullable &&
!value.hasLateSentinel);
}
@override
@@ -628,7 +654,7 @@ class CommonMasks implements AbstractValueDomain {
@override
AbstractBool isTruthy(TypeMask value) {
if (value is ValueTypeMask && !value.isNullable) {
if (value is ValueTypeMask && !value.isNullable && !value.hasLateSentinel) {
PrimitiveConstantValue constant = value.value;
if (constant is BoolConstantValue) {
return constant.boolValue ? AbstractBool.True : AbstractBool.False;
@@ -640,8 +666,9 @@ class CommonMasks implements AbstractValueDomain {
@override
AbstractBool isString(TypeMask value) {
return AbstractBool.trueOrMaybe(
value.containsOnlyString(_closedWorld) && !value.isNullable);
return AbstractBool.trueOrMaybe(value.containsOnlyString(_closedWorld) &&
!value.isNullable &&
!value.hasLateSentinel);
}
@override
@@ -972,7 +999,10 @@ String formatType(DartTypes dartTypes, TypeMask type) {
// a null value we accidentally printed out.
if (type.isEmpty) return 'Empty';
if (type.isEmptyOrFlagged) {
return [if (type.isNullable) 'Null'].join('');
return [
if (type.isNullable) 'Null',
if (type.hasLateSentinel) '\$',
].join('');
}
String nullFlag = type.isNullable ? '?' : '';
String subFlag = type.isExact
@@ -980,7 +1010,8 @@ String formatType(DartTypes dartTypes, TypeMask type) {
: type.isSubclass
? '+'
: '*';
return '${type.base.name}$nullFlag$subFlag';
String sentinelFlag = type.hasLateSentinel ? '\$' : '';
return '${type.base.name}$nullFlag$subFlag$sentinelFlag';
}
if (type is UnionTypeMask) {
return type.disjointMasks.map((m) => formatType(dartTypes, m)).join(' | ');
@@ -53,13 +53,19 @@ class SetTypeMask extends AllocationTypeMask {
}
@override
SetTypeMask withFlags({bool isNullable}) {
SetTypeMask withFlags({bool isNullable, bool hasLateSentinel}) {
isNullable ??= this.isNullable;
if (isNullable == this.isNullable) {
hasLateSentinel ??= this.hasLateSentinel;
if (isNullable == this.isNullable &&
hasLateSentinel == this.hasLateSentinel) {
return this;
}
return SetTypeMask(forwardTo.withFlags(isNullable: isNullable),
allocationNode, allocationElement, elementType);
return SetTypeMask(
forwardTo.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel),
allocationNode,
allocationElement,
elementType);
}
@override
@@ -70,8 +76,9 @@ class SetTypeMask extends AllocationTypeMask {
@override
TypeMask _unionSpecialCases(TypeMask other, CommonMasks domain,
{bool isNullable}) {
{bool isNullable, bool hasLateSentinel}) {
assert(isNullable != null);
assert(hasLateSentinel != null);
if (other is SetTypeMask &&
elementType != null &&
other.elementType != null) {
@@ -110,25 +110,32 @@ enum TypeMaskKind {
abstract class TypeMask implements AbstractValue {
const TypeMask();
factory TypeMask.empty() = FlatTypeMask.empty;
factory TypeMask.empty({bool hasLateSentinel: false}) =>
FlatTypeMask.empty(hasLateSentinel: hasLateSentinel);
factory TypeMask.exact(ClassEntity base, JClosedWorld closedWorld) {
factory TypeMask.exact(ClassEntity base, JClosedWorld closedWorld,
{bool hasLateSentinel: false}) {
assert(
closedWorld.classHierarchy.isInstantiated(base),
failedAt(
base ?? CURRENT_ELEMENT_SPANNABLE,
"Cannot create exact type mask for uninstantiated "
"class $base.\n${closedWorld.classHierarchy.dump(base)}"));
return FlatTypeMask.exact(base, closedWorld);
return FlatTypeMask.exact(base, closedWorld,
hasLateSentinel: hasLateSentinel);
}
factory TypeMask.exactOrEmpty(ClassEntity base, JClosedWorld closedWorld) {
if (closedWorld.classHierarchy.isInstantiated(base))
return FlatTypeMask.exact(base, closedWorld);
return TypeMask.empty();
factory TypeMask.exactOrEmpty(ClassEntity base, JClosedWorld closedWorld,
{bool hasLateSentinel: false}) {
if (closedWorld.classHierarchy.isInstantiated(base)) {
return FlatTypeMask.exact(base, closedWorld,
hasLateSentinel: hasLateSentinel);
}
return TypeMask.empty(hasLateSentinel: hasLateSentinel);
}
factory TypeMask.subclass(ClassEntity base, JClosedWorld closedWorld) {
factory TypeMask.subclass(ClassEntity base, JClosedWorld closedWorld,
{bool hasLateSentinel: false}) {
assert(
closedWorld.classHierarchy.isInstantiated(base),
failedAt(
@@ -137,50 +144,62 @@ abstract class TypeMask implements AbstractValue {
"class $base.\n${closedWorld.classHierarchy.dump(base)}"));
ClassEntity topmost = closedWorld.getLubOfInstantiatedSubclasses(base);
if (topmost == null) {
return TypeMask.empty();
return TypeMask.empty(hasLateSentinel: hasLateSentinel);
} else if (closedWorld.classHierarchy.hasAnyStrictSubclass(topmost)) {
return FlatTypeMask.subclass(topmost, closedWorld);
return FlatTypeMask.subclass(topmost, closedWorld,
hasLateSentinel: hasLateSentinel);
} else {
return TypeMask.exact(topmost, closedWorld);
return TypeMask.exact(topmost, closedWorld,
hasLateSentinel: hasLateSentinel);
}
}
factory TypeMask.subtype(ClassEntity base, JClosedWorld closedWorld) {
factory TypeMask.subtype(ClassEntity base, JClosedWorld closedWorld,
{bool hasLateSentinel: false}) {
ClassEntity topmost = closedWorld.getLubOfInstantiatedSubtypes(base);
if (topmost == null) {
return TypeMask.empty();
return TypeMask.empty(hasLateSentinel: hasLateSentinel);
}
if (closedWorld.classHierarchy.hasOnlySubclasses(topmost)) {
return TypeMask.subclass(topmost, closedWorld);
return TypeMask.subclass(topmost, closedWorld,
hasLateSentinel: hasLateSentinel);
}
if (closedWorld.classHierarchy.hasAnyStrictSubtype(topmost)) {
return FlatTypeMask.subtype(topmost, closedWorld);
return FlatTypeMask.subtype(topmost, closedWorld,
hasLateSentinel: hasLateSentinel);
} else {
return TypeMask.exact(topmost, closedWorld);
return TypeMask.exact(topmost, closedWorld,
hasLateSentinel: hasLateSentinel);
}
}
factory TypeMask.nonNullEmpty() = FlatTypeMask.nonNullEmpty;
factory TypeMask.nonNullEmpty({bool hasLateSentinel: false}) =>
FlatTypeMask.nonNullEmpty(hasLateSentinel: hasLateSentinel);
factory TypeMask.nonNullExact(ClassEntity base, JClosedWorld closedWorld) {
factory TypeMask.nonNullExact(ClassEntity base, JClosedWorld closedWorld,
{bool hasLateSentinel: false}) {
assert(
closedWorld.classHierarchy.isInstantiated(base),
failedAt(
base ?? CURRENT_ELEMENT_SPANNABLE,
"Cannot create exact type mask for uninstantiated "
"class $base.\n${closedWorld.classHierarchy.dump(base)}"));
return FlatTypeMask.nonNullExact(base, closedWorld);
return FlatTypeMask.nonNullExact(base, closedWorld,
hasLateSentinel: hasLateSentinel);
}
factory TypeMask.nonNullExactOrEmpty(
ClassEntity base, JClosedWorld closedWorld) {
ClassEntity base, JClosedWorld closedWorld,
{bool hasLateSentinel: false}) {
if (closedWorld.classHierarchy.isInstantiated(base)) {
return FlatTypeMask.nonNullExact(base, closedWorld);
return FlatTypeMask.nonNullExact(base, closedWorld,
hasLateSentinel: hasLateSentinel);
}
return TypeMask.nonNullEmpty();
return TypeMask.nonNullEmpty(hasLateSentinel: hasLateSentinel);
}
factory TypeMask.nonNullSubclass(ClassEntity base, JClosedWorld closedWorld) {
factory TypeMask.nonNullSubclass(ClassEntity base, JClosedWorld closedWorld,
{bool hasLateSentinel: false}) {
assert(
closedWorld.classHierarchy.isInstantiated(base),
failedAt(
@@ -189,26 +208,32 @@ abstract class TypeMask implements AbstractValue {
"class $base.\n${closedWorld.classHierarchy.dump(base)}"));
ClassEntity topmost = closedWorld.getLubOfInstantiatedSubclasses(base);
if (topmost == null) {
return TypeMask.nonNullEmpty();
return TypeMask.nonNullEmpty(hasLateSentinel: hasLateSentinel);
} else if (closedWorld.classHierarchy.hasAnyStrictSubclass(topmost)) {
return FlatTypeMask.nonNullSubclass(topmost, closedWorld);
return FlatTypeMask.nonNullSubclass(topmost, closedWorld,
hasLateSentinel: hasLateSentinel);
} else {
return TypeMask.nonNullExact(topmost, closedWorld);
return TypeMask.nonNullExact(topmost, closedWorld,
hasLateSentinel: hasLateSentinel);
}
}
factory TypeMask.nonNullSubtype(ClassEntity base, JClosedWorld closedWorld) {
factory TypeMask.nonNullSubtype(ClassEntity base, JClosedWorld closedWorld,
{bool hasLateSentinel: false}) {
ClassEntity topmost = closedWorld.getLubOfInstantiatedSubtypes(base);
if (topmost == null) {
return TypeMask.nonNullEmpty();
return TypeMask.nonNullEmpty(hasLateSentinel: hasLateSentinel);
}
if (closedWorld.classHierarchy.hasOnlySubclasses(topmost)) {
return TypeMask.nonNullSubclass(topmost, closedWorld);
return TypeMask.nonNullSubclass(topmost, closedWorld,
hasLateSentinel: hasLateSentinel);
}
if (closedWorld.classHierarchy.hasAnyStrictSubtype(topmost)) {
return FlatTypeMask.nonNullSubtype(topmost, closedWorld);
return FlatTypeMask.nonNullSubtype(topmost, closedWorld,
hasLateSentinel: hasLateSentinel);
} else {
return TypeMask.nonNullExact(topmost, closedWorld);
return TypeMask.nonNullExact(topmost, closedWorld,
hasLateSentinel: hasLateSentinel);
}
}
@@ -308,9 +333,12 @@ abstract class TypeMask implements AbstractValue {
/// Returns a non-nullable variant of [this] type mask.
TypeMask nonNullable() => withFlags(isNullable: false);
TypeMask withoutFlags() => withFlags(isNullable: false);
/// Returns a variant of [this] type mask whose value is neither `null` nor
/// the late sentinel.
TypeMask withoutFlags() =>
withFlags(isNullable: false, hasLateSentinel: false);
TypeMask withFlags({bool isNullable});
TypeMask withFlags({bool isNullable, bool hasLateSentinel});
/// Whether nothing matches this mask, not even null.
bool get isEmpty;
@@ -321,7 +349,14 @@ abstract class TypeMask implements AbstractValue {
/// Whether the only possible value in this mask is Null.
bool get isNull;
/// Whether [this] mask is empty or only represents values tracked by flags.
/// Whether [this] is a sentinel for an uninitialized late variable.
AbstractBool get isLateSentinel;
/// Whether a late sentinel is a valid value of this mask.
bool get hasLateSentinel => isLateSentinel.isPotentiallyTrue;
/// Whether [this] mask is empty or only represents values tracked by flags
/// (i.e. `null` and the late sentinel).
bool get isEmptyOrFlagged;
/// Whether this mask only includes instances of an exact class, and none of
@@ -22,11 +22,20 @@ class UnionTypeMask extends TypeMask {
@override
final bool isNullable;
UnionTypeMask._internal(this.disjointMasks, {this.isNullable})
@override
final bool hasLateSentinel;
@override
AbstractBool get isLateSentinel => AbstractBool.maybeOrFalse(hasLateSentinel);
UnionTypeMask._internal(this.disjointMasks,
{this.isNullable, this.hasLateSentinel})
: assert(isNullable != null),
assert(hasLateSentinel != null),
assert(disjointMasks.length > 1),
assert(disjointMasks.every((TypeMask mask) => !mask.isUnion)),
assert(disjointMasks.every((TypeMask mask) => !mask.isNullable));
assert(disjointMasks.every((TypeMask mask) => !mask.isNullable)),
assert(disjointMasks.every((TypeMask mask) => !mask.hasLateSentinel));
/// Deserializes a [UnionTypeMask] object from [source].
factory UnionTypeMask.readFromDataSource(
@@ -35,8 +44,10 @@ class UnionTypeMask extends TypeMask {
List<FlatTypeMask> disjointMasks =
source.readList(() => TypeMask.readFromDataSource(source, domain));
bool isNullable = source.readBool();
bool hasLateSentinel = source.readBool();
source.end(tag);
return UnionTypeMask._internal(disjointMasks, isNullable: isNullable);
return UnionTypeMask._internal(disjointMasks,
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
/// Serializes this [UnionTypeMask] to [sink].
@@ -47,6 +58,7 @@ class UnionTypeMask extends TypeMask {
sink.writeList(
disjointMasks, (FlatTypeMask mask) => mask.writeToDataSink(sink));
sink.writeBool(isNullable);
sink.writeBool(hasLateSentinel);
sink.end(tag);
}
@@ -55,16 +67,21 @@ class UnionTypeMask extends TypeMask {
(mask) => TypeMask.assertIsNormalized(mask, domain._closedWorld)));
List<FlatTypeMask> disjoint = <FlatTypeMask>[];
bool isNullable = masks.any((TypeMask mask) => mask.isNullable);
bool hasLateSentinel = masks.any((TypeMask mask) => mask.hasLateSentinel);
unionOfHelper(masks, disjoint, domain);
if (disjoint.isEmpty)
return isNullable ? TypeMask.empty() : TypeMask.nonNullEmpty();
return isNullable
? TypeMask.empty(hasLateSentinel: hasLateSentinel)
: TypeMask.nonNullEmpty(hasLateSentinel: hasLateSentinel);
if (disjoint.length > MAX_UNION_LENGTH) {
return flatten(disjoint, domain, includeNull: isNullable);
return flatten(disjoint, domain,
includeNull: isNullable, includeLateSentinel: hasLateSentinel);
}
if (disjoint.length == 1)
return disjoint[0].withFlags(isNullable: isNullable);
UnionTypeMask union =
UnionTypeMask._internal(disjoint, isNullable: isNullable);
return disjoint.single
.withFlags(isNullable: isNullable, hasLateSentinel: hasLateSentinel);
UnionTypeMask union = UnionTypeMask._internal(disjoint,
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
assert(TypeMask.assertIsNormalized(union, domain._closedWorld));
return union;
}
@@ -123,8 +140,9 @@ class UnionTypeMask extends TypeMask {
}
static TypeMask flatten(List<FlatTypeMask> masks, CommonMasks domain,
{bool includeNull}) {
{bool includeNull, bool includeLateSentinel}) {
assert(includeNull != null);
assert(includeLateSentinel != null);
// TODO(johnniwinther): Move this computation to [ClosedWorld] and use the
// class set structures.
@@ -173,7 +191,8 @@ class UnionTypeMask extends TypeMask {
bestKind = kind;
}
}
int flags = FlatTypeMask._computeFlags(bestKind, hasNull: includeNull);
int flags = FlatTypeMask._computeFlags(bestKind,
isNullable: includeNull, hasLateSentinel: includeLateSentinel);
return FlatTypeMask.normalized(bestElement, flags, domain);
}
@@ -181,16 +200,20 @@ class UnionTypeMask extends TypeMask {
TypeMask union(TypeMask other, CommonMasks domain) {
other = TypeMask.nonForwardingMask(other);
bool isNullable = this.isNullable || other.isNullable;
bool hasLateSentinel = this.hasLateSentinel || other.hasLateSentinel;
if (other is UnionTypeMask) {
if (_containsDisjointMasks(other)) {
return withFlags(isNullable: isNullable);
return withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
if (other._containsDisjointMasks(this)) {
return other.withFlags(isNullable: isNullable);
return other.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
} else {
if (disjointMasks.contains(other.withoutFlags())) {
return withFlags(isNullable: isNullable);
return withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
}
@@ -201,23 +224,28 @@ class UnionTypeMask extends TypeMask {
newList.add(other);
}
TypeMask newMask = TypeMask.unionOf(newList, domain);
return newMask.withFlags(isNullable: isNullable);
return newMask.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
@override
TypeMask intersection(TypeMask other, CommonMasks domain) {
other = TypeMask.nonForwardingMask(other);
bool isNullable = this.isNullable && other.isNullable;
bool hasLateSentinel = this.hasLateSentinel && other.hasLateSentinel;
if (other is UnionTypeMask) {
if (_containsDisjointMasks(other)) {
return other.withFlags(isNullable: isNullable);
return other.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
if (other._containsDisjointMasks(this)) {
return withFlags(isNullable: isNullable);
return withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
} else {
if (disjointMasks.contains(other.withoutFlags())) {
return other.withFlags(isNullable: isNullable);
return other.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
}
@@ -236,12 +264,14 @@ class UnionTypeMask extends TypeMask {
}
}
TypeMask newMask = TypeMask.unionOf(intersections, domain);
return newMask.withFlags(isNullable: isNullable);
return newMask.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
@override
bool isDisjoint(TypeMask other, JClosedWorld closedWorld) {
if (isNullable && other.isNullable) return false;
if (hasLateSentinel && other.hasLateSentinel) return false;
for (var current in disjointMasks) {
if (!current.isDisjoint(other, closedWorld)) return false;
}
@@ -249,13 +279,16 @@ class UnionTypeMask extends TypeMask {
}
@override
UnionTypeMask withFlags({bool isNullable}) {
UnionTypeMask withFlags({bool isNullable, bool hasLateSentinel}) {
isNullable ??= this.isNullable;
if (isNullable == this.isNullable) {
hasLateSentinel ??= this.hasLateSentinel;
if (isNullable == this.isNullable &&
hasLateSentinel == this.hasLateSentinel) {
return this;
}
List<FlatTypeMask> newList = List<FlatTypeMask>.of(disjointMasks);
return UnionTypeMask._internal(newList, isNullable: isNullable);
return UnionTypeMask._internal(newList,
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
@override
@@ -292,6 +325,7 @@ class UnionTypeMask extends TypeMask {
assert(!other.isUnion);
// Likewise, nullness should be covered.
assert(isNullable || !other.isNullable);
assert(hasLateSentinel || !other.hasLateSentinel);
other = other.withoutFlags();
// Ensure the cheap test fails.
assert(!disjointMasks.any((mask) => mask.containsMask(other, closedWorld)));
@@ -321,6 +355,7 @@ class UnionTypeMask extends TypeMask {
bool isInMask(TypeMask other, JClosedWorld closedWorld) {
other = TypeMask.nonForwardingMask(other);
if (isNullable && !other.isNullable) return false;
if (hasLateSentinel && !other.hasLateSentinel) return false;
if (other.isUnion) {
UnionTypeMask union = other;
return disjointMasks.every((FlatTypeMask disjointMask) {
@@ -341,6 +376,7 @@ class UnionTypeMask extends TypeMask {
bool containsMask(TypeMask other, JClosedWorld closedWorld) {
other = TypeMask.nonForwardingMask(other);
if (other.isNullable && !isNullable) return false;
if (other.hasLateSentinel && !hasLateSentinel) return false;
if (other.isUnion) return other.isInMask(this, closedWorld);
other = other.withoutFlags();
bool contained =
@@ -419,7 +455,8 @@ class UnionTypeMask extends TypeMask {
MemberEntity locateSingleMember(Selector selector, CommonMasks domain) {
MemberEntity candidate;
for (FlatTypeMask mask in disjointMasks) {
if (isNullable) mask = mask.nullable();
mask = mask.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
MemberEntity current = mask.locateSingleMember(selector, domain);
if (current == null) {
return null;
@@ -436,6 +473,7 @@ class UnionTypeMask extends TypeMask {
String toString() {
String masksString = [
if (isNullable) 'null',
if (hasLateSentinel) 'sentinel',
...disjointMasks.map((TypeMask mask) => mask.toString()).toList()..sort(),
].join(", ");
return 'Union($masksString)';
@@ -447,6 +485,7 @@ class UnionTypeMask extends TypeMask {
return other is UnionTypeMask &&
other.isNullable == isNullable &&
other.hasLateSentinel == hasLateSentinel &&
other.disjointMasks.length == disjointMasks.length &&
_containsDisjointMasks(other);
}
@@ -455,7 +494,8 @@ class UnionTypeMask extends TypeMask {
int get hashCode {
// The order of the masks in [disjointMasks] must not affect the
// hashCode.
return Hashing.setHash(disjointMasks, isNullable.hashCode);
return Hashing.setHash(
disjointMasks, Hashing.objectsHash(isNullable, hasLateSentinel));
}
bool _containsDisjointMasks(UnionTypeMask other) =>
@@ -36,12 +36,17 @@ class ValueTypeMask extends ForwardingTypeMask {
}
@override
ValueTypeMask withFlags({bool isNullable}) {
ValueTypeMask withFlags({bool isNullable, bool hasLateSentinel}) {
isNullable ??= this.isNullable;
if (isNullable == this.isNullable) {
hasLateSentinel ??= this.hasLateSentinel;
if (isNullable == this.isNullable &&
hasLateSentinel == this.hasLateSentinel) {
return this;
}
return ValueTypeMask(forwardTo.withFlags(isNullable: isNullable), value);
return ValueTypeMask(
forwardTo.withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel),
value);
}
@override
@@ -49,12 +54,14 @@ class ValueTypeMask extends ForwardingTypeMask {
@override
TypeMask _unionSpecialCases(TypeMask other, CommonMasks domain,
{bool isNullable}) {
{bool isNullable, bool hasLateSentinel}) {
assert(isNullable != null);
assert(hasLateSentinel != null);
if (other is ValueTypeMask &&
forwardTo.withoutFlags() == other.forwardTo.withoutFlags() &&
value == other.value) {
return withFlags(isNullable: isNullable);
return withFlags(
isNullable: isNullable, hasLateSentinel: hasLateSentinel);
}
return null;
}
+3
View File
@@ -1147,6 +1147,9 @@ abstract class HInstruction implements Spannable {
AbstractBool isNull(AbstractValueDomain domain) =>
domain.isNull(instructionType);
AbstractBool isLateSentinel(AbstractValueDomain domain) =>
domain.isLateSentinel(instructionType);
AbstractBool isConflicting(AbstractValueDomain domain) =>
domain.isEmpty(instructionType);
+7 -7
View File
@@ -447,7 +447,8 @@ class SsaInstructionSimplifier extends HBaseVisitor
ConstantValue getConstantFromType(HInstruction node) {
if (node.isValue(_abstractValueDomain) &&
node.isNull(_abstractValueDomain).isDefinitelyFalse) {
node.isNull(_abstractValueDomain).isDefinitelyFalse &&
node.isLateSentinel(_abstractValueDomain).isDefinitelyFalse) {
ConstantValue value =
_abstractValueDomain.getPrimitiveValue(node.instructionType);
if (value.isBool) {
@@ -1167,14 +1168,13 @@ class SsaInstructionSimplifier extends HBaseVisitor
@override
HInstruction visitIsLateSentinel(HIsLateSentinel node) {
HInstruction value = node.inputs[0];
if (value is HConstant) {
return _graph.addConstantBool(
value.constant is LateSentinelConstantValue, _closedWorld);
AbstractBool isLateSentinel = value.isLateSentinel(_abstractValueDomain);
if (isLateSentinel.isDefinitelyTrue) {
return _graph.addConstantBool(true, _closedWorld);
} else if (isLateSentinel.isDefinitelyFalse) {
return _graph.addConstantBool(false, _closedWorld);
}
// TODO(fishythefish): Simplify to `false` when the input cannot evalute to
// the sentinel. This can be implemented in the powerset domain.
return super.visitIsLateSentinel(node);
}
+8 -3
View File
@@ -49,7 +49,7 @@ abstract class TypeBuilder {
/// Create a type mask for 'trusting' a DartType. Returns `null` if there is
/// no approximating type mask (i.e. the type mask would be `dynamic`).
AbstractValue trustTypeMask(DartType type) {
AbstractValue trustTypeMask(DartType type, {bool hasLateSentinel: false}) {
if (type == null) return null;
type = builder.localsHandler.substInContext(type);
if (_closedWorld.dartTypes.isTopType(type)) return null;
@@ -59,15 +59,20 @@ abstract class TypeBuilder {
if (type is! InterfaceType) return null;
// The type element is either a class or the void element.
ClassEntity element = (type as InterfaceType).element;
return includeNull
AbstractValue mask = includeNull
? _abstractValueDomain.createNullableSubtype(element)
: _abstractValueDomain.createNonNullSubtype(element);
if (hasLateSentinel) mask = _abstractValueDomain.includeLateSentinel(mask);
return mask;
}
/// Create an instruction to simply trust the provided type.
HInstruction _trustType(HInstruction original, DartType type) {
assert(type != null);
AbstractValue mask = trustTypeMask(type);
bool hasLateSentinel = _abstractValueDomain
.isLateSentinel(original.instructionType)
.isPotentiallyTrue;
AbstractValue mask = trustTypeMask(type, hasLateSentinel: hasLateSentinel);
if (mask == null) return original;
return new HTypeKnown.pinned(mask, original);
}
@@ -35,6 +35,7 @@ checkMasks(JClosedWorld closedWorld, List<ClassEntity> allClasses,
List<ClassEntity> containedClasses}) {
AbstractValueDomain commonMasks = closedWorld.abstractValueDomain;
bool isNullable = masks.any((FlatTypeMask mask) => mask.isNullable);
bool hasLateSentinel = masks.any((FlatTypeMask mask) => mask.hasLateSentinel);
List<FlatTypeMask> disjoint = <FlatTypeMask>[];
UnionTypeMask.unionOfHelper(masks, disjoint, commonMasks);
Expect.listEquals(disjointMasks, disjoint,
@@ -42,12 +43,12 @@ checkMasks(JClosedWorld closedWorld, List<ClassEntity> allClasses,
if (flattened == null) {
Expect.throws(
() => UnionTypeMask.flatten(disjoint, commonMasks,
includeNull: isNullable),
includeNull: isNullable, includeLateSentinel: hasLateSentinel),
(e) => e is ArgumentError,
'Expect argument error on flattening of $disjoint.');
} else {
TypeMask flattenResult =
UnionTypeMask.flatten(disjoint, commonMasks, includeNull: isNullable);
TypeMask flattenResult = UnionTypeMask.flatten(disjoint, commonMasks,
includeNull: isNullable, includeLateSentinel: hasLateSentinel);
Expect.equals(
flattened,
flattenResult,
@@ -122,11 +123,20 @@ Future testUnionTypeMaskFlatten() async {
}
TypeMask empty = TypeMask.nonNullEmpty();
TypeMask sentinel = TypeMask.nonNullEmpty(hasLateSentinel: true);
TypeMask subclassObject = TypeMask.nonNullSubclass(Object_, closedWorld);
TypeMask subclassObjectOrSentinel =
TypeMask.nonNullSubclass(Object_, closedWorld, hasLateSentinel: true);
TypeMask exactA = TypeMask.nonNullExact(A, closedWorld);
TypeMask exactAOrSentinel =
TypeMask.nonNullExact(A, closedWorld, hasLateSentinel: true);
TypeMask subclassA = TypeMask.nonNullSubclass(A, closedWorld);
TypeMask subtypeA = TypeMask.nonNullSubtype(A, closedWorld);
TypeMask subtypeAOrSentinel =
TypeMask.nonNullSubtype(A, closedWorld, hasLateSentinel: true);
TypeMask exactB = TypeMask.nonNullExact(B, closedWorld);
TypeMask exactBOrSentinel =
TypeMask.nonNullExact(B, closedWorld, hasLateSentinel: true);
TypeMask subclassB = TypeMask.nonNullSubclass(B, closedWorld);
TypeMask exactC = TypeMask.nonNullExact(C, closedWorld);
TypeMask exactD = TypeMask.nonNullExact(D, closedWorld);
@@ -214,6 +224,52 @@ Future testUnionTypeMaskFlatten() async {
disjointMasks: [subclassB, exactA],
flattened: subclassObject,
containedClasses: [A, B, E]);
check([sentinel],
result: sentinel,
disjointMasks: const [],
flattened: null,
containedClasses: const []);
check([sentinel, sentinel],
result: sentinel,
disjointMasks: const [],
flattened: null,
containedClasses: const []);
check([empty, sentinel],
result: sentinel,
disjointMasks: const [],
flattened: null,
containedClasses: const []);
check([sentinel, empty],
result: sentinel,
disjointMasks: const [],
flattened: null,
containedClasses: const []);
check([exactAOrSentinel],
result: exactAOrSentinel,
disjointMasks: [exactA],
flattened: subtypeAOrSentinel, // TODO(37602): Imprecise.
containedClasses: [A]);
check([exactA, exactAOrSentinel],
result: exactAOrSentinel,
disjointMasks: [exactA],
flattened: subtypeAOrSentinel, // TODO(37602): Imprecise.
containedClasses: [A]);
check([exactAOrSentinel, exactB],
disjointMasks: [exactA, exactB],
flattened: subclassObjectOrSentinel,
containedClasses: [A, B]);
check([exactAOrSentinel, exactBOrSentinel],
disjointMasks: [exactA, exactB],
flattened: subclassObjectOrSentinel,
containedClasses: [A, B]);
}
Future testStringSubtypes() async {
@@ -17,7 +17,8 @@ AbstractValue simplify(AbstractValue value, AbstractValueDomain domain) {
return simplify(value.forwardTo, domain);
} else if (value is UnionTypeMask) {
return UnionTypeMask.flatten(value.disjointMasks, domain,
includeNull: value.isNullable);
includeNull: value.isNullable,
includeLateSentinel: value.hasLateSentinel);
} else {
return value;
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2021, 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 'package:expect/expect.dart';
// Tests to ensure that narrowing type information does not discard late
// sentinel values unintentionally.
class Foo<T> {
// Since `List<T>` contains a free type variable, any access of [x] will be
// immediately followed by a narrowing to the appropriate instantiation of
// `List<T>`. This narrowing should not exclude the late sentinel value from
// the abstract value.
late final List<T> x;
}
void main() {
Foo<int> foo = Foo();
Expect.throws(() => foo.x);
foo.x = const [];
Expect.isTrue(foo.x.isEmpty);
}