blob: eb3c52afdf49506fce7a9a18a5df47233b95879d [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:front_end/src/api_unstable/dart2js.dart'
show operatorFromString;
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/class_hierarchy.dart' as ir;
import 'package:kernel/type_algebra.dart' as ir;
import 'package:kernel/type_environment.dart' as ir;
import '../common/names.dart';
import '../util/util.dart';
import 'runtime_type_analysis.dart';
import 'scope.dart';
import 'static_type_base.dart';
import 'static_type_cache.dart';
import 'class_relation.dart';
ClassRelation computeClassRelationFromType(ir.DartType type) {
if (type is ThisInterfaceType) {
return ClassRelation.thisExpression;
} else if (type is ExactInterfaceType) {
return ClassRelation.exact;
} else {
return ClassRelation.subtype;
}
}
class StaticTypeCacheImpl implements ir.StaticTypeCache {
final Map<ir.Expression, ir.DartType> _expressionTypes = {};
final Map<ir.ForInStatement, ir.DartType> _forInIteratorTypes = {};
@override
ir.DartType getExpressionType(
ir.Expression node, ir.StaticTypeContext context) {
return _expressionTypes[node] ??= node.getStaticTypeInternal(context);
}
@override
ir.DartType getForInIteratorType(
ir.ForInStatement node, ir.StaticTypeContext context) {
return _forInIteratorTypes[node] ??= node.getElementTypeInternal(context);
}
@override
ir.DartType getForInElementType(
ir.ForInStatement node, ir.StaticTypeContext context) {
throw UnsupportedError('StaticTypeCacheImpl.getForInElementType');
}
}
/// Visitor that computes and caches the static type of expression while
/// visiting the full tree at expression level.
///
/// To ensure that the traversal only visits and computes the expression type
/// for each expression once, this class performs the traversal explicitly and
/// adds 'handleX' hooks for subclasses to handle individual expressions using
/// the readily compute static types of subexpressions.
abstract class StaticTypeVisitor extends StaticTypeBase {
final StaticTypeCacheImpl _staticTypeCache;
Map<ir.Expression, TypeMap>? typeMapsForTesting;
// TODO(johnniwinther): Change the key to `InstanceGet` when the old method
// invocation encoding is no longer used.
final Map<ir.Expression, RuntimeTypeUseData> _pendingRuntimeTypeUseData = {};
final ir.ClassHierarchy hierarchy;
ThisInterfaceType? _thisType;
ir.Library? _currentLibrary;
StaticTypeVisitor(
ir.TypeEnvironment typeEnvironment, this.hierarchy, this._staticTypeCache)
: super(typeEnvironment);
StaticTypeCache getStaticTypeCache() {
return StaticTypeCache(_staticTypeCache._expressionTypes,
_staticTypeCache._forInIteratorTypes);
}
/// If `true`, the effect of executing assert statements is taken into account
/// when computing the static type.
bool get useAsserts;
/// If `true`, the static type of an effectively final variable is inferred
/// from the static type of its initializer.
bool get inferEffectivelyFinalVariableTypes;
VariableScopeModel get variableScopeModel;
@override
ThisInterfaceType get thisType {
return _thisType!;
}
void set thisType(ThisInterfaceType? value) {
assert(value == null || _thisType == null);
_thisType = value;
}
ir.Library get currentLibrary {
return _currentLibrary!;
}
void set currentLibrary(ir.Library? value) {
assert(value == null || _currentLibrary == null);
_currentLibrary = value;
}
bool completes(ir.DartType? type) => type != const DoesNotCompleteType();
Set<ir.VariableDeclaration>? _currentVariables;
final Set<ir.VariableDeclaration> _invalidatedVariables = {};
TypeMap? _typeMapBase = const TypeMap();
TypeMap? _typeMapWhenTrue;
TypeMap? _typeMapWhenFalse;
/// Joins [_typeMapWhenTrue] and [_typeMapWhenFalse] and stores the result
/// in [_typeMapBase].
void _flattenTypeMap() {
if (_typeMapBase == null) {
_typeMapBase = _typeMapWhenTrue!.join(_typeMapWhenFalse!);
_typeMapWhenTrue = _typeMapWhenFalse = null;
}
}
/// Returns the local variable type promotions for when the boolean value of
/// the most recent node is not taken into account.
TypeMap get typeMap {
_flattenTypeMap();
return _typeMapBase!;
}
/// Sets the local variable type promotions for when the boolean value of
/// the most recent node is not taken into account.
void set typeMap(TypeMap value) {
_typeMapBase = value;
_typeMapWhenTrue = _typeMapWhenFalse = null;
}
/// Returns the local variable type promotions for when the boolean value of
/// the most recent node is `true`.
TypeMap get typeMapWhenTrue => _typeMapWhenTrue ?? _typeMapBase!;
/// Sets the local variable type promotions for when the boolean value of
/// the most recent node is `true`.
void set typeMapWhenTrue(TypeMap value) {
_typeMapWhenTrue = value;
_typeMapBase = null;
}
/// Returns the local variable type promotions for when the boolean value of
/// the most recent node is `false`.
TypeMap get typeMapWhenFalse => _typeMapWhenFalse ?? _typeMapBase!;
/// Sets the local variable type promotions for when the boolean value of
/// the most recent node is `false`.
void set typeMapWhenFalse(TypeMap value) {
_typeMapWhenFalse = value;
_typeMapBase = null;
}
@override
ir.DartType visitComponent(ir.Component node) {
visitNodes(node.libraries);
return const ir.VoidType();
}
@override
ir.DartType visitLibrary(ir.Library node) {
visitNodes(node.classes);
visitNodes(node.procedures);
visitNodes(node.fields);
return const ir.VoidType();
}
@override
ir.DartType visitClass(ir.Class node) {
visitNodes(node.constructors);
visitNodes(node.procedures);
visitNodes(node.fields);
return const ir.VoidType();
}
ir.InterfaceType? getInterfaceTypeOf(ir.DartType type) {
while (type is ir.TypeParameterType) {
type = type.parameter.bound;
}
if (type is ir.InterfaceType) {
return type;
} else if (type is ir.NullType) {
return typeEnvironment.coreTypes.deprecatedNullType;
}
return null;
}
/// Returns the static type of the expression as an instantiation of
/// [superclass].
///
/// Should only be used on code compiled in strong mode, as this method
/// assumes the IR is strongly typed.
///
/// This method furthermore assumes that the type of the expression actually
/// is a subtype of (some instantiation of) the given [superclass].
/// If this is not the case the raw type of [superclass] is returned.
///
/// This method is derived from `ir.Expression.getStaticTypeAsInstanceOf`.
ir.InterfaceType getTypeAsInstanceOf(ir.DartType type, ir.Class superclass) {
// This method assumes the program is correctly typed, so if the superclass
// is not generic, we can just return its raw type without computing the
// type of this expression. It also ensures that all types are considered
// subtypes of Object (not just interface types), and function types are
// considered subtypes of Function.
if (superclass.typeParameters.isEmpty) {
return typeEnvironment.coreTypes
.rawType(superclass, currentLibrary.nonNullable);
}
while (type is ir.TypeParameterType) {
type = type.parameter.bound;
}
if (type is ir.NullType ||
type is ir.NeverType &&
(type.nullability == ir.Nullability.nonNullable ||
type.nullability == ir.Nullability.legacy)) {
return typeEnvironment.coreTypes
.bottomInterfaceType(superclass, currentLibrary.nullable);
}
if (type is ir.InterfaceType) {
ir.InterfaceType? upcastType = typeEnvironment.getTypeAsInstanceOf(
type, superclass, currentLibrary, typeEnvironment.coreTypes);
if (upcastType != null) return upcastType;
}
// TODO(johnniwinther): Should we assert that this doesn't happen?
return typeEnvironment.coreTypes
.rawType(superclass, currentLibrary.nonNullable);
}
ir.Member? _resolveDynamicTarget(ir.DartType receiverType, ir.Name name) {
if (receiverType is ir.InterfaceType) {
return hierarchy.getInterfaceMember(receiverType.classNode, name);
}
return null;
}
ir.DartType _computeInstanceGetType(
ir.DartType receiverType, ir.Member interfaceTarget) {
ir.Class superclass = interfaceTarget.enclosingClass!;
final castType = getTypeAsInstanceOf(receiverType, superclass);
return ir.Substitution.fromInterfaceType(castType)
.substituteType(interfaceTarget.getterType);
}
/// Replaces [original] with [replacement] in the AST and removes cached
/// expression type information for [original].
void _replaceExpression(ir.Expression original, ir.Expression replacement) {
original.replaceWith(replacement);
_staticTypeCache._expressionTypes.remove(original);
}
void handleDynamicGet(ir.Expression node, ir.DartType receiverType,
ir.Name name, ir.DartType resultType) {}
void handleInstanceGet(ir.Expression node, ir.DartType receiverType,
ir.Member interfaceTarget, ir.DartType resultType) {}
// TODO(johnniwinther): Change [node] to `InstanceGet` when the old method
// invocation encoding is no longer used.
void handleRuntimeTypeUse(ir.Expression node, RuntimeTypeUseKind kind,
ir.DartType? receiverType, ir.DartType? argumentType) {}
void handleRuntimeTypeGet(ir.DartType receiverType, ir.Expression node) {
RuntimeTypeUseData data =
computeRuntimeTypeUse(_pendingRuntimeTypeUseData, node);
if (data.leftRuntimeTypeExpression == node) {
// [node] is the left (or single) occurrence of `.runtimeType` so we
// can set the static type of the receiver expression.
data.receiverType = receiverType;
} else {
// [node] is the right occurrence of `.runtimeType` so we
// can set the static type of the argument expression.
assert(data.rightRuntimeTypeExpression == node,
"Unexpected RuntimeTypeUseData for $node: $data");
data.argumentType = receiverType;
}
if (data.isComplete) {
/// We now have all need static types so we can remove the data from
/// the cache and handle the runtime type use.
_pendingRuntimeTypeUseData.remove(data.leftRuntimeTypeExpression);
if (data.rightRuntimeTypeExpression != null) {
_pendingRuntimeTypeUseData.remove(data.rightRuntimeTypeExpression);
}
handleRuntimeTypeUse(
node, data.kind, data.receiverType, data.argumentType);
}
}
@override
ir.DartType visitDynamicGet(ir.DynamicGet node) {
ir.DartType receiverType = visitNode(node.receiver);
ir.DartType resultType = super.visitDynamicGet(node);
ir.Member? interfaceTarget = _resolveDynamicTarget(receiverType, node.name);
if (interfaceTarget != null) {
resultType = _computeInstanceGetType(receiverType, interfaceTarget);
ir.InstanceGet instanceGet = ir.InstanceGet(
ir.InstanceAccessKind.Instance, node.receiver, node.name,
interfaceTarget: interfaceTarget, resultType: resultType)
..fileOffset = node.fileOffset;
_replaceExpression(node, instanceGet);
handleInstanceGet(instanceGet, receiverType, interfaceTarget, resultType);
} else if (node.name == ir.Name.callName &&
(receiverType is ir.FunctionType ||
(receiverType is ir.InterfaceType &&
receiverType.classNode == typeEnvironment.functionClass))) {
ir.FunctionTearOff functionTearOff = ir.FunctionTearOff(node.receiver)
..fileOffset = node.fileOffset;
_replaceExpression(node, functionTearOff);
handleDynamicGet(
functionTearOff, receiverType, ir.Name.callName, resultType);
resultType = receiverType;
} else {
handleDynamicGet(node, receiverType, node.name, resultType);
}
if (node.name.text == Identifiers.runtimeType_) {
// This handles `runtimeType` access on `Never`.
handleRuntimeTypeGet(receiverType, node);
}
return resultType;
}
@override
ir.DartType visitInstanceGet(ir.InstanceGet node) {
ir.DartType receiverType = visitNode(node.receiver);
// We compute the function type instead of reading it of [node] since the
// receiver and argument types might have improved through inference of
// effectively final variable types and type promotion.
ir.DartType resultType =
_computeInstanceGetType(receiverType, node.interfaceTarget);
node.resultType = resultType;
receiverType = _narrowInstanceReceiver(node.interfaceTarget, receiverType);
handleInstanceGet(node, receiverType, node.interfaceTarget, resultType);
if (node.name.text == Identifiers.runtimeType_) {
// This handles `runtimeType` access on non-Never types, like in
// `(throw 'foo').runtimeType`.
handleRuntimeTypeGet(receiverType, node);
}
return resultType;
}
@override
ir.DartType visitInstanceTearOff(ir.InstanceTearOff node) {
ir.DartType receiverType = visitNode(node.receiver);
// We compute the function type instead of reading it of [node] since the
// receiver and argument types might have improved through inference of
// effectively final variable types and type promotion.
ir.DartType resultType =
_computeInstanceGetType(receiverType, node.interfaceTarget);
node.resultType = resultType;
receiverType = _narrowInstanceReceiver(node.interfaceTarget, receiverType);
assert(node.name.text != Identifiers.runtimeType_,
"Unexpected .runtimeType instance tear-off.");
handleInstanceGet(node, receiverType, node.interfaceTarget, resultType);
return resultType;
}
@override
ir.DartType visitFunctionTearOff(ir.FunctionTearOff node) {
ir.DartType receiverType = visitNode(node.receiver);
handleDynamicGet(node, receiverType, ir.Name.callName, receiverType);
return receiverType;
}
void handleDynamicSet(ir.Expression node, ir.DartType receiverType,
ir.Name name, ir.DartType valueType) {}
void handleInstanceSet(ir.Expression node, ir.DartType receiverType,
ir.Member interfaceTarget, ir.DartType valueType) {}
ir.Member? _resolveDynamicSet(ir.DartType receiverType, ir.Name name) {
if (receiverType is ir.InterfaceType) {
return hierarchy.getInterfaceMember(receiverType.classNode, name,
setter: true);
}
return null;
}
ir.DartType _computeInstanceSetType(
ir.DartType receiverType, ir.Member interfaceTarget) {
ir.Class superclass = interfaceTarget.enclosingClass!;
ir.Substitution receiverSubstitution = ir.Substitution.fromInterfaceType(
getTypeAsInstanceOf(receiverType, superclass));
return receiverSubstitution.substituteType(interfaceTarget.setterType);
}
ir.AsExpression? _createImplicitAsIfNeeded(
ir.Expression value, ir.DartType valueType, ir.DartType setterType) {
if (!typeEnvironment.isSubtypeOf(
valueType, setterType, ir.SubtypeCheckMode.ignoringNullabilities)) {
// We need to insert an implicit cast to preserve the invariant that
// a property set with a known interface target is also statically
// checked.
return ir.AsExpression(value, setterType)..isTypeError = true;
}
return null;
}
@override
ir.DartType visitDynamicSet(ir.DynamicSet node) {
ir.DartType receiverType = visitNode(node.receiver);
ir.DartType valueType = visitNode(node.value);
ir.Member? interfaceTarget = _resolveDynamicSet(receiverType, node.name);
if (interfaceTarget != null) {
ir.DartType setterType =
_computeInstanceSetType(receiverType, interfaceTarget);
ir.Expression value = node.value;
ir.AsExpression? implicitCast =
_createImplicitAsIfNeeded(value, valueType, setterType);
if (implicitCast != null) {
value = implicitCast;
// Visit the newly created as expression; the original value has
// already been visited.
handleAsExpression(implicitCast, valueType);
valueType = setterType;
}
ir.InstanceSet instanceSet = ir.InstanceSet(
ir.InstanceAccessKind.Instance, node.receiver, node.name, value,
interfaceTarget: interfaceTarget);
_replaceExpression(node, instanceSet);
receiverType = _narrowInstanceReceiver(interfaceTarget, receiverType);
handleInstanceSet(node, receiverType, interfaceTarget, valueType);
} else {
handleDynamicSet(node, receiverType, node.name, valueType);
}
return valueType;
}
@override
ir.DartType visitInstanceSet(ir.InstanceSet node) {
ir.DartType receiverType = visitNode(node.receiver);
ir.DartType valueType = visitNode(node.value);
handleInstanceSet(node, receiverType, node.interfaceTarget, valueType);
return valueType;
}
/// Returns `true` if [interfaceTarget] is an arithmetic operator whose result
/// type is computed using both the receiver type and the argument type.
///
/// Visitors that subclass the [StaticTypeVisitor] must special case this
/// target as to avoid visiting the argument twice.
bool isSpecialCasedBinaryOperator(ir.Member interfaceTarget) {
return interfaceTarget is ir.Procedure &&
typeEnvironment.isSpecialCasedBinaryOperator(interfaceTarget);
}
/// Returns [receiverType] narrowed to enclosing class of [interfaceTarget].
///
/// If [interfaceTarget] is `null` or `receiverType` is _not_ `dynamic` no
/// narrowing is performed.
ir.DartType _narrowInstanceReceiver(
ir.Member? interfaceTarget, ir.DartType receiverType) {
if (interfaceTarget != null && receiverType == const ir.DynamicType()) {
receiverType = interfaceTarget.enclosingClass!.getThisType(
typeEnvironment.coreTypes,
interfaceTarget.enclosingLibrary.nonNullable);
}
return receiverType;
}
/// Returns `true` if [arguments] are applicable to the function type
/// structure.
bool _isApplicableToFunctionType(
ir.Arguments arguments,
int typeParameterCount,
int requiredParameterCount,
int positionalParameterCount,
Iterable<String> Function() getNamedParameters) {
if (arguments.types.isNotEmpty &&
arguments.types.length != typeParameterCount) {
return false;
}
if (arguments.positional.length < requiredParameterCount) {
return false;
}
if (arguments.positional.length > positionalParameterCount) {
return false;
}
Iterable<String> namedParameters = getNamedParameters();
if (arguments.named.length > namedParameters.length) {
return false;
}
if (arguments.named.isNotEmpty) {
for (ir.NamedExpression namedArguments in arguments.named) {
if (!namedParameters.contains(namedArguments.name)) {
return false;
}
}
}
return true;
}
/// Returns `true` if [arguments] are applicable to a value of the static
/// [type].
bool _isApplicableToType(ir.Arguments arguments, ir.DartType type) {
if (type is ir.DynamicType) return true;
if (type == typeEnvironment.coreTypes.functionLegacyRawType ||
type == typeEnvironment.coreTypes.functionNullableRawType ||
type == typeEnvironment.coreTypes.functionNonNullableRawType)
return true;
if (type is ir.FunctionType) {
return _isApplicableToFunctionType(
arguments,
type.typeParameters.length,
type.requiredParameterCount,
type.positionalParameters.length,
() => type.namedParameters.map((p) => p.name).toSet());
}
return false;
}
/// Returns `true` if [member] can be called with the structure of
/// [arguments].
bool _isApplicableToMember(ir.Arguments arguments, ir.Member? member) {
if (member is ir.Procedure) {
if (member.kind == ir.ProcedureKind.Setter ||
member.kind == ir.ProcedureKind.Factory) {
return false;
} else if (member.kind == ir.ProcedureKind.Getter) {
return _isApplicableToType(arguments, member.getterType);
} else if (member.kind == ir.ProcedureKind.Method ||
member.kind == ir.ProcedureKind.Operator) {
return _isApplicableToFunctionType(
arguments,
member.function.typeParameters.length,
member.function.requiredParameterCount,
member.function.positionalParameters.length,
() => member.function.namedParameters.map((p) => p.name!).toSet());
}
} else if (member is ir.Field) {
return _isApplicableToType(arguments, member.type);
}
return false;
}
/// Update the interface target on [node].
///
/// This inserts any implicit cast of the arguments necessary to uphold the
/// invariant that a method invocation with an interface target handles
/// the static types at the call site.
void _updateMethodInvocationTarget(ir.InvocationExpression node,
ArgumentTypes argumentTypes, ir.DartType functionType) {
if (functionType is! ir.FunctionType) return;
Map<int, ir.DartType> neededPositionalChecks = {};
for (int i = 0; i < node.arguments.positional.length; i++) {
ir.DartType argumentType = argumentTypes.positional[i];
ir.DartType parameterType = functionType.positionalParameters[i];
if (!typeEnvironment.isSubtypeOf(argumentType, parameterType,
ir.SubtypeCheckMode.ignoringNullabilities)) {
neededPositionalChecks[i] = parameterType;
}
}
Map<int, ir.DartType> neededNamedChecks = {};
for (int argumentIndex = 0;
argumentIndex < node.arguments.named.length;
argumentIndex++) {
ir.NamedExpression namedArgument = node.arguments.named[argumentIndex];
ir.DartType argumentType = argumentTypes.named[argumentIndex];
ir.DartType parameterType = functionType.namedParameters
.singleWhere((namedType) => namedType.name == namedArgument.name)
.type;
if (!typeEnvironment.isSubtypeOf(argumentType, parameterType,
ir.SubtypeCheckMode.ignoringNullabilities)) {
neededNamedChecks[argumentIndex] = parameterType;
}
}
if (neededPositionalChecks.isEmpty && neededNamedChecks.isEmpty) {
// No implicit casts needed
return;
}
List<ir.VariableDeclaration> letVariables = [];
// Arguments need to be hoisted to an enclosing let expression in order
// to ensure that the arguments are evaluated before any implicit cast.
ir.Expression updateArgument(ir.Expression expression, ir.TreeNode parent,
ir.DartType argumentType, ir.DartType? checkedParameterType) {
ir.VariableDeclaration variable =
ir.VariableDeclaration.forValue(expression, type: argumentType);
// Visit the newly created variable declaration.
handleVariableDeclaration(variable);
letVariables.add(variable);
ir.VariableGet get = ir.VariableGet(variable)..parent = parent;
// Visit the newly created variable get.
handleVariableGet(get, argumentType);
_staticTypeCache._expressionTypes[get] = argumentType;
if (checkedParameterType == null) {
return get;
}
// We need to insert an implicit cast to preserve the invariant that
// a method invocation with a known interface target is also
// statically checked.
ir.AsExpression implicitCast = ir.AsExpression(get, checkedParameterType)
..isTypeError = true
..parent = parent;
// Visit the newly created as expression; the original value has
// already been visited.
handleAsExpression(implicitCast, argumentType);
return implicitCast;
}
for (int index = 0; index < node.arguments.positional.length; index++) {
ir.DartType argumentType = argumentTypes.positional[index];
node.arguments.positional[index] = updateArgument(
node.arguments.positional[index],
node.arguments,
argumentType,
neededPositionalChecks[index]);
}
for (int argumentIndex = 0;
argumentIndex < node.arguments.named.length;
argumentIndex++) {
ir.NamedExpression namedArgument = node.arguments.named[argumentIndex];
ir.DartType argumentType = argumentTypes.named[argumentIndex];
namedArgument.value = updateArgument(namedArgument.value, namedArgument,
argumentType, neededNamedChecks[argumentIndex]);
}
ir.Expression dummy = ir.NullLiteral();
node.replaceWith(dummy);
ir.Expression body = node;
for (ir.VariableDeclaration variable in letVariables.reversed) {
body = ir.Let(variable, body);
}
dummy.replaceWith(body);
}
ir.Member? _resolveDynamicInvocationTarget(
ir.DartType receiverType, ir.Name name, ir.Arguments arguments) {
// TODO(34602): Remove when `interfaceTarget` is set on synthetic calls to
// ==.
if (name.text == '==' &&
arguments.types.isEmpty &&
arguments.positional.length == 1 &&
arguments.named.isEmpty) {
return typeEnvironment.coreTypes.objectEquals;
}
if (receiverType is ir.InterfaceType) {
ir.Member? member =
hierarchy.getInterfaceMember(receiverType.classNode, name);
if (_isApplicableToMember(arguments, member)) {
return member;
}
}
return null;
}
/// Computes the function type of the function invocation of type
/// [functionType] with the given [argument].
ir.DartType _computeFunctionInvocationType(
ir.DartType functionType, ir.Arguments arguments) {
if (functionType is ir.FunctionType) {
List<ir.DartType> typeArguments = arguments.types;
if (functionType.typeParameters.isNotEmpty && typeArguments.isEmpty) {
// If this was a dynamic call the invocation does not have the
// inferred default type arguments so we need to create them here
// to perform a valid substitution.
typeArguments =
functionType.typeParameters.map((t) => t.defaultType).toList();
}
return ir.Substitution.fromPairs(
functionType.typeParameters, typeArguments)
.substituteType(functionType.withoutTypeParameters);
}
return functionType;
}
/// Computes the function type of the instance invocation [node] on a receiver
/// of type [receiverType] on the [interfaceTarget] with the given
/// [argumentTypes].
ir.DartType _computeInstanceInvocationType(
ir.DartType receiverType,
ir.Member interfaceTarget,
ir.Arguments arguments,
ArgumentTypes argumentTypes) {
ir.Class superclass = interfaceTarget.enclosingClass!;
ir.Substitution receiverSubstitution = ir.Substitution.fromInterfaceType(
getTypeAsInstanceOf(receiverType, superclass));
ir.DartType getterType =
receiverSubstitution.substituteType(interfaceTarget.getterType);
if (getterType is ir.FunctionType) {
ir.FunctionType functionType = getterType;
List<ir.DartType> typeArguments = arguments.types;
if (interfaceTarget is ir.Procedure &&
interfaceTarget.function.typeParameters.isNotEmpty &&
typeArguments.isEmpty) {
// If this was a dynamic call the invocation does not have the
// inferred default type arguments so we need to create them here
// to perform a valid substitution.
typeArguments = interfaceTarget.function.typeParameters
.map((t) => receiverSubstitution.substituteType(t.defaultType))
.toList();
}
getterType =
ir.Substitution.fromPairs(functionType.typeParameters, typeArguments)
.substituteType(functionType.withoutTypeParameters);
}
if (isSpecialCasedBinaryOperator(interfaceTarget)) {
ir.DartType argumentType = argumentTypes.positional[0];
ir.DartType resultType = typeEnvironment
.getTypeOfSpecialCasedBinaryOperator(receiverType, argumentType);
return ir.FunctionType(
[argumentType], resultType, currentLibrary.nonNullable);
}
return getterType;
}
ir.DartType _getFunctionReturnType(ir.DartType functionType) {
return functionType is ir.FunctionType
? functionType.returnType
: const ir.DynamicType();
}
/// Computes the result type of the dynamic invocation [node] on a receiver of
/// type [receiverType].
ir.DartType _computeDynamicInvocationReturnType(
ir.InvocationExpression node, ir.DartType receiverType) {
if (node.name.text == 'call') {
if (receiverType is ir.FunctionType) {
if (receiverType.typeParameters.length != node.arguments.types.length) {
return const DoesNotCompleteType();
}
return ir.Substitution.fromPairs(
receiverType.typeParameters, node.arguments.types)
.substituteType(receiverType.returnType);
}
}
if (node.name.text == '==') {
// We use this special case to simplify generation of '==' checks.
return typeEnvironment.coreTypes.boolNonNullableRawType;
}
return const ir.DynamicType();
}
ArgumentTypes _visitArguments(ir.Arguments arguments) {
final positional = arguments.positional.isEmpty
? const <ir.DartType>[]
: arguments.positional.map(visitNode).toList(growable: false);
final named = arguments.named.isEmpty
? const <ir.DartType>[]
: arguments.named.map(visitNode).toList(growable: false);
return ArgumentTypes(positional, named);
}
void handleDynamicInvocation(
ir.InvocationExpression node,
ir.DartType receiverType,
ArgumentTypes argumentTypes,
ir.DartType returnType) {}
void handleFunctionInvocation(
ir.InvocationExpression node,
ir.DartType receiverType,
ArgumentTypes argumentTypes,
ir.DartType returnType) {}
void handleInstanceInvocation(
ir.InvocationExpression node,
ir.DartType receiverType,
ir.Member interfaceTarget,
ArgumentTypes argumentTypes) {}
void handleLocalFunctionInvocation(
ir.InvocationExpression node,
ir.FunctionDeclaration function,
ArgumentTypes argumentTypes,
ir.DartType returnType) {}
void handleEqualsCall(ir.Expression left, ir.DartType leftType,
ir.Expression right, ir.DartType rightType, ir.Member interfaceTarget) {}
void _registerEqualsNull(TypeMap afterInvocation, ir.Expression expression) {
if (expression is ir.VariableGet &&
!_invalidatedVariables.contains(expression.variable)) {
// If `expression == null` is true, we promote the type of the
// variable to `Null` by registering that is known _not_ to be of its
// declared type.
TypeMap notOfItsDeclaredType = afterInvocation.promote(
expression.variable, expression.variable.type,
isTrue: false);
TypeMap ofItsDeclaredType = afterInvocation
.promote(expression.variable, expression.variable.type, isTrue: true);
typeMapWhenTrue = notOfItsDeclaredType;
typeMapWhenFalse = ofItsDeclaredType;
}
}
@override
ir.DartType visitInstanceInvocation(ir.InstanceInvocation node) {
ArgumentTypes argumentTypes = _visitArguments(node.arguments);
ir.DartType receiverType = visitNode(node.receiver);
ir.Member interfaceTarget = node.interfaceTarget;
// We compute the function type instead of reading it of [node] since the
// receiver and argument types might have improved through inference of
// effectively final variable types and type promotion.
ir.DartType functionType = _computeInstanceInvocationType(
receiverType, interfaceTarget, node.arguments, argumentTypes);
if (functionType != node.functionType) {
node.functionType = functionType as ir.FunctionType;
// TODO(johnniwinther): To provide the static guarantee that arguments
// of a statically typed call have been checked against the parameter
// types we need to call [_updateMethodInvocationTarget]. This can create
// uses of type variables are not registered with the closure model so
// we skip it for now. Note that this invariant is not currently used
// in later phases since it wasn't provided for function invocations in
// the old method invocation encoding.
//_updateMethodInvocationTarget(node, argumentTypes, functionType);
}
ir.DartType returnType = _getFunctionReturnType(functionType);
receiverType = _narrowInstanceReceiver(node.interfaceTarget, receiverType);
handleInstanceInvocation(
node, receiverType, interfaceTarget, argumentTypes);
_staticTypeCache._expressionTypes[node] = returnType;
return returnType;
}
@override
ir.DartType visitInstanceGetterInvocation(ir.InstanceGetterInvocation node) {
ArgumentTypes argumentTypes = _visitArguments(node.arguments);
ir.DartType receiverType = visitNode(node.receiver);
ir.Member interfaceTarget = node.interfaceTarget;
// We compute the function type instead of reading it of [node] since the
// receiver and argument types might have improved through inference of
// effectively final variable types and type promotion.
ir.DartType functionType = _computeInstanceInvocationType(
receiverType, interfaceTarget, node.arguments, argumentTypes);
if (functionType is ir.FunctionType && functionType != node.functionType) {
node.functionType = functionType;
_updateMethodInvocationTarget(node, argumentTypes, functionType);
}
ir.DartType returnType = _getFunctionReturnType(functionType);
receiverType = _narrowInstanceReceiver(node.interfaceTarget, receiverType);
handleInstanceInvocation(
node, receiverType, interfaceTarget, argumentTypes);
_staticTypeCache._expressionTypes[node] = returnType;
return returnType;
}
@override
ir.DartType visitDynamicInvocation(ir.DynamicInvocation node) {
ArgumentTypes argumentTypes = _visitArguments(node.arguments);
ir.DartType receiverType = visitNode(node.receiver);
ir.Member? interfaceTarget = _resolveDynamicInvocationTarget(
receiverType, node.name, node.arguments);
if (interfaceTarget != null) {
// We can turn the dynamic invocation into an instance invocation.
ir.DartType functionType = _computeInstanceInvocationType(
receiverType, interfaceTarget, node.arguments, argumentTypes);
ir.InvocationExpression replacement;
if (interfaceTarget is ir.Field ||
(interfaceTarget is ir.Procedure && interfaceTarget.isGetter)) {
// This should actually be a function invocation of an instance get but
// this doesn't work for invocation of js-interop properties. We
// therefore use [ir.InstanceGetterInvocation] instead.
replacement = ir.InstanceGetterInvocation(
ir.InstanceAccessKind.Instance,
node.receiver,
node.name,
node.arguments,
interfaceTarget: interfaceTarget,
functionType: functionType is ir.FunctionType ? functionType : null)
..fileOffset = node.fileOffset;
} else {
replacement = ir.InstanceInvocation(ir.InstanceAccessKind.Instance,
node.receiver, node.name, node.arguments,
interfaceTarget: interfaceTarget as ir.Procedure,
functionType: functionType as ir.FunctionType)
..fileOffset = node.fileOffset;
}
_replaceExpression(node, replacement);
_updateMethodInvocationTarget(replacement, argumentTypes, functionType);
ir.DartType resultType = _getFunctionReturnType(functionType);
receiverType = _narrowInstanceReceiver(interfaceTarget, receiverType);
handleInstanceInvocation(
replacement, receiverType, interfaceTarget, argumentTypes);
return resultType;
} else if (node.name == ir.Name.callName &&
(receiverType is ir.FunctionType ||
(receiverType is ir.InterfaceType &&
receiverType.classNode == typeEnvironment.functionClass)) &&
_isApplicableToType(node.arguments, receiverType)) {
ir.DartType functionType =
_computeFunctionInvocationType(receiverType, node.arguments);
bool hasFunctionType = functionType is ir.FunctionType;
ir.FunctionInvocation replacement = ir.FunctionInvocation(
hasFunctionType
? ir.FunctionAccessKind.FunctionType
: ir.FunctionAccessKind.Function,
node.receiver,
node.arguments,
functionType: hasFunctionType ? functionType : null)
..fileOffset = node.fileOffset;
ir.DartType resultType = _getFunctionReturnType(functionType);
_replaceExpression(node, replacement);
_updateMethodInvocationTarget(replacement, argumentTypes, functionType);
handleFunctionInvocation(
replacement, receiverType, argumentTypes, resultType);
return resultType;
} else {
ir.DartType returnType =
_computeDynamicInvocationReturnType(node, receiverType);
_staticTypeCache._expressionTypes[node] = returnType;
handleDynamicInvocation(node, receiverType, argumentTypes, returnType);
if (operatorFromString(node.name.text) == null &&
receiverType is ir.DynamicType) {
// We might implicitly call a getter that returns a function.
handleFunctionInvocation(
node, const ir.DynamicType(), argumentTypes, returnType);
}
return returnType;
}
}
@override
ir.DartType visitEqualsCall(ir.EqualsCall node) {
ir.DartType leftType = visitNode(node.left);
ir.DartType rightType = visitNode(node.right);
// This is accessed to ensure that [typeMapWhenTrue] and [typeMapWhenFalse]
// are joined as the result of this node.
// This is related to dartbug.com/45053
_flattenTypeMap();
leftType = _narrowInstanceReceiver(node.interfaceTarget, leftType);
handleEqualsCall(
node.left, leftType, node.right, rightType, node.interfaceTarget);
return super.visitEqualsCall(node);
}
void handleEqualsNull(ir.EqualsNull node, ir.DartType expressionType) {}
@override
ir.DartType visitEqualsNull(ir.EqualsNull node) {
ir.DartType expressionType = visitNode(node.expression);
if (expressionType is ir.DynamicType) {
expressionType = currentLibrary.isNonNullableByDefault
? typeEnvironment.objectNullableRawType
: typeEnvironment.objectLegacyRawType;
}
_registerEqualsNull(typeMap, node.expression);
handleEqualsNull(node, expressionType);
return super.visitEqualsNull(node);
}
@override
ir.DartType visitFunctionInvocation(ir.FunctionInvocation node) {
ArgumentTypes argumentTypes = _visitArguments(node.arguments);
ir.DartType receiverType = visitNode(node.receiver);
ir.DartType functionType =
_computeFunctionInvocationType(receiverType, node.arguments);
if (functionType is ir.FunctionType) {
// We might have improved the known function type through inference of
// effectively final variable types and type promotion.
node.functionType = functionType;
}
// We compute the return type instead of reading it of [node] since the
// receiver and argument types might have improved through inference of
// effectively final variable types and type promotion.
ir.DartType returnType = _getFunctionReturnType(functionType);
handleFunctionInvocation(node, functionType, argumentTypes, returnType);
return returnType;
}
@override
ir.DartType visitLocalFunctionInvocation(ir.LocalFunctionInvocation node) {
ArgumentTypes argumentTypes = _visitArguments(node.arguments);
ir.FunctionDeclaration localFunction = node.localFunction;
ir.DartType returnType = super.visitLocalFunctionInvocation(node);
handleLocalFunctionInvocation(
node, localFunction, argumentTypes, returnType);
return returnType;
}
void handleVariableGet(ir.VariableGet node, ir.DartType type) {}
@override
ir.DartType visitVariableGet(ir.VariableGet node) {
typeMapsForTesting?[node] = typeMap;
ir.DartType promotedType = typeMap.typeOf(node, typeEnvironment);
assert(
node.promotedType == null ||
promotedType is ir.NullType ||
promotedType is ir.FutureOrType ||
typeEnvironment.isSubtypeOf(promotedType, node.promotedType!,
ir.SubtypeCheckMode.ignoringNullabilities),
"Unexpected promotion of ${node.variable} in ${node.parent}. "
"Expected ${node.promotedType}, found $promotedType");
_staticTypeCache._expressionTypes[node] = promotedType;
handleVariableGet(node, promotedType);
return promotedType;
}
void handleVariableSet(ir.VariableSet node, ir.DartType resultType) {}
@override
ir.DartType visitVariableSet(ir.VariableSet node) {
ir.DartType resultType = super.visitVariableSet(node);
handleVariableSet(node, resultType);
if (!_currentVariables!.contains(node.variable)) {
_invalidatedVariables.add(node.variable);
typeMap = typeMap.remove([node.variable]);
} else {
typeMap = typeMap.reduce(node, resultType, typeEnvironment);
}
return resultType;
}
void handleStaticGet(
ir.Expression node, ir.Member target, ir.DartType resultType) {}
void handleStaticTearOff(
ir.Expression node, ir.Member target, ir.DartType resultType) {}
@override
ir.DartType visitStaticGet(ir.StaticGet node) {
ir.DartType resultType = super.visitStaticGet(node);
ir.Member target = node.target;
if (target is ir.Procedure && target.kind == ir.ProcedureKind.Method) {
// TODO(johnniwinther): Remove this when dart2js uses the new method
// invocation encoding.
handleStaticTearOff(node, target, resultType);
} else {
handleStaticGet(node, target, resultType);
}
return resultType;
}
@override
ir.DartType visitStaticTearOff(ir.StaticTearOff node) {
ir.DartType resultType = super.visitStaticTearOff(node);
handleStaticTearOff(node, node.target, resultType);
return resultType;
}
void handleStaticSet(ir.StaticSet node, ir.DartType valueType) {}
@override
ir.DartType visitStaticSet(ir.StaticSet node) {
ir.DartType valueType = super.visitStaticSet(node);
handleStaticSet(node, valueType);
return valueType;
}
void handleStaticInvocation(ir.StaticInvocation node,
ArgumentTypes argumentTypes, ir.DartType returnType) {}
@override
ir.DartType visitStaticInvocation(ir.StaticInvocation node) {
ArgumentTypes argumentTypes = _visitArguments(node.arguments);
ir.DartType returnType = ir.Substitution.fromPairs(
node.target.function.typeParameters, node.arguments.types)
.substituteType(node.target.function.returnType);
_staticTypeCache._expressionTypes[node] = returnType;
handleStaticInvocation(node, argumentTypes, returnType);
return returnType;
}
void handleConstructorInvocation(ir.ConstructorInvocation node,
ArgumentTypes argumentTypes, ir.DartType resultType) {}
@override
ir.DartType visitConstructorInvocation(ir.ConstructorInvocation node) {
ArgumentTypes argumentTypes = _visitArguments(node.arguments);
ir.DartType resultType = node.arguments.types.isEmpty
? ExactInterfaceType.from(typeEnvironment.coreTypes
.nonNullableRawType(node.target.enclosingClass))
: ExactInterfaceType(node.target.enclosingClass,
ir.Nullability.nonNullable, node.arguments.types);
_staticTypeCache._expressionTypes[node] = resultType;
handleConstructorInvocation(node, argumentTypes, resultType);
return resultType;
}
void handleSuperPropertyGet(
ir.SuperPropertyGet node, ir.DartType resultType) {}
@override
ir.DartType visitSuperPropertyGet(ir.SuperPropertyGet node) {
ir.DartType resultType;
final interfaceTarget = node.interfaceTarget;
ir.Class declaringClass = interfaceTarget.enclosingClass!;
if (declaringClass.typeParameters.isEmpty) {
resultType = interfaceTarget.superGetterType;
} else {
ir.InterfaceType receiver = typeEnvironment.getTypeAsInstanceOf(
thisType, declaringClass, currentLibrary, typeEnvironment.coreTypes)!;
resultType = ir.Substitution.fromInterfaceType(receiver)
.substituteType(interfaceTarget.superGetterType);
}
_staticTypeCache._expressionTypes[node] = resultType;
handleSuperPropertyGet(node, resultType);
return resultType;
}
void handleSuperPropertySet(
ir.SuperPropertySet node, ir.DartType valueType) {}
@override
ir.DartType visitSuperPropertySet(ir.SuperPropertySet node) {
ir.DartType valueType = super.visitSuperPropertySet(node);
handleSuperPropertySet(node, valueType);
return valueType;
}
void handleSuperMethodInvocation(ir.SuperMethodInvocation node,
ArgumentTypes argumentTypes, ir.DartType returnType) {}
@override
ir.DartType visitSuperMethodInvocation(ir.SuperMethodInvocation node) {
ArgumentTypes argumentTypes = _visitArguments(node.arguments);
ir.DartType returnType;
final interfaceTarget = node.interfaceTarget;
ir.Class superclass = interfaceTarget.enclosingClass!;
ir.InterfaceType receiverType = typeEnvironment.getTypeAsInstanceOf(
thisType, superclass, currentLibrary, typeEnvironment.coreTypes)!;
returnType = ir.Substitution.fromInterfaceType(receiverType)
.substituteType(interfaceTarget.function.returnType);
returnType = ir.Substitution.fromPairs(
interfaceTarget.function.typeParameters, node.arguments.types)
.substituteType(returnType);
_staticTypeCache._expressionTypes[node] = returnType;
handleSuperMethodInvocation(node, argumentTypes, returnType);
return returnType;
}
@override
ir.DartType visitLogicalExpression(ir.LogicalExpression node) {
if (node.operatorEnum == ir.LogicalExpressionOperator.AND) {
visitNode(node.left);
TypeMap afterLeftWhenTrue = typeMapWhenTrue;
TypeMap afterLeftWhenFalse = typeMapWhenFalse;
typeMap = afterLeftWhenTrue;
visitNode(node.right);
TypeMap afterRightWhenTrue = typeMapWhenTrue;
TypeMap afterRightWhenFalse = typeMapWhenFalse;
typeMapWhenTrue = afterRightWhenTrue;
typeMapWhenFalse = afterLeftWhenFalse.join(afterRightWhenFalse);
} else {
visitNode(node.left);
TypeMap afterLeftWhenTrue = typeMapWhenTrue;
TypeMap afterLeftWhenFalse = typeMapWhenFalse;
typeMap = afterLeftWhenFalse;
visitNode(node.right);
TypeMap afterRightWhenTrue = typeMapWhenTrue;
TypeMap afterRightWhenFalse = typeMapWhenFalse;
typeMapWhenTrue = afterLeftWhenTrue.join(afterRightWhenTrue);
typeMapWhenFalse = afterRightWhenFalse;
}
return super.visitLogicalExpression(node);
}
@override
ir.DartType visitNot(ir.Not node) {
visitNode(node.operand);
TypeMap afterOperandWhenTrue = typeMapWhenTrue;
TypeMap afterOperandWhenFalse = typeMapWhenFalse;
typeMapWhenTrue = afterOperandWhenFalse;
typeMapWhenFalse = afterOperandWhenTrue;
return super.visitNot(node);
}
ir.DartType? _handleConditional(
ir.Expression condition, ir.TreeNode then, ir.TreeNode? otherwise) {
visitNode(condition);
TypeMap afterConditionWhenTrue = typeMapWhenTrue;
TypeMap afterConditionWhenFalse = typeMapWhenFalse;
typeMap = afterConditionWhenTrue;
ir.DartType thenType = visitNode(then);
TypeMap afterThen = typeMap;
typeMap = afterConditionWhenFalse;
ir.DartType? otherwiseType = visitNodeOrNull(otherwise);
TypeMap afterOtherwise = typeMap;
if (completes(thenType) && completes(otherwiseType)) {
typeMap = afterThen.join(afterOtherwise);
return null;
} else if (completes(thenType)) {
typeMap = afterThen;
return null;
} else if (completes(otherwiseType)) {
typeMap = afterOtherwise;
return null;
} else {
typeMap = afterThen.join(afterOtherwise);
return const DoesNotCompleteType();
}
}
@override
ir.DartType visitConditionalExpression(ir.ConditionalExpression node) {
// TODO(johnniwinther): Should we return `const DoesNotCompleteType()` if
// both branches are failing?
_handleConditional(node.condition, node.then, node.otherwise);
return super.visitConditionalExpression(node);
}
void handleIsExpression(ir.IsExpression node) {}
@override
ir.DartType visitIsExpression(ir.IsExpression node) {
ir.Expression operand = node.operand;
visitNode(operand);
if (operand is ir.VariableGet &&
!_invalidatedVariables.contains(operand.variable)) {
TypeMap afterOperand = typeMap;
typeMapWhenTrue =
afterOperand.promote(operand.variable, node.type, isTrue: true);
typeMapWhenFalse =
afterOperand.promote(operand.variable, node.type, isTrue: false);
}
handleIsExpression(node);
return super.visitIsExpression(node);
}
@override
ir.DartType visitLet(ir.Let node) {
_processLocalVariable(node.variable);
return super.visitLet(node);
}
@override
ir.DartType visitBlockExpression(ir.BlockExpression node) {
visitNode(node.body);
return super.visitBlockExpression(node);
}
ir.DartType _computeInstantiationType(
ir.Instantiation node, ir.FunctionType expressionType) {
return ir.Substitution.fromPairs(
expressionType.typeParameters, node.typeArguments)
.substituteType(expressionType.withoutTypeParameters);
}
void handleInstantiation(ir.Instantiation node,
ir.FunctionType expressionType, ir.DartType resultType) {}
@override
ir.DartType visitInstantiation(ir.Instantiation node) {
ir.FunctionType expressionType =
visitNode(node.expression) as ir.FunctionType;
ir.DartType resultType = _computeInstantiationType(node, expressionType);
_staticTypeCache._expressionTypes[node] = resultType;
handleInstantiation(node, expressionType, resultType);
return resultType;
}
@override
ir.DartType visitBlock(ir.Block node) {
assert(_pendingRuntimeTypeUseData.isEmpty,
"Incomplete RuntimeTypeUseData: $_pendingRuntimeTypeUseData");
ir.DartType? type;
for (ir.Statement statement in node.statements) {
if (!completes(visitNode(statement))) {
type = const DoesNotCompleteType();
}
}
assert(_pendingRuntimeTypeUseData.isEmpty,
"Incomplete RuntimeTypeUseData: $_pendingRuntimeTypeUseData");
return type ?? const ir.VoidType();
}
@override
ir.DartType visitExpressionStatement(ir.ExpressionStatement node) {
if (completes(visitNode(node.expression))) {
return const ir.VoidType();
} else {
return const DoesNotCompleteType();
}
}
void handleAsExpression(ir.AsExpression node, ir.DartType operandType) {}
@override
ir.DartType visitAsExpression(ir.AsExpression node) {
ir.DartType operandType = visitNode(node.operand);
handleAsExpression(node, operandType);
return super.visitAsExpression(node);
}
void handleNullCheck(ir.NullCheck node, ir.DartType operandType) {}
@override
ir.DartType visitNullCheck(ir.NullCheck node) {
ir.DartType operandType = visitNode(node.operand);
handleNullCheck(node, operandType);
ir.DartType resultType = operandType is ir.NullType
? const ir.NeverType.nonNullable()
: operandType.withDeclaredNullability(ir.Nullability.nonNullable);
_staticTypeCache._expressionTypes[node] = resultType;
return resultType;
}
void handleStringConcatenation(ir.StringConcatenation node) {}
@override
ir.DartType visitStringConcatenation(ir.StringConcatenation node) {
visitNodes(node.expressions);
handleStringConcatenation(node);
return super.visitStringConcatenation(node);
}
void handleIntLiteral(ir.IntLiteral node) {}
@override
ir.DartType visitIntLiteral(ir.IntLiteral node) {
handleIntLiteral(node);
return super.visitIntLiteral(node);
}
void handleDoubleLiteral(ir.DoubleLiteral node) {}
@override
ir.DartType visitDoubleLiteral(ir.DoubleLiteral node) {
handleDoubleLiteral(node);
return super.visitDoubleLiteral(node);
}
void handleBoolLiteral(ir.BoolLiteral node) {}
@override
ir.DartType visitBoolLiteral(ir.BoolLiteral node) {
handleBoolLiteral(node);
return super.visitBoolLiteral(node);
}
void handleStringLiteral(ir.StringLiteral node) {}
@override
ir.DartType visitStringLiteral(ir.StringLiteral node) {
handleStringLiteral(node);
return super.visitStringLiteral(node);
}
void handleSymbolLiteral(ir.SymbolLiteral node) {}
@override
ir.DartType visitSymbolLiteral(ir.SymbolLiteral node) {
handleSymbolLiteral(node);
return super.visitSymbolLiteral(node);
}
void handleNullLiteral(ir.NullLiteral node) {}
@override
ir.DartType visitNullLiteral(ir.NullLiteral node) {
handleNullLiteral(node);
return super.visitNullLiteral(node);
}
void handleListLiteral(ir.ListLiteral node) {}
@override
ir.DartType visitListLiteral(ir.ListLiteral node) {
visitNodes(node.expressions);
handleListLiteral(node);
return super.visitListLiteral(node);
}
void handleSetLiteral(ir.SetLiteral node) {}
@override
ir.DartType visitSetLiteral(ir.SetLiteral node) {
visitNodes(node.expressions);
handleSetLiteral(node);
return super.visitSetLiteral(node);
}
void handleMapLiteral(ir.MapLiteral node) {}
@override
ir.DartType visitMapLiteral(ir.MapLiteral node) {
visitNodes(node.entries);
handleMapLiteral(node);
return super.visitMapLiteral(node);
}
@override
ir.DartType visitMapLiteralEntry(ir.MapLiteralEntry entry) {
visitNode(entry.key);
visitNode(entry.value);
return const ir.VoidType();
}
void handleFunctionExpression(ir.FunctionExpression node) {}
@override
ir.DartType visitFunctionExpression(ir.FunctionExpression node) {
TypeMap beforeClosure =
typeMap.remove(variableScopeModel.getScopeFor(node).assignedVariables);
typeMap = typeMap.remove(variableScopeModel.assignedVariables);
ir.DartType returnType = super.visitFunctionExpression(node);
Set<ir.VariableDeclaration>? _oldVariables = _currentVariables;
_currentVariables = {};
visitSignature(node.function);
visitNodeOrNull(node.function.body);
handleFunctionExpression(node);
_invalidatedVariables.removeAll(_currentVariables!);
_currentVariables = _oldVariables;
typeMap = beforeClosure;
return returnType;
}
void handleThrow(ir.Throw node) {}
@override
ir.DartType visitThrow(ir.Throw node) {
visitNode(node.expression);
handleThrow(node);
return super.visitThrow(node);
}
@override
ir.DartType visitSwitchCase(ir.SwitchCase node) {
visitNodes(node.expressions);
visitNode(node.body);
return const ir.VoidType();
}
@override
ir.DartType visitContinueSwitchStatement(ir.ContinueSwitchStatement node) {
return const DoesNotCompleteType();
}
@override
ir.DartType visitLabeledStatement(ir.LabeledStatement node) {
visitNode(node.body);
return const ir.VoidType();
}
@override
ir.DartType visitBreakStatement(ir.BreakStatement node) {
return const DoesNotCompleteType();
}
@override
ir.DartType visitYieldStatement(ir.YieldStatement node) {
visitNode(node.expression);
return const ir.VoidType();
}
@override
ir.DartType visitAssertInitializer(ir.AssertInitializer node) {
visitNode(node.statement);
return const ir.VoidType();
}
void handleFieldInitializer(ir.FieldInitializer node) {}
@override
ir.DartType visitFieldInitializer(ir.FieldInitializer node) {
visitNode(node.value);
handleFieldInitializer(node);
return const ir.VoidType();
}
void handleRedirectingInitializer(
ir.RedirectingInitializer node, ArgumentTypes argumentTypes) {}
@override
ir.DartType visitRedirectingInitializer(ir.RedirectingInitializer node) {
ArgumentTypes argumentTypes = _visitArguments(node.arguments);
handleRedirectingInitializer(node, argumentTypes);
return const ir.VoidType();
}
void handleSuperInitializer(
ir.SuperInitializer node, ArgumentTypes argumentTypes) {}
@override
ir.DartType visitSuperInitializer(ir.SuperInitializer node) {
ArgumentTypes argumentTypes = _visitArguments(node.arguments);
handleSuperInitializer(node, argumentTypes);
return const ir.VoidType();
}
@override
ir.DartType visitLocalInitializer(ir.LocalInitializer node) {
visitNode(node.variable);
return const ir.VoidType();
}
@override
ir.DartType visitNamedExpression(ir.NamedExpression node) =>
visitNode(node.value);
@override
ir.DartType visitEmptyStatement(ir.EmptyStatement node) =>
const ir.VoidType();
@override
ir.DartType visitForStatement(ir.ForStatement node) {
visitNodes(node.variables);
TypeMap beforeLoop = typeMap =
typeMap.remove(variableScopeModel.getScopeFor(node).assignedVariables);
visitNodeOrNull(node.condition);
typeMap = typeMapWhenTrue;
visitNode(node.body);
visitNodes(node.updates);
typeMap = beforeLoop;
return const ir.VoidType();
}
void handleForInStatement(ir.ForInStatement node, ir.DartType iterableType,
ir.DartType iteratorType) {}
@override
ir.DartType visitForInStatement(ir.ForInStatement node) {
// For sync for-in [iterableType] is a subtype of `Iterable`, for async
// for-in [iterableType] is a subtype of `Stream`.
ir.DartType iterableType = visitNode(node.iterable);
ir.DartType iteratorType = const ir.DynamicType();
ir.InterfaceType? iterableInterfaceType = getInterfaceTypeOf(iterableType);
if (iterableInterfaceType != null) {
if (node.isAsync) {
ir.InterfaceType? streamType = typeEnvironment.getTypeAsInstanceOf(
iterableInterfaceType,
typeEnvironment.coreTypes.streamClass,
currentLibrary,
typeEnvironment.coreTypes);
if (streamType != null) {
iteratorType = ir.InterfaceType(
typeEnvironment.coreTypes.streamIteratorClass,
ir.Nullability.nonNullable,
streamType.typeArguments);
}
} else {
ir.Member? member = hierarchy.getInterfaceMember(
iterableInterfaceType.classNode, ir.Name(Identifiers.iterator));
if (member != null) {
iteratorType = ir.Substitution.fromInterfaceType(
typeEnvironment.getTypeAsInstanceOf(
iterableInterfaceType,
member.enclosingClass!,
currentLibrary,
typeEnvironment.coreTypes)!)
.substituteType(member.getterType);
}
}
}
_staticTypeCache._forInIteratorTypes[node] = iteratorType;
TypeMap beforeLoop = typeMap =
typeMap.remove(variableScopeModel.getScopeFor(node).assignedVariables);
visitNode(node.variable);
visitNode(node.body);
handleForInStatement(node, iterableType, iteratorType);
typeMap = beforeLoop;
return const ir.VoidType();
}
@override
ir.DartType visitDoStatement(ir.DoStatement node) {
TypeMap beforeLoop = typeMap =
typeMap.remove(variableScopeModel.getScopeFor(node).assignedVariables);
visitNode(node.body);
visitNode(node.condition);
typeMap = beforeLoop;
return const ir.VoidType();
}
@override
ir.DartType visitWhileStatement(ir.WhileStatement node) {
TypeMap beforeLoop = typeMap =
typeMap.remove(variableScopeModel.getScopeFor(node).assignedVariables);
visitNode(node.condition);
typeMap = typeMapWhenTrue;
visitNode(node.body);
typeMap = beforeLoop;
return const ir.VoidType();
}
void handleSwitchStatement(ir.SwitchStatement node) {}
@override
ir.DartType visitSwitchStatement(ir.SwitchStatement node) {
visitNode(node.expression);
TypeMap afterExpression = typeMap;
VariableScope scope = variableScopeModel.getScopeFor(node);
TypeMap afterStatement = afterExpression.remove(scope.assignedVariables);
TypeMap beforeCase =
scope.hasContinueSwitch ? afterStatement : afterExpression;
for (ir.SwitchCase switchCase in node.cases) {
typeMap = beforeCase;
visitNode(switchCase);
}
handleSwitchStatement(node);
typeMap = afterStatement;
return const ir.VoidType();
}
@override
ir.DartType visitReturnStatement(ir.ReturnStatement node) {
visitNodeOrNull(node.expression);
return const DoesNotCompleteType();
}
@override
ir.DartType visitIfStatement(ir.IfStatement node) {
_handleConditional(node.condition, node.then, node.otherwise);
return const ir.VoidType();
}
@override
ir.DartType visitTryCatch(ir.TryCatch node) {
visitNode(node.body);
visitNodes(node.catches);
return const ir.VoidType();
}
void handleCatch(ir.Catch node) {}
@override
ir.DartType visitCatch(ir.Catch node) {
handleCatch(node);
visitNode(node.body);
return const ir.VoidType();
}
@override
ir.DartType visitTryFinally(ir.TryFinally node) {
visitNode(node.body);
visitNode(node.finalizer);
return const ir.VoidType();
}
void handleTypeLiteral(ir.TypeLiteral node) {}
@override
ir.DartType visitTypeLiteral(ir.TypeLiteral node) {
handleTypeLiteral(node);
return super.visitTypeLiteral(node);
}
void handleLoadLibrary(ir.LoadLibrary node) {}
@override
ir.DartType visitLoadLibrary(ir.LoadLibrary node) {
handleLoadLibrary(node);
return super.visitLoadLibrary(node);
}
void handleAssertStatement(ir.AssertStatement node) {}
@override
ir.DartType visitAssertStatement(ir.AssertStatement node) {
TypeMap beforeCondition = typeMap;
visitNode(node.condition);
TypeMap afterConditionWhenTrue = typeMapWhenTrue;
TypeMap afterConditionWhenFalse = typeMapWhenFalse;
typeMap = afterConditionWhenFalse;
visitNodeOrNull(node.message);
handleAssertStatement(node);
typeMap = useAsserts ? afterConditionWhenTrue : beforeCondition;
return const ir.VoidType();
}
void handleFunctionDeclaration(ir.FunctionDeclaration node) {}
@override
ir.DartType visitFunctionDeclaration(ir.FunctionDeclaration node) {
TypeMap beforeClosure =
typeMap.remove(variableScopeModel.getScopeFor(node).assignedVariables);
typeMap = typeMap.remove(variableScopeModel.assignedVariables);
Set<ir.VariableDeclaration>? _oldVariables = _currentVariables;
_currentVariables = {};
visitSignature(node.function);
visitNodeOrNull(node.function.body);
handleFunctionDeclaration(node);
_invalidatedVariables.removeAll(_currentVariables!);
_currentVariables = _oldVariables;
typeMap = beforeClosure;
return const ir.VoidType();
}
void handleParameter(ir.VariableDeclaration node) {}
void visitParameter(ir.VariableDeclaration node) {
_currentVariables?.add(node);
visitNodeOrNull(node.initializer);
handleParameter(node);
}
void handleSignature(ir.FunctionNode node) {}
void visitSignature(ir.FunctionNode node) {
node.positionalParameters.forEach(visitParameter);
node.namedParameters.forEach(visitParameter);
handleSignature(node);
}
void handleProcedure(ir.Procedure node) {}
@override
ir.DartType visitProcedure(ir.Procedure node) {
thisType = ThisInterfaceType.from(node.enclosingClass?.getThisType(
typeEnvironment.coreTypes, node.enclosingLibrary.nonNullable));
_currentVariables = {};
currentLibrary = node.enclosingLibrary;
visitSignature(node.function);
visitNodeOrNull(node.function.body);
handleProcedure(node);
_invalidatedVariables.removeAll(_currentVariables!);
_currentVariables = null;
thisType = null;
currentLibrary = null;
return const ir.VoidType();
}
void handleConstructor(ir.Constructor node) {}
@override
ir.DartType visitConstructor(ir.Constructor node) {
thisType = ThisInterfaceType.from(node.enclosingClass.getThisType(
typeEnvironment.coreTypes, node.enclosingLibrary.nonNullable));
_currentVariables = {};
currentLibrary = node.enclosingLibrary;
visitSignature(node.function);
visitNodes(node.initializers);
visitNodeOrNull(node.function.body);
handleConstructor(node);
_invalidatedVariables.removeAll(_currentVariables!);
_currentVariables = null;
thisType = null;
currentLibrary = null;
return const ir.VoidType();
}
void handleField(ir.Field node) {}
@override
ir.DartType visitField(ir.Field node) {
thisType = ThisInterfaceType.from(node.enclosingClass?.getThisType(
typeEnvironment.coreTypes, node.enclosingLibrary.nonNullable));
_currentVariables = {};
currentLibrary = node.enclosingLibrary;
visitNodeOrNull(node.initializer);
handleField(node);
_invalidatedVariables.removeAll(_currentVariables!);
_currentVariables = null;
thisType = null;
currentLibrary = null;
return const ir.VoidType();
}
void handleVariableDeclaration(ir.VariableDeclaration node) {}
void _processLocalVariable(ir.VariableDeclaration node) {
_currentVariables?.add(node);
final initializer = node.initializer;
if (initializer != null) {
ir.DartType type = visitNode(initializer);
if (variableScopeModel.isEffectivelyFinal(node) &&
inferEffectivelyFinalVariableTypes) {
node.type = type;
}
}
}
@override
ir.DartType visitVariableDeclaration(ir.VariableDeclaration node) {
_processLocalVariable(node);
handleVariableDeclaration(node);
return const ir.VoidType();
}
void handleConstantExpression(ir.ConstantExpression node) {}
@override
ir.DartType visitConstantExpression(ir.ConstantExpression node) {
handleConstantExpression(node);
return super.visitConstantExpression(node);
}
}
class ArgumentTypes {
final List<ir.DartType> positional;
final List<ir.DartType> named;
ArgumentTypes(this.positional, this.named);
@override
String toString() {
return 'ArgumentTypes(position=[${positional.join(',')}],'
' named=[${named.join(',')}])';
}
}
/// Type information collected for a single path for a local variable.
///
/// This is used to implement guarded type promotion.
///
/// The terminology and implementation is based on this paper:
///
/// http://www.cs.williams.edu/FTfJP2011/6-Winther.pdf
///
class TypeHolder {
/// The declared type of the local variable.
final ir.DartType declaredType;
/// The types that the local variable is known to be an instance of.
final Set<ir.DartType>? trueTypes;
/// The types that the local variable is known _not_ to be an instance of.
final Set<ir.DartType>? falseTypes;
TypeHolder(this.declaredType, this.trueTypes, this.falseTypes);
/// Computes a single type that soundly represents the promoted type of the
/// local variable on this single path.
ir.DartType? typeOf(ir.TypeEnvironment typeEnvironment) {
ir.DartType candidate = declaredType;
final trueTypes = this.trueTypes;
final falseTypes = this.falseTypes;
if (falseTypes != null) {
// TODO(johnniwinther): Special-case the `== null` representation to
// make it faster.
for (ir.DartType type in falseTypes) {
if (typeEnvironment.isSubtypeOf(
declaredType, type, ir.SubtypeCheckMode.ignoringNullabilities)) {
return const ir.NullType();
}
}
}
if (trueTypes != null) {
for (ir.DartType type in trueTypes) {
if (type is ir.NullType) {
return type;
}
if (typeEnvironment.isSubtypeOf(
type, candidate, ir.SubtypeCheckMode.ignoringNullabilities)) {
candidate = type;
} else if (!typeEnvironment.isSubtypeOf(
candidate, type, ir.SubtypeCheckMode.ignoringNullabilities)) {
// We cannot promote. No single type is most specific.
// TODO(johnniwinther): Compute implied types? For instance when the
// declared type is `Iterable<String>` and tested type is
// `List<dynamic>` we could promote to the implied type
// `List<String>`.
return null;
}
}
}
return candidate;
}
@override
late final int hashCode = Hashing.setHash(
falseTypes, Hashing.setHash(trueTypes, Hashing.objectHash(declaredType)));
@override
bool operator ==(other) {
if (identical(this, other)) return true;
return other is TypeHolder &&
declaredType == other.declaredType &&
equalSets(trueTypes, other.trueTypes) &&
equalSets(falseTypes, other.falseTypes);
}
void _getText(
StringBuffer sb, String Function(Iterable<ir.DartType>) typesToText) {
sb.write('{');
String comma = '';
final trueTypes = this.trueTypes;
final falseTypes = this.falseTypes;
if (trueTypes != null) {
sb.write('true:');
sb.write(typesToText(trueTypes));
comma = ',';
}
if (falseTypes != null) {
sb.write(comma);
sb.write('false:');
sb.write(typesToText(falseTypes));
}
sb.write('}');
}
@override
String toString() {
StringBuffer sb = StringBuffer();
sb.write('TypeHolder(');
sb.write('declared=$declaredType');
if (trueTypes != null) {
sb.write(',true=$trueTypes');
}
if (falseTypes != null) {
sb.write(',false=$falseTypes');
}
sb.write(')');
return sb.toString();
}
}
/// Type information for a single local variable on all possible paths.
///
/// This is used to implement guarded type promotion.
///
/// The terminology and implementation is based on this paper:
///
/// http://www.cs.williams.edu/FTfJP2011/6-Winther.pdf
///
class TargetInfo {
/// The declared type of the local variable.
final ir.DartType declaredType;
/// Collected type information for disjoint paths.
final Iterable<TypeHolder> typeHolders;
/// Types relevant for promotion of the local variable.
final Iterable<ir.DartType> typesOfInterest;
TargetInfo(this.declaredType, this.typeHolders, this.typesOfInterest);
/// Returns the [TargetInfo] that describes the added type knowledge for the
/// local variable. If [isTrue] is `true`, the local variable is known to
/// be an instance of [type]. If [isTrue] is `false`, the local variable is
/// known _not_ to be an instance of [type].
TargetInfo promote(ir.DartType type, {required bool isTrue}) {
Set<TypeHolder> newTypeHolders = {};
bool addTypeHolder(TypeHolder? typeHolder) {
bool changed = false;
Set<ir.DartType> addAsCopy(Set<ir.DartType>? set, ir.DartType type) {
Set<ir.DartType> result;
if (set == null) {
result = {};
} else if (set.contains(type)) {
return set;
} else {
result = Set.of(set);
}
changed = true;
return result..add(type);
}
Set<ir.DartType>? trueTypes = typeHolder?.trueTypes;
Set<ir.DartType>? falseTypes = typeHolder?.falseTypes;
if (isTrue) {
trueTypes = addAsCopy(trueTypes, type);
} else {
falseTypes = addAsCopy(falseTypes, type);
}
// TODO(johnniwinther): Check validity; if the true types are
// contradicting, for instance if the local is known to be and instance
// of types `int` and `String` simultaneously, then we could flag code
// as dead code.
newTypeHolders.add(TypeHolder(declaredType, trueTypes, falseTypes));
return changed;
}
bool changed = false;
if (typeHolders.isEmpty) {
changed |= addTypeHolder(null);
} else {
for (TypeHolder typeHolder in typeHolders) {
changed |= addTypeHolder(typeHolder);
}
}
Iterable<ir.DartType> newTypesOfInterest;
if (typesOfInterest.contains(type)) {
newTypesOfInterest = typesOfInterest;
} else {
newTypesOfInterest = {...typesOfInterest, type};
changed = true;
}
return changed
? TargetInfo(declaredType, newTypeHolders, newTypesOfInterest)
: this;
}
/// Returns the [TargetInfo] that describes that the local is either of [this]
/// or the [other] type.
///
/// Returns `null` if the join is empty.
TargetInfo? join(TargetInfo? other) {
if (other == null) return null;
if (identical(this, other)) return this;
Set<TypeHolder> newTypeHolders = {};
Set<ir.DartType> newTypesOfInterest = {};
/// Adds the [typeHolders] to [newTypeHolders] for types in
/// [otherTypesOfInterest] while removing the information
/// invalidated by [otherTrueTypes] and [otherFalseTypes].
void addTypeHolders(
Iterable<TypeHolder> typeHolders,
Set<ir.DartType> otherTrueTypes,
Set<ir.DartType> otherFalseTypes,
Iterable<ir.DartType> otherTypesOfInterest) {
for (TypeHolder typeHolder in typeHolders) {
Set<ir.DartType>? newTrueTypes;
final holderTrueTypes = typeHolder.trueTypes;
if (holderTrueTypes != null) {
newTrueTypes = Set.of(holderTrueTypes);
/// Only types in [otherTypesOfInterest] has information from all
/// paths.
newTrueTypes.retainAll(otherTypesOfInterest);
/// Remove types that are known to be false on other paths; these
/// would amount to knowing that a variable is or is not of some
/// type.
newTrueTypes.removeAll(otherFalseTypes);
if (newTrueTypes.isEmpty) {
newTrueTypes = null;
} else {
newTypesOfInterest.addAll(newTrueTypes);
}
}
Set<ir.DartType>? newFalseTypes;
final holderFalseTypes = typeHolder.falseTypes;
if (holderFalseTypes != null) {
newFalseTypes = Set.of(holderFalseTypes);
/// Only types in [otherTypesOfInterest] has information from all
/// paths.
newFalseTypes.retainAll(otherTypesOfInterest);
/// Remove types that are known to be true on other paths; these
/// would amount to knowing that a variable is or is not of some
/// type.
newFalseTypes.removeAll(otherTrueTypes);
if (newFalseTypes.isEmpty) {
newFalseTypes = null;
} else {
newTypesOfInterest.addAll(newFalseTypes);
}
}
if (newTrueTypes != null || newFalseTypes != null) {
// Only include type holders with information.
newTypeHolders
.add(TypeHolder(declaredType, newTrueTypes, newFalseTypes));
}
}
}
Set<ir.DartType> thisTrueTypes = {};
Set<ir.DartType> thisFalseTypes = {};
for (TypeHolder typeHolder in typeHolders) {
final holderTrueTypes = typeHolder.trueTypes;
final holderFalseTypes = typeHolder.falseTypes;
if (holderTrueTypes != null) {
thisTrueTypes.addAll(holderTrueTypes);
}
if (holderFalseTypes != null) {
thisFalseTypes.addAll(holderFalseTypes);
}
}
Set<ir.DartType> otherTrueTypes = {};
Set<ir.DartType> otherFalseTypes = {};
for (TypeHolder typeHolder in other.typeHolders) {
final holderTrueTypes = typeHolder.trueTypes;
final holderFalseTypes = typeHolder.falseTypes;
if (holderTrueTypes != null) {
otherTrueTypes.addAll(holderTrueTypes);
}
if (holderFalseTypes != null) {
otherFalseTypes.addAll(holderFalseTypes);
}
}
addTypeHolders(this.typeHolders, otherTrueTypes, otherFalseTypes,
other.typesOfInterest);
addTypeHolders(
other.typeHolders, thisTrueTypes, thisFalseTypes, this.typesOfInterest);
if (newTypeHolders.isEmpty) {
assert(newTypesOfInterest.isEmpty);
return null;
}
return TargetInfo(declaredType, newTypeHolders, newTypesOfInterest);
}
/// Computes a single type that soundly represents the promoted type of the
/// local variable on all possible paths.
ir.DartType? typeOf(ir.TypeEnvironment typeEnvironment) {
ir.DartType? candidate = null;
for (TypeHolder typeHolder in typeHolders) {
ir.DartType? type = typeHolder.typeOf(typeEnvironment);
if (type == null) {
// We cannot promote. No single type is most specific.
return null;
}
if (candidate == null) {
candidate = type;
} else {
if (type is ir.NullType) {
// Keep the current candidate.
} else if (candidate is ir.NullType) {
candidate = type;
} else if (typeEnvironment.isSubtypeOf(
candidate, type, ir.SubtypeCheckMode.ignoringNullabilities)) {
candidate = type;
} else if (!typeEnvironment.isSubtypeOf(
type, candidate, ir.SubtypeCheckMode.ignoringNullabilities)) {
// We cannot promote. No promoted type of one path is a supertype of
// the promoted type from all other paths.
// TODO(johnniwinther): Compute a greatest lower bound, instead?
return null;
}
}
}
return candidate;
}
void _getText(
StringBuffer sb, String Function(Iterable<ir.DartType>) typesToText) {
sb.write('[');
String comma = '';
for (TypeHolder typeHolder in typeHolders) {
sb.write(comma);
typeHolder._getText(sb, typesToText);
comma = ',';
}
sb.write('|');
sb.write(typesToText(typesOfInterest));
sb.write(']');
}
@override
String toString() {
StringBuffer sb = StringBuffer();
sb.write('TargetInfo(');
sb.write('declaredType=$declaredType,');
sb.write('typeHolders=$typeHolders,');
sb.write('declarationsOfInterest=$typesOfInterest');
sb.write(')');
return sb.toString();
}
}
/// Map from local variables to type information used for guarded type
/// promotion.
///
/// The terminology and implementation is based on this paper:
///
/// http://www.cs.williams.edu/FTfJP2011/6-Winther.pdf
///
class TypeMap {
final Map<ir.VariableDeclaration, TargetInfo> _targetInfoMap;
const TypeMap([this._targetInfoMap = const {}]);
/// Returns the [TypeMap] that describes the added type knowledge for the
/// local [variable]. If [isTrue] is `true`, the local [variable] is known to
/// be an instance of [type]. If [isTrue] is `false`, the local [variable] is
/// known _not_ to be an instance of [type].
TypeMap promote(ir.VariableDeclaration variable, ir.DartType type,
{required bool isTrue}) {
Map<ir.VariableDeclaration, TargetInfo> newInfoMap = Map.of(_targetInfoMap);
TargetInfo? targetInfo = newInfoMap[variable];
bool changed = false;
if (targetInfo != null) {
TargetInfo result = targetInfo.promote(type, isTrue: isTrue);
changed = !identical(targetInfo, result);
targetInfo = result;
} else {
changed = true;
Set<ir.DartType>? trueTypes = isTrue ? {type} : null;
Set<ir.DartType>? falseTypes = isTrue ? null : {type};
TypeHolder typeHolder = TypeHolder(variable.type, trueTypes, falseTypes);
targetInfo = TargetInfo(
variable.type, <TypeHolder>[typeHolder], <ir.DartType>[type]);
}
newInfoMap[variable] = targetInfo;
return changed ? TypeMap(newInfoMap) : this;
}
/// Returns the [TypeMap] that describes that the locals are either of [this]
/// or the [other] types.
TypeMap join(TypeMap other) {
if (identical(this, other)) return this;
Map<ir.VariableDeclaration, TargetInfo> newInfoMap = {};
bool changed = false;
_targetInfoMap.forEach((ir.VariableDeclaration variable, TargetInfo info) {
TargetInfo? result = info.join(other._targetInfoMap[variable]);
changed |= !identical(info, result);
if (result != null) {
// Add only non-empty information.
newInfoMap[variable] = result;
}
});
return changed ? TypeMap(newInfoMap) : this;
}
/// Returns the [TypeMap] in which all type information for any of the
/// [variables] has been removed.
TypeMap remove(Iterable<ir.VariableDeclaration> variables) {
bool changed = false;
Map<ir.VariableDeclaration, TargetInfo> newInfoMap = {};
_targetInfoMap.forEach((ir.VariableDeclaration variable, TargetInfo info) {
if (!variables.contains(variable)) {
newInfoMap[variable] = info;
} else {
changed = true;
}
});
return changed ? TypeMap(newInfoMap) : this;
}
/// Returns the [TypeMap] where type information for `node.variable` is
/// reduced to the promotions upheld by an assignment to `node.variable` of
/// the static [type].
TypeMap reduce(ir.VariableSet node, ir.DartType? type,
ir.TypeEnvironment typeEnvironment) {
Map<ir.VariableDeclaration, TargetInfo> newInfoMap = {};
bool changed = false;
_targetInfoMap.forEach((ir.VariableDeclaration variable, TargetInfo info) {
if (variable != node.variable) {
newInfoMap[variable] = info;
} else if (type != null) {
changed = true;
Set<ir.DartType> newTypesOfInterest = {};
for (ir.DartType typeOfInterest in info.typesOfInterest) {
if (typeEnvironment.isSubtypeOf(type, typeOfInterest,
ir.SubtypeCheckMode.ignoringNullabilities)) {
newTypesOfInterest.add(typeOfInterest);
}
}
if (newTypesOfInterest.length > 1 ||
(newTypesOfInterest.length == 1 &&
newTypesOfInterest.single != info.declaredType)) {
// If [newTypesOfInterest] only contains the declared type we have no
// information about the variable (it is either an instance of its
// declared type or null) and the canonical way to represent this is
// to have _no_ target info.
TypeHolder typeHolderIfNonNull =
TypeHolder(info.declaredType, newTypesOfInterest, null);
TypeHolder typeHolderIfNull =
TypeHolder(info.declaredType, null, {info.declaredType});
newInfoMap[variable] = TargetInfo(
info.declaredType,
<TypeHolder>[typeHolderIfNonNull, typeHolderIfNull],
newTypesOfInterest);
}
} else {
changed = true;
}
});
return changed ? TypeMap(newInfoMap) : this;
}
/// Computes a single type that soundly represents the promoted type of
/// `node.variable` on all possible paths.
ir.DartType typeOf(ir.VariableGet node, ir.TypeEnvironment typeEnvironment) {
TargetInfo? info = _targetInfoMap[node.variable];
ir.DartType? type;
if (info != null) {
type = info.typeOf(typeEnvironment);
}
return type ?? node.promotedType ?? node.variable.type;
}
String getText(String Function(Iterable<ir.DartType>) typesToText) {
StringBuffer sb = StringBuffer();
sb.write('{');
String comma = '';
_targetInfoMap.forEach((ir.VariableDeclaration variable, TargetInfo info) {
sb.write('${comma}${variable.name}:');
info._getText(sb, typesToText);
comma = ',';
});
sb.write('}');
return sb.toString();
}
@override
String toString() {
StringBuffer sb = StringBuffer();
sb.write('TypeMap(');
String comma = '';
_targetInfoMap.forEach((ir.VariableDeclaration variable, TargetInfo info) {
sb.write('${comma}$variable->$info');
comma = ',';
});
sb.write(')');
return sb.toString();
}
}