| // Copyright (c) 2013, 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. |
| |
| part of masks; |
| |
| enum _FlatTypeMaskKind { empty, exact, subclass, subtype } |
| |
| /// A flat type mask is a type mask that has been flattened to contain a |
| /// base type. |
| class FlatTypeMask extends TypeMask { |
| /// Tag used for identifying serialized [FlatTypeMask] objects in a |
| /// debugging data stream. |
| static const String tag = 'flat-type-mask'; |
| |
| static const int _NULL_INDEX = 0; |
| 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 isNullable = false, bool hasLateSentinel = false}) { |
| int mask = _NONE_MASK; |
| if (isNullable) mask |= _NULL_MASK; |
| if (hasLateSentinel) mask |= _LATE_SENTINEL_MASK; |
| return _computeFlagsRaw(kind.index, mask); |
| } |
| |
| static int _computeFlagsRaw(int kind, int mask) => |
| kind << _USED_INDICES | mask; |
| |
| static _FlatTypeMaskKind _lookupKind(int flags) => |
| _FlatTypeMaskKind.values[flags >> _USED_INDICES]; |
| |
| static bool _hasNullableFlag(int flags) => flags & _NULL_MASK != _NONE_MASK; |
| |
| 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, hasLateSentinel: hasLateSentinel); |
| factory FlatTypeMask.subclass(ClassEntity base, JClosedWorld world, |
| {bool hasLateSentinel = false}) => |
| FlatTypeMask._canonicalize(base, _FlatTypeMaskKind.subclass, world, |
| isNullable: true, hasLateSentinel: hasLateSentinel); |
| factory FlatTypeMask.subtype(ClassEntity base, JClosedWorld world, |
| {bool hasLateSentinel = false}) => |
| FlatTypeMask._canonicalize(base, _FlatTypeMaskKind.subtype, world, |
| isNullable: true, hasLateSentinel: hasLateSentinel); |
| |
| factory FlatTypeMask.nonNullEmpty({bool hasLateSentinel = false}) => |
| hasLateSentinel |
| ? const FlatTypeMask._(null, _LATE_SENTINEL_MASK) |
| : const FlatTypeMask._(null, _NONE_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, |
| {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 hasLateSentinel = false}) { |
| if (base == world.commonElements.nullClass) { |
| return FlatTypeMask.empty(hasLateSentinel: hasLateSentinel); |
| } |
| return FlatTypeMask._( |
| base, |
| _computeFlags(kind, |
| isNullable: isNullable, hasLateSentinel: hasLateSentinel)); |
| } |
| |
| const FlatTypeMask._(this.base, this.flags); |
| |
| /// Ensures that the generated mask is normalized, i.e., a call to |
| /// [TypeMask.assertIsNormalized] with the factory's result returns `true`. |
| factory FlatTypeMask.normalized( |
| ClassEntity base, int flags, CommonMasks domain) { |
| bool isNullable = _hasNullableFlag(flags); |
| bool hasLateSentinel = _hasLateSentinelFlag(flags); |
| if (base == domain.commonElements.nullClass) { |
| return FlatTypeMask.empty(hasLateSentinel: hasLateSentinel); |
| } |
| _FlatTypeMaskKind kind = _lookupKind(flags); |
| if (kind == _FlatTypeMaskKind.empty || kind == _FlatTypeMaskKind.exact) { |
| return FlatTypeMask._(base, flags); |
| } |
| if (kind == _FlatTypeMaskKind.subtype) { |
| if (!domain._closedWorld.classHierarchy.hasAnyStrictSubtype(base) || |
| domain._closedWorld.classHierarchy.hasOnlySubclasses(base)) { |
| flags = _computeFlags(_FlatTypeMaskKind.subclass, |
| isNullable: isNullable, hasLateSentinel: hasLateSentinel); |
| } |
| } |
| if (kind == _FlatTypeMaskKind.subclass && |
| !domain._closedWorld.classHierarchy.hasAnyStrictSubclass(base)) { |
| flags = _computeFlags(_FlatTypeMaskKind.exact, |
| isNullable: isNullable, hasLateSentinel: hasLateSentinel); |
| } |
| return domain.getCachedMask(base, flags, () => FlatTypeMask._(base, flags)); |
| } |
| |
| /// Deserializes a [FlatTypeMask] object from [source]. |
| factory FlatTypeMask.readFromDataSource( |
| DataSource source, CommonMasks domain) { |
| source.begin(tag); |
| ClassEntity base = source.readClassOrNull(); |
| int flags = source.readInt(); |
| source.end(tag); |
| return domain.getCachedMask(base, flags, () => FlatTypeMask._(base, flags)); |
| } |
| |
| /// Serializes this [FlatTypeMask] to [sink]. |
| @override |
| void writeToDataSink(DataSink sink) { |
| sink.writeEnum(TypeMaskKind.flat); |
| sink.begin(tag); |
| sink.writeClassOrNull(base); |
| sink.writeInt(flags); |
| sink.end(tag); |
| } |
| |
| _FlatTypeMaskKind get _kind => _lookupKind(flags); |
| |
| int get _mask => flags & _ALL_MASK; |
| |
| ClassQuery get _classQuery => isExact |
| ? ClassQuery.EXACT |
| : (isSubclass ? ClassQuery.SUBCLASS : ClassQuery.SUBTYPE); |
| |
| @override |
| bool get isEmpty => isEmptyOrFlagged && _mask == _NONE_MASK; |
| @override |
| bool get isNull => isEmptyOrFlagged && _mask == _NULL_MASK; |
| @override |
| bool get isEmptyOrFlagged => _kind == _FlatTypeMaskKind.empty; |
| @override |
| bool get isExact => _kind == _FlatTypeMaskKind.exact; |
| @override |
| 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; |
| @override |
| bool get isContainer => false; |
| @override |
| bool get isSet => false; |
| @override |
| bool get isMap => false; |
| @override |
| bool get isDictionary => false; |
| @override |
| bool get isForwarding => false; |
| @override |
| bool get isValue => false; |
| |
| // TODO(kasperl): Get rid of these. They should not be a visible |
| // part of the implementation because they make it hard to add |
| // proper union types if we ever want to. |
| bool get isSubclass => _kind == _FlatTypeMaskKind.subclass; |
| bool get isSubtype => _kind == _FlatTypeMaskKind.subtype; |
| |
| @override |
| 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); |
| } |
| |
| @override |
| bool contains(ClassEntity other, JClosedWorld closedWorld) { |
| if (isEmptyOrFlagged) { |
| return false; |
| } else if (identical(base, other)) { |
| return true; |
| } else if (isExact) { |
| return false; |
| } else if (isSubclass) { |
| return closedWorld.classHierarchy.isSubclassOf(other, base); |
| } else { |
| assert(isSubtype); |
| return closedWorld.classHierarchy.isSubtypeOf(other, base); |
| } |
| } |
| |
| bool _isSingleImplementationOf(ClassEntity cls, JClosedWorld closedWorld) { |
| // Special case basic types so that, for example, JSString is the |
| // single implementation of String. |
| // The general optimization is to realize there is only one class that |
| // implements [base] and [base] is not instantiated. We however do |
| // not track correctly the list of truly instantiated classes. |
| CommonElements commonElements = closedWorld.commonElements; |
| if (containsOnlyString(closedWorld)) { |
| return cls == closedWorld.commonElements.stringClass || |
| cls == commonElements.jsStringClass; |
| } |
| if (containsOnlyBool(closedWorld)) { |
| return cls == closedWorld.commonElements.boolClass || |
| cls == commonElements.jsBoolClass; |
| } |
| if (containsOnlyInt(closedWorld)) { |
| return cls == closedWorld.commonElements.intClass || |
| cls == commonElements.jsIntClass || |
| cls == commonElements.jsPositiveIntClass || |
| cls == commonElements.jsUInt32Class || |
| cls == commonElements.jsUInt31Class; |
| } |
| return false; |
| } |
| |
| @override |
| 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; |
| other = TypeMask.nonForwardingMask(other); |
| // If other is union, delegate to UnionTypeMask.containsMask. |
| if (other is! FlatTypeMask) return other.containsMask(this, closedWorld); |
| // The other must be flat, so compare base and flags. |
| FlatTypeMask flatOther = other; |
| ClassEntity otherBase = flatOther.base; |
| // If other is exact, it only contains its base. |
| // TODO(herhut): Get rid of _isSingleImplementationOf. |
| if (flatOther.isExact) { |
| return (isExact && base == otherBase) || |
| _isSingleImplementationOf(otherBase, closedWorld); |
| } |
| // If other is subclass, this has to be subclass, as well. Unless |
| // flatOther.base covers all subtypes of this. Currently, we only |
| // consider object to behave that way. |
| // TODO(herhut): Add check whether flatOther.base is superclass of |
| // all subclasses of this.base. |
| if (flatOther.isSubclass) { |
| if (isSubtype) |
| return (otherBase == closedWorld.commonElements.objectClass); |
| return closedWorld.classHierarchy.isSubclassOf(base, otherBase); |
| } |
| assert(flatOther.isSubtype); |
| // Check whether this TypeMask satisfies otherBase's interface. |
| return satisfies(otherBase, closedWorld); |
| } |
| |
| @override |
| bool containsMask(TypeMask other, JClosedWorld closedWorld) { |
| return other.isInMask(this, closedWorld); |
| } |
| |
| @override |
| bool containsOnlyInt(JClosedWorld closedWorld) { |
| CommonElements commonElements = closedWorld.commonElements; |
| return base == commonElements.intClass || |
| base == commonElements.jsIntClass || |
| base == commonElements.jsPositiveIntClass || |
| base == commonElements.jsUInt31Class || |
| base == commonElements.jsUInt32Class; |
| } |
| |
| @override |
| bool containsOnlyNum(JClosedWorld closedWorld) { |
| return containsOnlyInt(closedWorld) || |
| base == closedWorld.commonElements.doubleClass || |
| base == closedWorld.commonElements.jsNumNotIntClass || |
| base == closedWorld.commonElements.numClass || |
| base == closedWorld.commonElements.jsNumberClass; |
| } |
| |
| @override |
| bool containsOnlyBool(JClosedWorld closedWorld) { |
| return base == closedWorld.commonElements.boolClass || |
| base == closedWorld.commonElements.jsBoolClass; |
| } |
| |
| @override |
| bool containsOnlyString(JClosedWorld closedWorld) { |
| return base == closedWorld.commonElements.stringClass || |
| base == closedWorld.commonElements.jsStringClass; |
| } |
| |
| @override |
| bool containsOnly(ClassEntity cls) { |
| return base == cls; |
| } |
| |
| @override |
| bool satisfies(ClassEntity cls, JClosedWorld closedWorld) { |
| if (isEmptyOrFlagged) return false; |
| if (closedWorld.classHierarchy.isSubtypeOf(base, cls)) return true; |
| return false; |
| } |
| |
| @override |
| 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) { |
| return closedWorld.classHierarchy.hasAnyStrictSubclass(base) |
| ? null |
| : base; |
| } else { |
| assert(isSubtype); |
| return null; |
| } |
| } |
| |
| @override |
| bool containsAll(JClosedWorld closedWorld) { |
| if (isEmptyOrFlagged || isExact) return false; |
| return identical(base, closedWorld.commonElements.objectClass); |
| } |
| |
| @override |
| TypeMask union(TypeMask other, CommonMasks domain) { |
| JClosedWorld closedWorld = domain._closedWorld; |
| assert(other != null); |
| assert(TypeMask.assertIsNormalized(this, closedWorld)); |
| assert(TypeMask.assertIsNormalized(other, closedWorld)); |
| 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, hasLateSentinel: hasLateSentinel); |
| } else if (flatOther.isEmptyOrFlagged) { |
| return withFlags( |
| isNullable: isNullable, hasLateSentinel: hasLateSentinel); |
| } else if (base == flatOther.base) { |
| return unionSame(flatOther, domain); |
| } else if (closedWorld.classHierarchy.isSubclassOf(flatOther.base, base)) { |
| return unionStrictSubclass(flatOther, domain); |
| } else if (closedWorld.classHierarchy.isSubclassOf(base, flatOther.base)) { |
| return flatOther.unionStrictSubclass(this, domain); |
| } else if (closedWorld.classHierarchy.isSubtypeOf(flatOther.base, base)) { |
| return unionStrictSubtype(flatOther, domain); |
| } else if (closedWorld.classHierarchy.isSubtypeOf(base, flatOther.base)) { |
| return flatOther.unionStrictSubtype(this, domain); |
| } else { |
| return UnionTypeMask._internal( |
| <FlatTypeMask>[withoutFlags(), flatOther.withoutFlags()], |
| isNullable: isNullable, hasLateSentinel: hasLateSentinel); |
| } |
| } |
| |
| TypeMask unionSame(FlatTypeMask other, CommonMasks domain) { |
| assert(base == other.base); |
| assert(TypeMask.assertIsNormalized(this, domain._closedWorld)); |
| assert(TypeMask.assertIsNormalized(other, domain._closedWorld)); |
| // The two masks share the base type, so we must chose the least |
| // constraining kind (the highest) of the two. If either one of |
| // the masks are nullable the result should be nullable too. |
| // As both masks are normalized, the result will be, too. |
| int combined = |
| (flags > other.flags) ? flags | other._mask : other.flags | _mask; |
| if (flags == combined) { |
| return this; |
| } else if (other.flags == combined) { |
| return other; |
| } else { |
| return FlatTypeMask.normalized(base, combined, domain); |
| } |
| } |
| |
| TypeMask unionStrictSubclass(FlatTypeMask other, CommonMasks domain) { |
| assert(base != other.base); |
| assert(domain._closedWorld.classHierarchy.isSubclassOf(other.base, base)); |
| assert(TypeMask.assertIsNormalized(this, domain._closedWorld)); |
| assert(TypeMask.assertIsNormalized(other, domain._closedWorld)); |
| int combined; |
| if ((isExact && other.isExact) || |
| base == domain.commonElements.objectClass) { |
| // Since the other mask is a subclass of this mask, we need the |
| // resulting union to be a subclass too. If either one of the |
| // masks are nullable the result should be nullable too. |
| combined = _computeFlagsRaw( |
| _FlatTypeMaskKind.subclass.index, _mask | other._mask); |
| } else { |
| // Both masks are at least subclass masks, so we pick the least |
| // constraining kind (the highest) of the two. If either one of |
| // the masks are nullable the result should be nullable too. |
| combined = |
| (flags > other.flags) ? flags | other._mask : other.flags | _mask; |
| } |
| // If we weaken the constraint on this type, we have to make sure that |
| // the result is normalized. |
| return flags != combined |
| ? FlatTypeMask.normalized(base, combined, domain) |
| : this; |
| } |
| |
| TypeMask unionStrictSubtype(FlatTypeMask other, CommonMasks domain) { |
| assert(base != other.base); |
| assert(!domain._closedWorld.classHierarchy.isSubclassOf(other.base, base)); |
| assert(domain._closedWorld.classHierarchy.isSubtypeOf(other.base, base)); |
| assert(TypeMask.assertIsNormalized(this, domain._closedWorld)); |
| assert(TypeMask.assertIsNormalized(other, domain._closedWorld)); |
| // Since the other mask is a subtype of this mask, we need the |
| // resulting union to be a subtype too. If either one of the masks |
| // are nullable the result should be nullable too. |
| int combined = |
| _computeFlagsRaw(_FlatTypeMaskKind.subtype.index, _mask | other._mask); |
| // We know there is at least one subtype, [other.base], so no need |
| // to normalize. |
| return flags != combined |
| ? FlatTypeMask.normalized(base, combined, domain) |
| : this; |
| } |
| |
| @override |
| TypeMask intersection(TypeMask other, CommonMasks domain) { |
| assert(other != null); |
| if (other is! FlatTypeMask) return other.intersection(this, domain); |
| assert(TypeMask.assertIsNormalized(this, domain._closedWorld)); |
| assert(TypeMask.assertIsNormalized(other, domain._closedWorld)); |
| FlatTypeMask flatOther = other; |
| |
| ClassEntity otherBase = flatOther.base; |
| |
| bool includeNull = isNullable && flatOther.isNullable; |
| bool includeLateSentinel = hasLateSentinel && flatOther.hasLateSentinel; |
| |
| if (isEmptyOrFlagged) { |
| return withFlags( |
| isNullable: includeNull, hasLateSentinel: includeLateSentinel); |
| } else if (flatOther.isEmptyOrFlagged) { |
| return other.withFlags( |
| isNullable: includeNull, hasLateSentinel: includeLateSentinel); |
| } |
| |
| SubclassResult result = domain._closedWorld.classHierarchy |
| .commonSubclasses(base, _classQuery, otherBase, flatOther._classQuery); |
| |
| switch (result.kind) { |
| case SubclassResultKind.EMPTY: |
| return includeNull |
| ? TypeMask.empty(hasLateSentinel: includeLateSentinel) |
| : TypeMask.nonNullEmpty(hasLateSentinel: includeLateSentinel); |
| case SubclassResultKind.EXACT1: |
| assert(isExact); |
| return withFlags( |
| isNullable: includeNull, hasLateSentinel: includeLateSentinel); |
| case SubclassResultKind.EXACT2: |
| assert(other.isExact); |
| return other.withFlags( |
| isNullable: includeNull, hasLateSentinel: includeLateSentinel); |
| case SubclassResultKind.SUBCLASS1: |
| assert(isSubclass); |
| return withFlags( |
| isNullable: includeNull, hasLateSentinel: includeLateSentinel); |
| case SubclassResultKind.SUBCLASS2: |
| assert(flatOther.isSubclass); |
| return other.withFlags( |
| isNullable: includeNull, hasLateSentinel: includeLateSentinel); |
| case SubclassResultKind.SUBTYPE1: |
| assert(isSubtype); |
| return withFlags( |
| isNullable: includeNull, hasLateSentinel: includeLateSentinel); |
| case SubclassResultKind.SUBTYPE2: |
| assert(flatOther.isSubtype); |
| return other.withFlags( |
| isNullable: includeNull, hasLateSentinel: includeLateSentinel); |
| case SubclassResultKind.SET: |
| default: |
| if (result.classes.isEmpty) { |
| 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, |
| 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, |
| includeLateSentinel: includeLateSentinel); |
| } |
| return UnionTypeMask._internal(masks, |
| isNullable: includeNull, hasLateSentinel: includeLateSentinel); |
| } |
| } |
| |
| @override |
| bool isDisjoint(TypeMask other, JClosedWorld closedWorld) { |
| if (other is! FlatTypeMask) return other.isDisjoint(this, closedWorld); |
| 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; |
| |
| if (isExact) return !flatOther.contains(base, closedWorld); |
| if (flatOther.isExact) return !contains(flatOther.base, closedWorld); |
| |
| // Normalization guarantees that isExact === !isSubclass && !isSubtype. |
| // Both are subclass or subtype masks, so if there is a subclass |
| // relationship, they are not disjoint. |
| if (closedWorld.classHierarchy.isSubclassOf(flatOther.base, base)) { |
| return false; |
| } |
| if (closedWorld.classHierarchy.isSubclassOf(base, flatOther.base)) { |
| return false; |
| } |
| |
| // Two different base classes have no common subclass unless one is a |
| // subclass of the other (checked above). |
| if (isSubclass && flatOther.isSubclass) return true; |
| |
| return _isDisjointHelper(this, flatOther, closedWorld); |
| } |
| |
| static bool _isDisjointHelper( |
| FlatTypeMask a, FlatTypeMask b, JClosedWorld closedWorld) { |
| if (!a.isSubclass && b.isSubclass) { |
| return _isDisjointHelper(b, a, closedWorld); |
| } |
| assert(a.isSubclass || a.isSubtype); |
| assert(b.isSubtype); |
| var elements = a.isSubclass |
| ? closedWorld.classHierarchy.strictSubclassesOf(a.base) |
| : closedWorld.classHierarchy.strictSubtypesOf(a.base); |
| for (var element in elements) { |
| if (closedWorld.classHierarchy.isSubtypeOf(element, b.base)) return false; |
| } |
| return true; |
| } |
| |
| TypeMask intersectionSame(FlatTypeMask other, CommonMasks domain) { |
| assert(base == other.base); |
| // The two masks share the base type, so we must chose the most |
| // constraining kind (the lowest) of the two. Only if both masks |
| // are nullable, will the result be nullable too. |
| // The result will be normalized, as the two inputs are normalized, too. |
| int combined = (flags < other.flags) |
| ? flags & (other._mask | ~_ALL_MASK) |
| : other.flags & (_mask | ~_ALL_MASK); |
| if (flags == combined) { |
| return this; |
| } else if (other.flags == combined) { |
| return other; |
| } else { |
| return FlatTypeMask.normalized(base, combined, domain); |
| } |
| } |
| |
| TypeMask intersectionStrictSubclass(FlatTypeMask other, CommonMasks domain) { |
| assert(base != other.base); |
| assert(domain._closedWorld.classHierarchy.isSubclassOf(other.base, base)); |
| // If this mask isn't at least a subclass mask, then the |
| // intersection with the other mask is empty. |
| if (isExact) return intersectionEmpty(other); |
| // Only the other mask puts constraints on the intersection mask, |
| // so base the combined flags on the other mask. Only if both |
| // masks are nullable, will the result be nullable too. |
| // The result is guaranteed to be normalized, as the other type |
| // was normalized. |
| int combined = other.flags & (_mask | ~_ALL_MASK); |
| if (other.flags == combined) { |
| return other; |
| } else { |
| return FlatTypeMask.normalized(other.base, combined, domain); |
| } |
| } |
| |
| TypeMask intersectionEmpty(FlatTypeMask other) { |
| bool isNullable = this.isNullable && other.isNullable; |
| bool hasLateSentinel = this.hasLateSentinel && other.hasLateSentinel; |
| return isNullable |
| ? TypeMask.empty(hasLateSentinel: hasLateSentinel) |
| : TypeMask.nonNullEmpty(hasLateSentinel: hasLateSentinel); |
| } |
| |
| @override |
| bool canHit(MemberEntity element, Name name, JClosedWorld closedWorld) { |
| CommonElements commonElements = closedWorld.commonElements; |
| assert(element.name == name.text); |
| if (isEmptyOrFlagged) { |
| return isNullable && |
| closedWorld.hasElementIn(commonElements.jsNullClass, name, element); |
| } |
| |
| ClassEntity other = element.enclosingClass; |
| if (other == commonElements.jsNullClass) { |
| return isNullable; |
| } else if (isExact) { |
| return closedWorld.hasElementIn(base, name, element); |
| } else if (isSubclass) { |
| return closedWorld.hasElementIn(base, name, element) || |
| closedWorld.classHierarchy.isSubclassOf(other, base) || |
| closedWorld.hasAnySubclassThatMixes(base, other); |
| } else { |
| assert(isSubtype); |
| bool result = closedWorld.hasElementIn(base, name, element) || |
| closedWorld.classHierarchy.isSubtypeOf(other, base) || |
| closedWorld.hasAnySubclassThatImplements(other, base) || |
| closedWorld.hasAnySubclassOfMixinUseThatImplements(other, base); |
| if (result) return true; |
| // If the class is used as a mixin, we have to check if the element |
| // can be hit from any of the mixin applications. |
| Iterable<ClassEntity> mixinUses = closedWorld.mixinUsesOf(base); |
| return mixinUses.any((mixinApplication) => |
| closedWorld.hasElementIn(mixinApplication, name, element) || |
| closedWorld.classHierarchy.isSubclassOf(other, mixinApplication) || |
| closedWorld.hasAnySubclassThatMixes(mixinApplication, other)); |
| } |
| } |
| |
| @override |
| bool needsNoSuchMethodHandling( |
| Selector selector, covariant JClosedWorld closedWorld) { |
| // A call on an empty type mask is either dead code, or a call on |
| // `null`. |
| if (isEmptyOrFlagged) return false; |
| // A call on an exact mask for an abstract class is dead code. |
| // TODO(johnniwinther): A type mask cannot be abstract. Remove the need |
| // for this noise (currently used for super-calls in inference and mirror |
| // usage). |
| if (isExact && base.isAbstract) return false; |
| |
| return closedWorld.needsNoSuchMethod(base, selector, _classQuery); |
| } |
| |
| @override |
| MemberEntity locateSingleMember(Selector selector, CommonMasks domain) { |
| if (isEmptyOrFlagged) return null; |
| JClosedWorld closedWorld = domain._closedWorld; |
| if (closedWorld.includesClosureCallInDomain(selector, this, domain)) |
| return null; |
| Iterable<MemberEntity> targets = |
| closedWorld.locateMembersInDomain(selector, this, domain); |
| if (targets.length != 1) return null; |
| MemberEntity result = targets.first; |
| ClassEntity enclosing = result.enclosingClass; |
| // We only return the found element if it is guaranteed to be implemented on |
| // all classes in the receiver type [this]. It could be found only in a |
| // subclass or in an inheritance-wise unrelated class in case of subtype |
| // selectors. |
| if (isSubtype) { |
| // if (closedWorld.isUsedAsMixin(enclosing)) { |
| if (closedWorld.everySubtypeIsSubclassOfOrMixinUseOf(base, enclosing)) { |
| return result; |
| } |
| //} |
| return null; |
| } else { |
| if (closedWorld.classHierarchy.isSubclassOf(base, enclosing)) { |
| return result; |
| } |
| if (closedWorld.isSubclassOfMixinUseOf(base, enclosing)) return result; |
| } |
| return null; |
| } |
| |
| @override |
| bool operator ==(var other) { |
| if (identical(this, other)) return true; |
| if (other is! FlatTypeMask) return false; |
| FlatTypeMask otherMask = other; |
| return (flags == otherMask.flags) && (base == otherMask.base); |
| } |
| |
| @override |
| int get hashCode { |
| return (base == null ? 0 : base.hashCode) + 31 * flags.hashCode; |
| } |
| |
| @override |
| String toString() { |
| StringBuffer buffer = StringBuffer('['); |
| 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}', |
| ], '|'); |
| buffer.write(']'); |
| return buffer.toString(); |
| } |
| } |