[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:
committed by
commit-bot@chromium.org
parent
93164ae2a6
commit
f5b8cc2c76
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user