blob: c13eaaa0315c75a5fee9df05f69c3712c4b8bfaa [file] [log] [blame]
// 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;
}
}