| // Copyright (c) 2017, 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:kernel/ast.dart' as ir; |
| import '../common.dart'; |
| import '../constants/values.dart' show BoolConstantValue; |
| import '../elements/entities.dart'; |
| import '../elements/types.dart'; |
| import '../world_interfaces.dart'; |
| import 'abstract_value_domain.dart'; |
| import 'type_graph_nodes.dart'; |
| |
| /// Strategy for creating type information from members and parameters and type |
| /// information for nodes. |
| abstract class TypeSystemStrategy { |
| /// Creates [MemberTypeInformation] for [member]. |
| MemberTypeInformation createMemberTypeInformation( |
| AbstractValueDomain abstractValueDomain, MemberEntity member); |
| |
| /// Creates [ParameterTypeInformation] for [parameter]. |
| ParameterTypeInformation createParameterTypeInformation( |
| AbstractValueDomain abstractValueDomain, |
| Local parameter, |
| TypeSystem types); |
| |
| /// Calls [f] for each parameter in [function]. |
| void forEachParameter(FunctionEntity function, void f(Local parameter)); |
| |
| /// Returns whether [node] is valid as a general phi node. |
| bool checkPhiNode(ir.Node? node); |
| |
| /// Returns whether [node] is valid as a loop phi node. |
| bool checkLoopPhiNode(ir.Node node); |
| |
| /// Returns whether [node] is valid as a list allocation node. |
| bool checkListNode(ir.Node node); |
| |
| /// Returns whether [node] is valid as a set allocation node. |
| bool checkSetNode(ir.Node node); |
| |
| /// Returns whether [node] is valid as a map allocation node. |
| bool checkMapNode(ir.Node node); |
| |
| /// Returns whether [cls] is valid as a type mask base class. |
| bool checkClassEntity(ClassEntity cls); |
| } |
| |
| /// The class [SimpleInferrerVisitor] will use when working on types. |
| class TypeSystem { |
| final JClosedWorld _closedWorld; |
| final TypeSystemStrategy strategy; |
| |
| /// [parameterTypeInformations] and [memberTypeInformations] ordered by |
| /// creation time. This is used as the inference enqueueing order. |
| final List<TypeInformation> _orderedTypeInformations = <TypeInformation>[]; |
| |
| /// [ParameterTypeInformation]s for parameters. |
| final Map<Local, ParameterTypeInformation> parameterTypeInformations = |
| Map<Local, ParameterTypeInformation>(); |
| |
| /// [MemberTypeInformation]s for members. |
| final Map<MemberEntity, MemberTypeInformation> memberTypeInformations = |
| Map<MemberEntity, MemberTypeInformation>(); |
| |
| /// [ListTypeInformation] for allocated lists. |
| final Map<ir.TreeNode, ListTypeInformation> allocatedLists = |
| Map<ir.TreeNode, ListTypeInformation>(); |
| |
| /// [SetTypeInformation] for allocated Sets. |
| final Map<ir.TreeNode, SetTypeInformation> allocatedSets = |
| Map<ir.TreeNode, SetTypeInformation>(); |
| |
| /// [MapTypeInformation] for allocated Maps. |
| final Map<ir.TreeNode, TypeInformation> allocatedMaps = |
| Map<ir.TreeNode, TypeInformation>(); |
| |
| /// Closures found during the analysis. |
| final Set<TypeInformation> allocatedClosures = Set<TypeInformation>(); |
| |
| /// Cache of [ConcreteTypeInformation]. |
| final Map<AbstractValue, ConcreteTypeInformation> concreteTypes = {}; |
| |
| /// Cache of some primitive constant types. |
| final Map<Object, TypeInformation> primitiveConstantTypes = {}; |
| |
| /// List of [TypeInformation]s for calls inside method bodies. |
| final List<CallSiteTypeInformation> allocatedCalls = |
| <CallSiteTypeInformation>[]; |
| |
| /// List of [TypeInformation]s allocated inside method bodies (narrowing, |
| /// phis, and containers). |
| final List<TypeInformation> allocatedTypes = <TypeInformation>[]; |
| |
| /// [parameterTypeInformations] and [memberTypeInformations] ordered by |
| /// creation time. This is used as the inference enqueueing order. |
| Iterable<TypeInformation> get orderedTypeInformations => |
| _orderedTypeInformations; |
| |
| Iterable<TypeInformation> get allTypes => [ |
| parameterTypeInformations.values, |
| memberTypeInformations.values, |
| allocatedLists.values, |
| allocatedSets.values, |
| allocatedMaps.values, |
| allocatedClosures, |
| concreteTypes.values, |
| primitiveConstantTypes.values, |
| allocatedCalls, |
| allocatedTypes, |
| ].expand((x) => x); |
| |
| TypeSystem(this._closedWorld, this.strategy); |
| |
| AbstractValueDomain get _abstractValueDomain => |
| _closedWorld.abstractValueDomain; |
| |
| /// Used to group [TypeInformation] nodes by the element that triggered their |
| /// creation. |
| MemberTypeInformation? _currentMember = null; |
| MemberTypeInformation? get currentMember => _currentMember; |
| |
| void withMember(MemberEntity element, void action()) { |
| assert(_currentMember == null, |
| failedAt(element, "Already constructing graph for $_currentMember.")); |
| _currentMember = getInferredTypeOfMember(element); |
| action(); |
| _currentMember = null; |
| } |
| |
| late final TypeInformation nullType = |
| getConcreteTypeFor(_abstractValueDomain.nullType); |
| |
| late final TypeInformation intType = |
| getConcreteTypeFor(_abstractValueDomain.intType); |
| |
| late final TypeInformation uint32Type = |
| getConcreteTypeFor(_abstractValueDomain.uint32Type); |
| |
| late final TypeInformation uint31Type = |
| getConcreteTypeFor(_abstractValueDomain.uint31Type); |
| |
| late final TypeInformation positiveIntType = |
| getConcreteTypeFor(_abstractValueDomain.positiveIntType); |
| |
| late final TypeInformation numType = |
| getConcreteTypeFor(_abstractValueDomain.numType); |
| |
| late final TypeInformation boolType = |
| getConcreteTypeFor(_abstractValueDomain.boolType); |
| |
| late final TypeInformation functionType = |
| getConcreteTypeFor(_abstractValueDomain.functionType); |
| |
| late final TypeInformation listType = |
| getConcreteTypeFor(_abstractValueDomain.listType); |
| |
| late final TypeInformation constListType = |
| getConcreteTypeFor(_abstractValueDomain.constListType); |
| |
| late final TypeInformation fixedListType = |
| getConcreteTypeFor(_abstractValueDomain.fixedListType); |
| |
| late final TypeInformation growableListType = |
| getConcreteTypeFor(_abstractValueDomain.growableListType); |
| |
| late final TypeInformation mutableArrayType = |
| getConcreteTypeFor(_abstractValueDomain.mutableArrayType); |
| |
| late final TypeInformation setType = |
| getConcreteTypeFor(_abstractValueDomain.setType); |
| |
| late final TypeInformation constSetType = |
| getConcreteTypeFor(_abstractValueDomain.constSetType); |
| |
| late final TypeInformation mapType = |
| getConcreteTypeFor(_abstractValueDomain.mapType); |
| |
| late final TypeInformation constMapType = |
| getConcreteTypeFor(_abstractValueDomain.constMapType); |
| |
| late final TypeInformation stringType = |
| getConcreteTypeFor(_abstractValueDomain.stringType); |
| |
| late final TypeInformation typeType = |
| getConcreteTypeFor(_abstractValueDomain.typeType); |
| |
| late final TypeInformation dynamicType = |
| getConcreteTypeFor(_abstractValueDomain.dynamicType); |
| |
| // Subtype of Future returned by async methods. |
| late final TypeInformation asyncFutureType = |
| getConcreteTypeFor(_abstractValueDomain.asyncFutureType); |
| |
| late final TypeInformation syncStarIterableType = |
| getConcreteTypeFor(_abstractValueDomain.syncStarIterableType); |
| |
| late final TypeInformation asyncStarStreamType = |
| getConcreteTypeFor(_abstractValueDomain.asyncStarStreamType); |
| |
| late final TypeInformation lateSentinelType = |
| getConcreteTypeFor(_abstractValueDomain.lateSentinelType); |
| |
| late final TypeInformation nonNullEmptyType = |
| getConcreteTypeFor(_abstractValueDomain.emptyType); |
| |
| TypeInformation stringLiteralType(String value) { |
| return StringLiteralTypeInformation( |
| _abstractValueDomain, value, _abstractValueDomain.stringType); |
| } |
| |
| TypeInformation boolLiteralType(bool value) { |
| return primitiveConstantTypes[value] ??= _boolLiteralType(value); |
| } |
| |
| TypeInformation _boolLiteralType(bool value) { |
| AbstractValue abstractValue = _abstractValueDomain |
| .computeAbstractValueForConstant(BoolConstantValue(value)); |
| return BoolLiteralTypeInformation( |
| _abstractValueDomain, value, abstractValue); |
| } |
| |
| bool isLiteralTrue(TypeInformation info) { |
| return info is BoolLiteralTypeInformation && info.value == true; |
| } |
| |
| bool isLiteralFalse(TypeInformation info) { |
| return info is BoolLiteralTypeInformation && info.value == false; |
| } |
| |
| /// Returns the least upper bound between [firstType] and |
| /// [secondType]. |
| TypeInformation computeLUB( |
| TypeInformation? firstType, TypeInformation secondType) { |
| if (firstType == null) return secondType; |
| if (firstType == secondType) return firstType; |
| if (firstType == nonNullEmptyType) return secondType; |
| if (secondType == nonNullEmptyType) return firstType; |
| if (firstType == dynamicType || secondType == dynamicType) { |
| return dynamicType; |
| } |
| return getConcreteTypeFor( |
| _abstractValueDomain.union(firstType.type, secondType.type)); |
| } |
| |
| /// Returns `true` if `selector` should be updated to reflect the new |
| /// `receiverType`. |
| bool selectorNeedsUpdate(TypeInformation info, AbstractValue mask) { |
| return info.type != mask; |
| } |
| |
| bool _isNonNullNarrow(TypeInformation type) => |
| type is NarrowTypeInformation && |
| _abstractValueDomain.isNull(type.typeAnnotation).isDefinitelyFalse; |
| |
| /// Returns the intersection between [type] and [annotation]. |
| /// |
| /// [isCast] indicates whether narrowing comes from a cast or parameter check |
| /// rather than an 'is' test. (In legacy semantics these differ on whether |
| /// `null` is accepted). |
| /// |
| /// 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 excludeLateSentinel = false}) { |
| AbstractValue inferredType = type.type; |
| |
| TypeInformation _excludeLateSentinel() { |
| if (!excludeLateSentinel) return type; |
| final newType = NarrowTypeInformation( |
| _abstractValueDomain, type, _abstractValueDomain.dynamicType); |
| allocatedTypes.add(newType); |
| return newType; |
| } |
| |
| // Avoid refining an input with an exact type, since we are almost always |
| // adding a narrowing to a subtype of the same class or a superclass. |
| if (_abstractValueDomain.isExact(inferredType).isDefinitelyTrue) { |
| return _excludeLateSentinel(); |
| } |
| |
| AbstractValue narrowing = _abstractValueDomain |
| .createFromStaticType(annotation, nullable: isCast) |
| .abstractValue; |
| |
| if (excludeNull) { |
| narrowing = _abstractValueDomain.excludeNull(narrowing); |
| } |
| |
| if (!excludeLateSentinel) { |
| // Narrowing is an intersection of [AbstractValue]s. Unless |
| // [excludeLateSentinel] is `true`, we include the late sentinel here so |
| // that it is preserved by the intersection. |
| narrowing = _abstractValueDomain.includeLateSentinel(narrowing); |
| } |
| |
| if (_abstractValueDomain.containsAll(narrowing).isPotentiallyTrue) { |
| // Top, or non-nullable Top. |
| if (_abstractValueDomain.isNull(narrowing).isPotentiallyTrue) { |
| return _excludeLateSentinel(); |
| } |
| // If the input is already narrowed to be non-null, there is no value in |
| // adding another non-null narrowing node. |
| if (_isNonNullNarrow(type)) return _excludeLateSentinel(); |
| } |
| |
| TypeInformation newType = |
| NarrowTypeInformation(_abstractValueDomain, type, narrowing); |
| allocatedTypes.add(newType); |
| return newType; |
| } |
| |
| ParameterTypeInformation getInferredTypeOfParameter(Local parameter) { |
| return parameterTypeInformations.putIfAbsent(parameter, () { |
| ParameterTypeInformation typeInformation = |
| strategy.createParameterTypeInformation( |
| _abstractValueDomain, parameter, this); |
| _orderedTypeInformations.add(typeInformation); |
| return typeInformation; |
| }); |
| } |
| |
| void forEachParameterType( |
| void f(Local parameter, ParameterTypeInformation typeInformation)) { |
| parameterTypeInformations.forEach(f); |
| } |
| |
| MemberTypeInformation getInferredTypeOfMember(MemberEntity member) { |
| assert(!member.isAbstract, |
| failedAt(member, "Unexpected abstract member $member.")); |
| return memberTypeInformations[member] ??= _getInferredTypeOfMember(member); |
| } |
| |
| void forEachMemberType( |
| void f(MemberEntity member, MemberTypeInformation typeInformation)) { |
| memberTypeInformations.forEach(f); |
| } |
| |
| MemberTypeInformation _getInferredTypeOfMember(MemberEntity member) { |
| MemberTypeInformation typeInformation = |
| strategy.createMemberTypeInformation(_abstractValueDomain, member); |
| _orderedTypeInformations.add(typeInformation); |
| return typeInformation; |
| } |
| |
| /// Returns the internal inferrer representation for [mask]. |
| ConcreteTypeInformation getConcreteTypeFor(AbstractValue mask) { |
| assert((mask as dynamic) != null); // TODO(48820): Remove when sound. |
| return concreteTypes.putIfAbsent(mask, () { |
| return ConcreteTypeInformation(mask); |
| }); |
| } |
| |
| String getInferredSignatureOfMethod(FunctionEntity function) { |
| ElementTypeInformation info = getInferredTypeOfMember(function); |
| var res = ""; |
| strategy.forEachParameter(function, (Local parameter) { |
| TypeInformation type = getInferredTypeOfParameter(parameter); |
| res += "${res.isEmpty ? '(' : ', '}${type.type} ${parameter.name}"; |
| }); |
| res += ") -> ${info.type}"; |
| return res; |
| } |
| |
| TypeInformation nonNullSubtype(ClassEntity cls) { |
| assert(strategy.checkClassEntity(cls)); |
| return getConcreteTypeFor(_abstractValueDomain.createNonNullSubtype(cls)); |
| } |
| |
| TypeInformation nonNullSubclass(ClassEntity cls) { |
| assert(strategy.checkClassEntity(cls)); |
| return getConcreteTypeFor(_abstractValueDomain.createNonNullSubclass(cls)); |
| } |
| |
| TypeInformation nonNullExact(ClassEntity cls) { |
| assert(strategy.checkClassEntity(cls)); |
| return getConcreteTypeFor(_abstractValueDomain.createNonNullExact(cls)); |
| } |
| |
| TypeInformation nonNullEmpty() { |
| return nonNullEmptyType; |
| } |
| |
| bool isNull(TypeInformation type) { |
| return type == nullType; |
| } |
| |
| TypeInformation allocateList(TypeInformation type, ir.TreeNode node, |
| MemberEntity enclosing, TypeInformation elementType, |
| [int? length]) { |
| assert(strategy.checkListNode(node)); |
| final typedDataClass = _closedWorld.commonElements.typedDataClass; |
| bool isTypedArray = |
| _closedWorld.classHierarchy.isInstantiated(typedDataClass) && |
| _abstractValueDomain |
| .isInstanceOfOrNull(type.type, typedDataClass) |
| .isDefinitelyTrue; |
| bool isConst = (type.type == _abstractValueDomain.constListType); |
| bool isFixed = (type.type == _abstractValueDomain.fixedListType) || |
| isConst || |
| isTypedArray; |
| bool isElementInferred = isConst || isTypedArray; |
| |
| final inferredLength = isFixed ? length : null; |
| final elementTypeMask = |
| isElementInferred ? elementType.type : dynamicType.type; |
| AbstractValue mask = _abstractValueDomain.createContainerValue( |
| type.type, node, enclosing, elementTypeMask, inferredLength); |
| ElementInContainerTypeInformation element = |
| ElementInContainerTypeInformation( |
| _abstractValueDomain, currentMember, elementType); |
| element.inferred = isElementInferred; |
| |
| allocatedTypes.add(element); |
| return allocatedLists[node] = ListTypeInformation( |
| _abstractValueDomain, currentMember, mask, element, length); |
| } |
| |
| /// Creates a [TypeInformation] object either for the closurization of a |
| /// static or top-level method [element] used as a function constant or for |
| /// the synthesized 'call' method [element] created for a local function. |
| TypeInformation allocateClosure(FunctionEntity element) { |
| TypeInformation result = |
| ClosureTypeInformation(_abstractValueDomain, currentMember, element); |
| allocatedClosures.add(result); |
| return result; |
| } |
| |
| TypeInformation allocateSet(TypeInformation type, ir.TreeNode node, |
| MemberEntity enclosing, TypeInformation elementType) { |
| assert(strategy.checkSetNode(node)); |
| bool isConst = type.type == _abstractValueDomain.constSetType; |
| |
| AbstractValue elementTypeMask = |
| isConst ? elementType.type : dynamicType.type; |
| AbstractValue mask = _abstractValueDomain.createSetValue( |
| type.type, node, enclosing, elementTypeMask); |
| ElementInSetTypeInformation element = ElementInSetTypeInformation( |
| _abstractValueDomain, currentMember, elementType); |
| element.inferred = isConst; |
| |
| allocatedTypes.add(element); |
| return allocatedSets[node] = |
| SetTypeInformation(currentMember, mask, element); |
| } |
| |
| TypeInformation allocateMap( |
| ConcreteTypeInformation type, |
| ir.TreeNode node, |
| MemberEntity element, |
| List<TypeInformation> keyTypes, |
| List<TypeInformation> valueTypes) { |
| assert(strategy.checkMapNode(node)); |
| assert(keyTypes.length == valueTypes.length); |
| bool isFixed = (type.type == _abstractValueDomain.constMapType); |
| |
| PhiElementTypeInformation? keyType, valueType; |
| for (int i = 0; i < keyTypes.length; ++i) { |
| final typeForKey = keyTypes[i]; |
| keyType = keyType == null |
| ? allocatePhi(null, null, typeForKey, isTry: false) |
| : addPhiInput(null, keyType, typeForKey); |
| |
| final typeForValue = valueTypes[i]; |
| valueType = valueType == null |
| ? allocatePhi(null, null, typeForValue, isTry: false) |
| : addPhiInput(null, valueType, typeForValue); |
| } |
| |
| final simplifiedKeyType = |
| keyType == null ? nonNullEmpty() : simplifyPhi(null, null, keyType); |
| final simplifiedValueType = |
| valueType == null ? nonNullEmpty() : simplifyPhi(null, null, valueType); |
| |
| AbstractValue keyTypeMask, valueTypeMask; |
| if (isFixed) { |
| keyTypeMask = simplifiedKeyType.type; |
| valueTypeMask = simplifiedValueType.type; |
| } else { |
| keyTypeMask = valueTypeMask = dynamicType.type; |
| } |
| AbstractValue mask = _abstractValueDomain.createMapValue( |
| type.type, node, element, keyTypeMask, valueTypeMask); |
| |
| final keyTypeInfo = KeyInMapTypeInformation( |
| _abstractValueDomain, currentMember, simplifiedKeyType); |
| final valueTypeInfo = ValueInMapTypeInformation( |
| _abstractValueDomain, currentMember, simplifiedValueType); |
| allocatedTypes.add(keyTypeInfo); |
| allocatedTypes.add(valueTypeInfo); |
| |
| MapTypeInformation map = |
| MapTypeInformation(currentMember, mask, keyTypeInfo, valueTypeInfo); |
| |
| for (int i = 0; i < keyTypes.length; ++i) { |
| final newType = map.addEntryInput( |
| _abstractValueDomain, keyTypes[i], valueTypes[i], true); |
| if (newType != null) allocatedTypes.add(newType); |
| } |
| |
| // Shortcut: If we already have a first approximation of the key/value type, |
| // start propagating it early. |
| if (isFixed) map.markAsInferred(); |
| |
| allocatedMaps[node] = map; |
| return map; |
| } |
| |
| AbstractValue newTypedSelector(TypeInformation info, AbstractValue mask) { |
| // Only type the selector if [info] is concrete, because the other |
| // kinds of [TypeInformation] have the empty type at this point of |
| // analysis. |
| return info.isConcrete ? info.type : mask; |
| } |
| |
| /// Returns a new type that unions [firstInput] and [secondInput]. |
| TypeInformation allocateDiamondPhi( |
| TypeInformation firstInput, TypeInformation secondInput) { |
| PhiElementTypeInformation result = PhiElementTypeInformation( |
| _abstractValueDomain, currentMember, null, null, |
| isTry: false); |
| result.addInput(firstInput); |
| result.addInput(secondInput); |
| allocatedTypes.add(result); |
| return result; |
| } |
| |
| PhiElementTypeInformation _addPhi( |
| ir.Node? node, Local? variable, TypeInformation inputType, bool isTry) { |
| PhiElementTypeInformation result = PhiElementTypeInformation( |
| _abstractValueDomain, currentMember, node, variable, |
| isTry: isTry); |
| allocatedTypes.add(result); |
| result.addInput(inputType); |
| return result; |
| } |
| |
| /// Returns a new type for holding the potential types of [element]. |
| /// [inputType] is the first incoming type of the phi. |
| PhiElementTypeInformation allocatePhi( |
| ir.Node? node, Local? variable, TypeInformation inputType, |
| {required bool isTry}) { |
| assert(strategy.checkPhiNode(node)); |
| // Check if [inputType] is a phi for a local updated in |
| // the try/catch block [node]. If it is, no need to allocate a new |
| // phi. |
| if (inputType is PhiElementTypeInformation && |
| inputType.branchNode == node && |
| inputType.isTry) { |
| return inputType; |
| } |
| return _addPhi(node, variable, inputType, isTry); |
| } |
| |
| /// Returns a new type for holding the potential types of [element]. |
| /// [inputType] is the first incoming type of the phi. [allocateLoopPhi] |
| /// only differs from [allocatePhi] in that it allows the underlying |
| /// implementation of [TypeSystem] to differentiate Phi nodes due to loops |
| /// from other merging uses. |
| PhiElementTypeInformation allocateLoopPhi( |
| ir.Node node, Local variable, TypeInformation inputType, |
| {required bool isTry}) { |
| assert(strategy.checkLoopPhiNode(node)); |
| return _addPhi(node, variable, inputType, isTry); |
| } |
| |
| /// Simplifies the phi representing [element] and of the type |
| /// [phiType]. For example, if this phi has one incoming input, an |
| /// implementation of this method could just return that incoming |
| /// input type. |
| TypeInformation simplifyPhi( |
| ir.Node? node, Local? variable, PhiElementTypeInformation phiType) { |
| assert(phiType.branchNode == node); |
| if (phiType.inputs.length == 1) return phiType.inputs.first; |
| return phiType; |
| } |
| |
| /// Adds [newType] as an input of [phiType]. |
| PhiElementTypeInformation addPhiInput(Local? variable, |
| PhiElementTypeInformation phiType, TypeInformation newType) { |
| phiType.addInput(newType); |
| return phiType; |
| } |
| |
| AbstractValue computeTypeMask(Iterable<TypeInformation> assignments) { |
| return joinTypeMasks(assignments.map((e) => e.type)); |
| } |
| |
| AbstractValue joinTypeMasks(Iterable<AbstractValue> masks) { |
| 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 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 == internalTopType` so we can continue |
| // to track the non-nullable and late sentinel bits. |
| if (_abstractValueDomain.containsAll(mask).isPotentiallyTrue) { |
| isTopIgnoringFlags = true; |
| } |
| if (_abstractValueDomain.isNull(mask).isPotentiallyTrue) { |
| mayBeNull = true; |
| } |
| if (_abstractValueDomain.isLateSentinel(mask).isPotentiallyTrue) { |
| mayBeLateSentinel = true; |
| } |
| if (isTopIgnoringFlags && mayBeNull && mayBeLateSentinel) return topType; |
| list.add(mask); |
| } |
| |
| AbstractValue? newType; |
| for (AbstractValue mask in list) { |
| newType = |
| newType == null ? mask : _abstractValueDomain.union(newType, mask); |
| // Likewise - stop early if we already reach dynamic. |
| if (_abstractValueDomain.containsAll(newType).isPotentiallyTrue) { |
| isTopIgnoringFlags = true; |
| } |
| if (_abstractValueDomain.isNull(newType).isPotentiallyTrue) { |
| mayBeNull = true; |
| } |
| if (_abstractValueDomain.isLateSentinel(newType).isPotentiallyTrue) { |
| mayBeLateSentinel = true; |
| } |
| if (isTopIgnoringFlags && mayBeNull && mayBeLateSentinel) return topType; |
| } |
| |
| return newType ?? _abstractValueDomain.emptyType; |
| } |
| } |