blob: 3255c82dc5af97480d706de994dce049999f9c21 [file] [log] [blame]
// Copyright (c) 2019, 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:_fe_analyzer_shared/src/types/shared_type.dart';
import 'package:meta/meta.dart';
import '../type_inference/assigned_variables.dart';
import '../type_inference/promotion_key_store.dart';
import 'flow_analysis_operations.dart';
import 'flow_link.dart';
/// [PropertyTarget] representing an implicit reference to the target of the
/// innermost enclosing cascade expression.
class CascadePropertyTarget extends PropertyTarget<Never> {
static const CascadePropertyTarget singleton =
const CascadePropertyTarget._();
const CascadePropertyTarget._() : super._();
@override
String toString() => 'CascadePropertyTarget()';
@override
SsaNode<Type> _getSsaNode<Type extends Object>(
_PropertyTargetHelper<Object, Type> helper) =>
helper._cascadeTargetStack.last.ssaNode;
}
/// Non-promotion reason describing the situation where a variable was not
/// promoted due to an explicit write to the variable appearing somewhere in the
/// source code.
class DemoteViaExplicitWrite<Variable extends Object>
extends NonPromotionReason {
/// The local variable that was not promoted.
final Variable variable;
/// The node that wrote to the variable; this corresponds to a node that was
/// passed to [FlowAnalysis.write].
final Object node;
DemoteViaExplicitWrite(this.variable, this.node);
@override
NonPromotionDocumentationLink get documentationLink =>
NonPromotionDocumentationLink.write;
@override
String get shortName => 'explicitWrite';
@override
R accept<R, Node extends Object, Variable extends Object,
Type extends Object>(
NonPromotionReasonVisitor<R, Node, Variable, Type> visitor) =>
visitor.visitDemoteViaExplicitWrite(
this as DemoteViaExplicitWrite<Variable>);
@override
String toString() => 'DemoteViaExplicitWrite($node)';
}
/// Information gathered by flow analysis about an expression. This includes its
/// static type, whether it refers to `null` or to something promotable, and the
/// flow models representing execution state after the expression is evaluated.
class ExpressionInfo<Type extends Object> {
/// The static type of the expression.
final Type _type;
/// The flow model representing execution state after the expression is
/// evaluated, if the expression evaluates to `true`.
@visibleForTesting
final FlowModel<Type> ifTrue;
/// The flow model representing execution state after the expression is
/// evaluated, if the expression evaluates to `false`.
@visibleForTesting
final FlowModel<Type> ifFalse;
/// Creates an [ExpressionInfo] for an expression whose value influences the
/// flow model (e.g. an `!= null` or `is Type` check applied to a promotable
/// target, which causes a promotion if it evaluates to `true`).
@visibleForTesting
ExpressionInfo(
{required Type type, required this.ifTrue, required this.ifFalse})
: _type = type;
/// Creates an [ExpressionInfo] for an expression whose value doesn't
/// influence the flow model.
@visibleForTesting
ExpressionInfo.trivial({required Type type, required FlowModel<Type> model})
: _type = type,
ifTrue = model,
ifFalse = model;
/// Determines if the value of the expression represented by `this` influences
/// the flow model.
bool get isNonTrivial => !identical(ifTrue, ifFalse);
/// Indicates whether the expression represented by `this` is a `null`
/// literal.
bool get isNull => false;
@override
String toString() => 'ExpressionInfo(type: $_type, '
'_ifTrue: $ifTrue, ifFalse: $ifFalse)';
/// Creates an [ExpressionInfo] containing information about the logical
/// inversion of the expression represented by `this`. For example, if `this`
/// contains information about the expression `x == null`, calling this method
/// produces an [ExpressionInfo] containing information about the expression
/// `x != null`.
ExpressionInfo<Type> _invert() => isNonTrivial
? new ExpressionInfo<Type>(type: _type, ifTrue: ifFalse, ifFalse: ifTrue)
: this;
}
/// [PropertyTarget] that is an expression appearing explicitly in the source
/// code.
class ExpressionPropertyTarget<Expression extends Object>
extends PropertyTarget<Expression> {
/// The expression whose property is being accessed.
final Expression expression;
ExpressionPropertyTarget(this.expression) : super._();
@override
String toString() => 'ExpressionPropertyTarget($expression)';
@override
SsaNode<Type>? _getSsaNode<Type extends Object>(
covariant _PropertyTargetHelper<Expression, Type> helper) =>
helper._getExpressionReference(expression)?.ssaNode;
}
/// Implementation of flow analysis to be shared between the analyzer and the
/// front end.
///
/// The client should create one instance of this class for every method, field,
/// or top level variable to be analyzed, and call the appropriate methods
/// while visiting the code for type inference.
abstract class FlowAnalysis<Node extends Object, Statement extends Node,
Expression extends Node, Variable extends Object, Type extends Object> {
factory FlowAnalysis(FlowAnalysisOperations<Variable, Type> operations,
AssignedVariables<Node, Variable> assignedVariables,
{required bool respectImplicitlyTypedVarInitializers,
required bool fieldPromotionEnabled}) {
return new _FlowAnalysisImpl(operations, assignedVariables,
respectImplicitlyTypedVarInitializers:
respectImplicitlyTypedVarInitializers,
fieldPromotionEnabled: fieldPromotionEnabled);
}
factory FlowAnalysis.legacy(FlowAnalysisOperations<Variable, Type> operations,
AssignedVariables<Node, Variable> assignedVariables) =
_LegacyTypePromotion<Node, Statement, Expression, Variable, Type>;
/// Return `true` if the current state is reachable.
bool get isReachable;
FlowAnalysisOperations<Variable, Type> get operations;
/// Call this method after visiting an "as" expression.
///
/// [subExpression] should be the expression to which the "as" check was
/// applied. [type] should be the type being checked.
void asExpression_end(Expression subExpression, Type type);
/// Call this method after visiting the condition part of an assert statement
/// (or assert initializer).
///
/// [condition] should be the assert statement's condition.
///
/// See [assert_begin] for more information.
void assert_afterCondition(Expression condition);
/// Call this method before visiting the condition part of an assert statement
/// (or assert initializer).
///
/// The order of visiting an assert statement with no "message" part should
/// be:
/// - Call [assert_begin]
/// - Visit the condition
/// - Call [assert_afterCondition]
/// - Call [assert_end]
///
/// The order of visiting an assert statement with a "message" part should be:
/// - Call [assert_begin]
/// - Visit the condition
/// - Call [assert_afterCondition]
/// - Visit the message
/// - Call [assert_end]
void assert_begin();
/// Call this method after visiting an assert statement (or assert
/// initializer).
///
/// See [assert_begin] for more information.
void assert_end();
/// Call this method after visiting a reference to a variable inside a pattern
/// assignment. [node] is the pattern, [variable] is the referenced variable,
/// and [writtenType] is the type that's written to that variable by the
/// assignment.
void assignedVariablePattern(Node node, Variable variable, Type writtenType);
/// Call this method when the temporary variable holding the result of a
/// pattern match is assigned to a user-accessible variable. (Depending on
/// the client's model, this might happen right after a variable pattern is
/// matched, or later, after one or more logical-or patterns have been
/// handled).
///
/// [promotionKey] is the promotion key used by flow analysis to represent the
/// temporary variable holding the result of the pattern match, and [variable]
/// is the user-accessible variable that the value is being assigned to.
///
/// Returns the promotion key used by flow analysis to represent [variable].
/// This may be used in future calls to [assignMatchedPatternVariable] to
/// handle nested logical-ors, or logical-ors nested within switch cases that
/// share a body.
void assignMatchedPatternVariable(Variable variable, int promotionKey);
/// Call this method when visiting a boolean literal expression.
void booleanLiteral(Expression expression, bool value);
/// Call this method just after visiting the target of a cascade expression.
/// [target] is the target expression (the expression before the first `..` or
/// `?..`), and [targetType] is its static type. [isNullAware] indicates
/// whether the cascade expression is null-aware (meaning its first separator
/// is `?..` rather than `..`).
///
/// Returns the effective type of the target expression during execution of
/// the cascade sections (this is either the same as [targetType], or its
/// non-nullable equivalent, if [isNullAware] is `true`).
///
/// The order of visiting a cascade expression should be:
/// - Visit the target
/// - Call [cascadeExpression_afterTarget].
/// - If this is a null-aware cascade, call [nullAwareAccess_rightBegin].
/// - Visit each cascade section
/// - If this is a null-aware cascade, call [nullAwareAccess_end].
/// - Call [cascadeExpression_end].
Type cascadeExpression_afterTarget(Expression target, Type targetType,
{required bool isNullAware});
/// Call this method just after visiting a cascade expression. See
/// [cascadeExpression_afterTarget] for details.
///
/// [wholeExpression] should be the whole cascade expression.
void cascadeExpression_end(Expression wholeExpression);
/// Call this method just before visiting a conditional expression ("?:").
void conditional_conditionBegin();
/// Call this method upon reaching the ":" part of a conditional expression
/// ("?:"). [thenExpression] should be the expression preceding the ":".
/// [thenType] should be the static type of the expression preceding the ":".
void conditional_elseBegin(Expression thenExpression, Type thenType);
/// Call this method when finishing the visit of a conditional expression
/// ("?:"). [elseExpression] should be the expression following the ":", and
/// [conditionalExpression] should be the whole conditional expression.
/// [elseType] should be the static type of the expression following the ":",
/// and [conditionalExpressionType] should be the static type of the whole
/// conditional expression.
void conditional_end(Expression conditionalExpression,
Type conditionalExpressionType, Expression elseExpression, Type elseType);
/// Call this method upon reaching the "?" part of a conditional expression
/// ("?:"). [condition] should be the expression preceding the "?".
/// [conditionalExpression] should be the entire conditional expression.
void conditional_thenBegin(Expression condition, Node conditionalExpression);
/// Call this method after processing a constant pattern. [expression] should
/// be the pattern's constant expression, and [type] should be its static
/// type.
///
/// [matchedValueType] should be the type returned by [getMatchedValueType].
///
/// If [patternsEnabled] is `true`, pattern support is enabled and this is an
/// ordinary constant pattern. if [patternsEnabled] is `false`, pattern
/// support is disabled and this constant pattern is one of the cases of a
/// legacy switch statement.
void constantPattern_end(Expression expression, Type type,
{required bool patternsEnabled, required Type matchedValueType});
/// Copy promotion data associated with one promotion key to another. This
/// is used after analyzing a branch of a logical-or pattern, to move the
/// promotion data associated with the result of a pattern match on the left
/// hand and right hand sides of the logical-or into a common promotion key,
/// so that promotions will be properly unified when the control flow paths
/// are joined.
void copyPromotionData({required int sourceKey, required int destinationKey});
/// Register a declaration of the [variable] in the current state.
/// Should also be called for function parameters.
///
/// [staticType] should be the static type of the variable (after type
/// inference).
///
/// A local variable is [initialized] if its declaration has an initializer.
/// A function parameter is always initialized, so [initialized] is `true`.
///
/// In debug builds, an assertion will normally verify that no variable gets
/// declared more than once. This assertion may be disabled by passing `true`
/// to [skipDuplicateCheck].
///
/// TODO(paulberry): try to remove all uses of skipDuplicateCheck
void declare(Variable variable, Type staticType,
{required bool initialized, bool skipDuplicateCheck = false});
/// Call this method after visiting a variable pattern in a non-assignment
/// context (or a wildcard pattern).
///
/// [matchedType] should be the static type of the value being matched.
/// [staticType] should be the static type of the variable pattern itself.
/// [isFinal] indicates whether the variable is final, and [isImplicitlyTyped]
/// indicates whether the variable has an explicit type annotation.
///
/// Although pattern variables in Dart cannot be late, the client is allowed
/// to model a traditional (non-patterned) variable declaration statement
/// using the same flow analysis machinery as it uses for pattern variable
/// declaration statements; when it does so, it may use [isLate] to indicate
/// whether the variable in question is a `late` variable.
///
/// Returns the promotion key used by flow analysis to track the temporary
/// variable that holds the matched value.
int declaredVariablePattern(
{required Type matchedType,
required Type staticType,
bool isFinal = false,
bool isLate = false,
required bool isImplicitlyTyped});
/// Call this method before visiting the body of a "do-while" statement.
/// [doStatement] should be the same node that was passed to
/// [AssignedVariables.endNode] for the do-while statement.
void doStatement_bodyBegin(Statement doStatement);
/// Call this method after visiting the body of a "do-while" statement, and
/// before visiting its condition.
void doStatement_conditionBegin();
/// Call this method after visiting the condition of a "do-while" statement.
/// [condition] should be the condition of the loop.
void doStatement_end(Expression condition);
/// Call this method just after visiting either side of a binary `==` or `!=`
/// expression, or an argument to `identical`.
///
/// Returns information about the expression that will later be needed by
/// [equalityOperation_end].
///
/// Note: the return type is nullable because legacy type promotion doesn't
/// need to record information about equality operands.
ExpressionInfo<Type>? equalityOperand_end(Expression operand, Type type);
/// Call this method just after visiting the operands of a binary `==` or `!=`
/// expression, or an invocation of `identical`.
///
/// [leftOperandInfo] and [rightOperandInfo] should be the values returned by
/// [equalityOperand_end].
void equalityOperation_end(
Expression wholeExpression,
ExpressionInfo<Type>? leftOperandInfo,
ExpressionInfo<Type>? rightOperandInfo,
{bool notEqual = false});
/// Call this method after processing a relational pattern that uses an
/// equality operator (either `==` or `!=`). [operand] should be the operand
/// to the right of the operator, [operandType] should be its static type, and
/// [notEqual] should be `true` iff the operator was `!=`.
///
/// [matchedValueType] should be the type returned by [getMatchedValueType].
void equalityRelationalPattern_end(Expression operand, Type operandType,
{bool notEqual = false, required Type matchedValueType});
/// Retrieves the [ExpressionInfo] associated with [target], if known. Will
/// return `null` if (a) no info is associated with [target], or (b) another
/// expression with info has been visited more recently than [target]. For
/// testing only.
ExpressionInfo<Type>? expressionInfoForTesting(Expression target);
/// This method should be called at the conclusion of flow analysis for a top
/// level function or method. Performs assertion checks.
void finish();
/// Call this method just before visiting the body of a conventional "for"
/// statement or collection element. See [for_conditionBegin] for details.
///
/// If a "for" statement is being entered, [node] is an opaque representation
/// of the loop, for use as the target of future calls to [handleBreak] or
/// [handleContinue]. If a "for" collection element is being entered, [node]
/// should be `null`.
///
/// [condition] is an opaque representation of the loop condition; it is
/// matched against expressions passed to previous calls to determine whether
/// the loop condition should cause any promotions to occur. If [condition]
/// is null, the condition is understood to be empty (equivalent to a
/// condition of `true`).
void for_bodyBegin(Statement? node, Expression? condition);
/// Call this method just before visiting the condition of a conventional
/// "for" statement or collection element.
///
/// Note that a conventional "for" statement is a statement of the form
/// `for (initializers; condition; updaters) body`. Statements of the form
/// `for (variable in iterable) body` should use [forEach_bodyBegin]. Similar
/// for "for" collection elements.
///
/// The order of visiting a "for" statement or collection element should be:
/// - Visit the initializers.
/// - Call [for_conditionBegin].
/// - Visit the condition.
/// - Call [for_bodyBegin].
/// - Visit the body.
/// - Call [for_updaterBegin].
/// - Visit the updaters.
/// - Call [for_end].
///
/// [node] should be the same node that was passed to
/// [AssignedVariables.endNode] for the for statement.
void for_conditionBegin(Node node);
/// Call this method just after visiting the updaters of a conventional "for"
/// statement or collection element. See [for_conditionBegin] for details.
void for_end();
/// Call this method just before visiting the updaters of a conventional "for"
/// statement or collection element. See [for_conditionBegin] for details.
void for_updaterBegin();
/// Call this method just before visiting the body of a "for-in" statement or
/// collection element.
///
/// The order of visiting a "for-in" statement or collection element should
/// be:
/// - Visit the iterable expression.
/// - Call [forEach_bodyBegin].
/// - Visit the body.
/// - Call [forEach_end].
///
/// [node] should be the same node that was passed to
/// [AssignedVariables.endNode] for the for statement.
void forEach_bodyBegin(Node node);
/// Call this method just before visiting the body of a "for-in" statement or
/// collection element. See [forEach_bodyBegin] for details.
void forEach_end();
/// Call this method to forward information on [oldExpression] to
/// [newExpression].
///
/// This can be used to preserve promotions through a replacement from
/// [oldExpression] to [newExpression]. For instance when rewriting
///
/// method(int i) {
/// if (i is int) { ... } else { ... }
/// }
///
/// to
///
/// method(int i) {
/// if (i is int || throw ...) { ... } else { ... }
/// }
///
/// the promotion `i is int` can be forwarded to `i is int || throw ...` and
/// there preserved in the surrounding if statement.
void forwardExpression(Expression newExpression, Expression oldExpression);
/// Call this method just before visiting the body of a function expression or
/// local function.
///
/// [node] should be the same node that was passed to
/// [AssignedVariables.endNode] for the function expression.
void functionExpression_begin(Node node);
/// Call this method just after visiting the body of a function expression or
/// local function.
void functionExpression_end();
/// Gets the matched value type that should be used to type check the pattern
/// currently being analyzed.
///
/// May only be called in the context of a pattern.
Type getMatchedValueType();
/// Call this method when visiting a break statement. [target] should be the
/// statement targeted by the break.
///
/// To facilitate error recovery, [target] is allowed to be `null`; if this
/// happens, the break statement is analyzed as though it's an unconditional
/// branch to nowhere (i.e. similar to a `return` or `throw`).
void handleBreak(Statement? target);
/// Call this method when visiting a continue statement. [target] should be
/// the statement targeted by the continue.
///
/// To facilitate error recovery, [target] is allowed to be `null`; if this
/// happens, the continue statement is analyzed as though it's an
/// unconditional branch to nowhere (i.e. similar to a `return` or `throw`).
void handleContinue(Statement? target);
/// Register the fact that the current state definitely exists, e.g. returns
/// from the body, throws an exception, etc.
///
/// Should also be called if a subexpression's type is Never.
void handleExit();
/// Call this method after visiting the scrutinee expression of an if-case
/// statement.
///
/// [scrutinee] is the scrutinee expression, and [scrutineeType] is its static
/// type.
void ifCaseStatement_afterExpression(
Expression scrutinee, Type scrutineeType);
/// Call this method before visiting an if-case statement.
///
/// The order of visiting an if-case statement with no "else" part should be:
/// - Call [ifCaseStatement_begin]
/// - Visit the expression
/// - Call [ifCaseStatement_afterExpression]
/// - Visit the pattern
/// - Visit the guard (if any)
/// - Call [ifCaseStatement_thenBegin]
/// - Visit the "then" statement
/// - Call [ifStatement_end], passing `false` for `hasElse`.
///
/// The order of visiting an if-case statement with an "else" part should be:
/// - Call [ifCaseStatement_begin]
/// - Visit the expression
/// - Call [ifCaseStatement_afterExpression]
/// - Visit the pattern
/// - Visit the guard (if any)
/// - Call [ifCaseStatement_thenBegin]
/// - Visit the "then" statement
/// - Call [ifStatement_elseBegin]
/// - Visit the "else" statement
/// - Call [ifStatement_end], passing `true` for `hasElse`.
void ifCaseStatement_begin();
/// Call this method after visiting pattern and guard parts of an if-case
/// statement.
///
/// [guard] should be the guard expression (if present); otherwise `null`.
void ifCaseStatement_thenBegin(Expression? guard);
/// Call this method after visiting the RHS of an if-null expression ("??")
/// or if-null assignment ("??=").
///
/// Note: for an if-null assignment, the call to [write] should occur before
/// the call to [ifNullExpression_end] (since the write only occurs if the
/// read resulted in a null value).
void ifNullExpression_end();
/// Call this method after visiting the LHS of an if-null expression ("??")
/// or if-null assignment ("??=").
void ifNullExpression_rightBegin(
Expression leftHandSide, Type leftHandSideType);
/// Call this method before visiting the condition part of an if statement.
///
/// The order of visiting an if statement with no "else" part should be:
/// - Call [ifStatement_conditionBegin]
/// - Visit the condition
/// - Call [ifStatement_thenBegin]
/// - Visit the "then" statement
/// - Call [ifStatement_end], passing `false` for `hasElse`.
///
/// The order of visiting an if statement with an "else" part should be:
/// - Call [ifStatement_conditionBegin]
/// - Visit the condition
/// - Call [ifStatement_thenBegin]
/// - Visit the "then" statement
/// - Call [ifStatement_elseBegin]
/// - Visit the "else" statement
/// - Call [ifStatement_end], passing `true` for `hasElse`.
void ifStatement_conditionBegin();
/// Call this method after visiting the "then" part of an if statement, and
/// before visiting the "else" part.
void ifStatement_elseBegin();
/// Call this method after visiting an if statement.
void ifStatement_end(bool hasElse);
/// Call this method after visiting the condition part of an if statement.
/// [condition] should be the if statement's condition. [ifNode] should be
/// the entire `if` statement (or the collection literal entry).
void ifStatement_thenBegin(Expression condition, Node ifNode);
/// Call this method after visiting the initializer of a variable declaration,
/// or a variable pattern that is being matched (and hence being initialized
/// with an implicit value).
///
/// If the initialized value is not known (i.e. because this is a variable
/// pattern that's being matched), pass `null` for [initializerExpression].
void initialize(
Variable variable, Type matchedType, Expression? initializerExpression,
{required bool isFinal,
required bool isLate,
required bool isImplicitlyTyped});
/// Return whether the [variable] is definitely assigned in the current state.
bool isAssigned(Variable variable);
/// Call this method after visiting the LHS of an "is" expression.
///
/// [isExpression] should be the complete expression. [subExpression] should
/// be the expression to which the "is" check was applied. [isNot] should be
/// a boolean indicating whether this is an "is" or an "is!" expression.
/// [type] should be the type being checked.
void isExpression_end(
Expression isExpression, Expression subExpression, bool isNot, Type type);
/// Return whether the [variable] is definitely unassigned in the current
/// state.
bool isUnassigned(Variable variable);
/// Call this method before visiting a labeled statement.
/// Call [labeledStatement_end] after visiting the statement.
void labeledStatement_begin(Statement node);
/// Call this method after visiting a labeled statement.
void labeledStatement_end();
/// Call this method just before visiting the initializer of a late variable.
void lateInitializer_begin(Node node);
/// Call this method just after visiting the initializer of a late variable.
void lateInitializer_end();
/// Call this method before visiting the LHS of a logical binary operation
/// ("||" or "&&").
void logicalBinaryOp_begin();
/// Call this method after visiting the RHS of a logical binary operation
/// ("||" or "&&").
/// [wholeExpression] should be the whole logical binary expression.
/// [rightOperand] should be the RHS. [isAnd] should indicate whether the
/// logical operator is "&&" or "||".
void logicalBinaryOp_end(Expression wholeExpression, Expression rightOperand,
{required bool isAnd});
/// Call this method after visiting the LHS of a logical binary operation
/// ("||" or "&&").
/// [leftOperand] should be the LHS. [isAnd] should indicate whether the
/// logical operator is "&&" or "||". [wholeExpression] should be the whole
/// logical binary expression.
void logicalBinaryOp_rightBegin(Expression leftOperand, Node wholeExpression,
{required bool isAnd});
/// Call this method after visiting a logical not ("!") expression.
/// [notExpression] should be the complete expression. [operand] should be
/// the subexpression whose logical value is being negated.
void logicalNot_end(Expression notExpression, Expression operand);
/// Call this method after visiting the left hand side of a logical-or (`||`)
/// pattern.
void logicalOrPattern_afterLhs();
/// Call this method before visiting a logical-or (`||`) pattern.
void logicalOrPattern_begin();
/// Call this method after visiting a logical-or (`||`) pattern.
void logicalOrPattern_end();
/// Call this method after processing a relational pattern that uses a
/// non-equality operator (any operator other than `==` or `!=`).
void nonEqualityRelationalPattern_end();
/// Call this method just after visiting a non-null assertion (`x!`)
/// expression.
void nonNullAssert_end(Expression operand);
/// Call this method after visiting an expression using `?.`.
void nullAwareAccess_end();
/// Call this method after visiting a null-aware operator such as `?.`,
/// `?..`, `?.[`, or `?..[`.
///
/// [target] should be the expression just before the null-aware operator, or
/// `null` if the null-aware access starts a cascade section.
///
/// [targetType] should be the type of the expression just before the
/// null-aware operator, and should be non-null even if the null-aware access
/// starts a cascade section.
///
/// Note that [nullAwareAccess_end] should be called after the conclusion
/// of any null-shorting that is caused by the `?.`. So, for example, if the
/// code being analyzed is `x?.y?.z(x)`, [nullAwareAccess_rightBegin] should
/// be called once upon reaching each `?.`, but [nullAwareAccess_end] should
/// not be called until after processing the method call to `z(x)`.
void nullAwareAccess_rightBegin(Expression? target, Type targetType);
/// Call this method after visiting the value of a null-aware map entry.
void nullAwareMapEntry_end({required bool isKeyNullAware});
/// Call this method after visiting the key of a null-aware map entry.
void nullAwareMapEntry_valueBegin(Expression key, Type keyType,
{required bool isKeyNullAware});
/// Call this method before visiting the subpattern of a null-check or a
/// null-assert pattern. [isAssert] indicates whether the pattern is a
/// null-check or a null-assert pattern.
///
/// [matchedValueType] should be the type returned by [getMatchedValueType].
bool nullCheckOrAssertPattern_begin(
{required bool isAssert, required Type matchedValueType});
/// Call this method after visiting the subpattern of a null-check or a
/// null-assert pattern.
void nullCheckOrAssertPattern_end();
/// Call this method when encountering an expression that is a `null` literal.
/// [type] should be the static type of the literal (i.e. the type `Null`).
void nullLiteral(Expression expression, Type type);
/// Call this method just after visiting a parenthesized expression.
///
/// This is only necessary if the implementation uses a different [Expression]
/// object to represent a parenthesized expression and its contents.
void parenthesizedExpression(
Expression outerExpression, Expression innerExpression);
/// Call this method just after visiting the right hand side of a pattern
/// assignment expression, and before visiting the pattern.
///
/// [rhs] is the right hand side expression, and [rhsType] is its static type.
void patternAssignment_afterRhs(Expression rhs, Type rhsType);
/// Call this method after visiting a pattern assignment expression.
void patternAssignment_end();
/// Call this method just after visiting the expression (which usually
/// implements `Iterable`, but can also be `dynamic`), and before visiting
/// the pattern or body.
///
/// [elementType] is the element type of the `Iterable`, or `dynamic`.
void patternForIn_afterExpression(Type elementType);
/// Call this method after visiting the body.
void patternForIn_end();
/// Call this method just after visiting the initializer of a pattern variable
/// declaration, and before visiting the pattern.
///
/// [initializer] is the declaration's initializer expression, and
/// [initializerType] is its static type.
void patternVariableDeclaration_afterInitializer(
Expression initializer, Type initializerType);
/// Call this method after visiting the pattern of a pattern variable
/// declaration.
void patternVariableDeclaration_end();
/// Call this method after visiting the subpattern of an object pattern, to
/// restore the state that was saved by [pushPropertySubpattern].
void popPropertySubpattern();
/// Call this method after visiting a pattern's subpattern, to restore the
/// state that was saved by [pushSubpattern].
void popSubpattern();
/// Retrieves the type that a property named [propertyName] is promoted to, if
/// the property is currently promoted. Otherwise returns `null`.
///
/// The [target] parameter determines how the property is being looked up. If
/// it is [ExpressionPropertyTarget], a property of an expression is being
/// queried, and this method should be called just after visiting the
/// expression. If it is [ThisPropertyTarget], a property of `this` is being
/// queried. If it is [SuperPropertyTarget], a property of `super` is being
/// queried.
///
/// [propertyMember] should be whatever data structure the client uses to keep
/// track of the field or property being accessed. If not `null`, and field
/// promotion is enabled for the current library,
/// [FlowAnalysisOperations.isPropertyPromotable] will be consulted to find
/// out whether the property is promotable. [unpromotedType] should be the
/// static type of the value returned by the property get.
///
/// Note: although only fields can be promoted, this method uses the
/// nomenclature "property" rather than "field", to highlight the fact that
/// it is not necessary for the client to check whether a property refers to a
/// field before calling this method; if the property does not refer to a
/// field, `null` will be returned.
Type? promotedPropertyType(PropertyTarget<Expression> target,
String propertyName, Object? propertyMember, Type unpromotedType);
/// Retrieves the type that the [variable] is promoted to, if the [variable]
/// is currently promoted. Otherwise returns `null`.
Type? promotedType(Variable variable);
/// Call this method when visiting a pattern whose semantics constrain the
/// type of the matched value. This could be due to a required type of a
/// declared variable pattern, list pattern, map pattern, record pattern,
/// object pattern, or wildcard pattern, or it could be due to the
/// demonstrated type of a record pattern.
///
/// [matchedType] should be the matched value type, and [knownType] should
/// be the type that the matched value is now known to satisfy.
///
/// If [matchFailsIfWrongType] is `true` (the default), flow analysis models
/// the usual semantics of a type test in a pattern: if the matched value
/// fails to have the type [knownType], the pattern will fail to match.
/// If it is `false`, it models the semantics where the no match failure can
/// occur (either because the matched value is known, due to other invariants
/// to have the type [knownType], or because a type test failure would result
/// in an exception being thrown).
///
/// If [matchMayFailEvenIfCorrectType] is `true`, flow analysis would always
/// update the unmatched value.
///
/// Returns `true` if [matchedType] is a subtype of [knownType] (and thus the
/// user might need to be warned of an unnecessary cast or unnecessary
/// wildcard pattern).
bool promoteForPattern(
{required Type matchedType,
required Type knownType,
bool matchFailsIfWrongType = true,
bool matchMayFailEvenIfCorrectType = false});
/// Call this method just after visiting a property get expression.
/// [wholeExpression] should be the whole property get, and [propertyName]
/// should be the identifier to the right hand side of the `.`.
/// [unpromotedType] should be the static type of the value returned by the
/// property get.
///
/// The [target] parameter determines how the property is being looked up. If
/// it is [ExpressionPropertyTarget], a property of an expression was just
/// visited, and this method should be called just after visiting the
/// expression. If it is [ThisPropertyTarget], a property of `this` was just
/// visited. If it is [SuperPropertyTarget], a property of `super` was just
/// visited.
///
/// [wholeExpression] is used by flow analysis to detect the case where the
/// property get is used as a subexpression of a larger expression that
/// participates in promotion (e.g. promotion of a property of a property).
/// If there is no expression corresponding to the property get (e.g. because
/// the property is being invoked like a method, or the property get is part
/// of a compound assignment), [wholeExpression] may be `null`.
///
/// [propertyMember] should be whatever data structure the client uses to keep
/// track of the field or property being accessed. If not `null`, and field
/// promotion is enabled for the current library,
/// [FlowAnalysisOperations.isPropertyPromotable] will be consulted to find
/// out whether the property is promotable. In the event of non-promotion of
/// a property get, this value can be retrieved from
/// [PropertyNotPromoted.propertyMember].
///
/// If the property's type is currently promoted, the promoted type is
/// returned. Otherwise `null` is returned.
Type? propertyGet(
Expression? wholeExpression,
PropertyTarget<Expression> target,
String propertyName,
Object? propertyMember,
Type unpromotedType);
/// Call this method just before analyzing a subpattern of an object pattern.
///
/// [propertyName] is the name of the property being accessed by this
/// subpattern, [propertyMember] is the data structure the client uses to keep
/// track of the field or property being accessed (as would be passed to
/// [propertyGet]), and [unpromotedType] is the static type of the field or
/// property.
///
/// If the property's type is currently promoted, the promoted type is
/// returned. Otherwise `null` is returned.
Type? pushPropertySubpattern(
String propertyName, Object? propertyMember, Type unpromotedType);
/// Call this method just before analyzing a subpattern of a pattern.
///
/// [matchedType] is the type that should be used to type check the
/// subpattern.
///
/// Flow analysis makes no assumptions about the relation between the matched
/// value for the outer pattern and the subpattern.
void pushSubpattern(Type matchedType);
/// Retrieves the SSA node associated with [variable], or `null` if [variable]
/// is not associated with an SSA node because it is write captured. For
/// testing only.
@visibleForTesting
SsaNode<Type>? ssaNodeForTesting(Variable variable);
/// Call this method just after visiting a `case` or `default` body. See
/// [switchStatement_expressionEnd] for details.
///
/// This method returns a boolean indicating whether the end of the case body
/// is "locally reachable" (i.e. reachable from its start).
bool switchStatement_afterCase();
/// Call this method just before visiting a `case` or `default` clause. See
/// [switchStatement_expressionEnd] for details.
void switchStatement_beginAlternative();
/// Call this method just before visiting a sequence of one or more `case` or
/// `default` clauses that share a body. See [switchStatement_expressionEnd]
/// for details.
void switchStatement_beginAlternatives();
/// Call this method just after visiting the body of a switch statement. See
/// [switchStatement_expressionEnd] for details.
///
/// [isExhaustive] indicates whether the switch statement had a "default"
/// case, or is based on an enumeration and all the enumeration constants
/// were listed in cases.
///
/// Returns a boolean indicating whether flow analysis was able to prove the
/// switch statement to be exhaustive (e.g. due to the presence of a `default`
/// clause, or a pattern that is guaranteed to match the scrutinee type).
bool switchStatement_end(bool isExhaustive);
/// Call this method just after visiting a `case` or `default` clause. See
/// [switchStatement_expressionEnd] for details.`
///
/// [guard] should be the expression following the `when` keyword, if present.
///
/// If the clause is a `case` clause, [variables] should contain an entry for
/// all variables defined by the clause's pattern; the key should be the
/// variable name and the value should be the variable itself. If the clause
/// is a `default` clause, [variables] should be an empty map.
void switchStatement_endAlternative(
Expression? guard, Map<String, Variable> variables);
/// Call this method just after visiting a sequence of one or more `case` or
/// `default` clauses that share a body. See [switchStatement_expressionEnd]
/// for details.`
///
/// [node] should be the same node that was passed to
/// [AssignedVariables.endNode] for the switch statement.
///
/// [hasLabels] indicates whether the case has any labels.
///
/// Returns a data structure describing the relationship among variables
/// defined by patterns in the various alternatives.
PatternVariableInfo<Variable> switchStatement_endAlternatives(Statement? node,
{required bool hasLabels});
/// Call this method just after visiting the expression part of a switch
/// statement or expression. [switchStatement] should be the switch statement
/// itself (or `null` if this is a switch expression).
///
/// The order of visiting a switch statement should be:
/// - Visit the switch expression.
/// - Call [switchStatement_expressionEnd].
/// - For each case body:
/// - Call [switchStatement_beginAlternatives].
/// - For each `case` or `default` clause associated with this case body:
/// - Call [switchStatement_beginAlternative].
/// - If a pattern is present, visit it.
/// - If a guard is present, visit it.
/// - Call [switchStatement_endAlternative].
/// - Call [switchStatement_endAlternatives].
/// - Visit the case body.
/// - Call [switchStatement_afterCase].
/// - Call [switchStatement_end].
///
/// [scrutinee] should be the expression appearing in parentheses after the
/// `switch` keyword, and [scrutineeType] should be its static type.
void switchStatement_expressionEnd(
Statement? switchStatement, Expression scrutinee, Type scrutineeType);
/// Call this method just after visiting the expression `this` (or the
/// pseudo-expression `super`, in the case of the analyzer, which represents
/// `super.x` as a property get whose target is `super`). [expression] should
/// be the `this` or `super` expression. [staticType] should be the static
/// type of `this`.
///
/// [isSuper] indicates whether the expression that was visited was the
/// pseudo-expression `super`.
void thisOrSuper(Expression expression, Type staticType,
{required bool isSuper});
/// Call this method just before visiting the body of a "try/catch" statement.
///
/// The order of visiting a "try/catch" statement should be:
/// - Call [tryCatchStatement_bodyBegin]
/// - Visit the try block
/// - Call [tryCatchStatement_bodyEnd]
/// - For each catch block:
/// - Call [tryCatchStatement_catchBegin]
/// - Call [initialize] for the exception and stack trace variables
/// - Visit the catch block
/// - Call [tryCatchStatement_catchEnd]
/// - Call [tryCatchStatement_end]
///
/// The order of visiting a "try/catch/finally" statement should be:
/// - Call [tryFinallyStatement_bodyBegin]
/// - Call [tryCatchStatement_bodyBegin]
/// - Visit the try block
/// - Call [tryCatchStatement_bodyEnd]
/// - For each catch block:
/// - Call [tryCatchStatement_catchBegin]
/// - Call [initialize] for the exception and stack trace variables
/// - Visit the catch block
/// - Call [tryCatchStatement_catchEnd]
/// - Call [tryCatchStatement_end]
/// - Call [tryFinallyStatement_finallyBegin]
/// - Visit the finally block
/// - Call [tryFinallyStatement_end]
void tryCatchStatement_bodyBegin();
/// Call this method just after visiting the body of a "try/catch" statement.
/// See [tryCatchStatement_bodyBegin] for details.
///
/// [body] should be the same node that was passed to
/// [AssignedVariables.endNode] for the "try" part of the try/catch statement.
void tryCatchStatement_bodyEnd(Node body);
/// Call this method just before visiting a catch clause of a "try/catch"
/// statement. See [tryCatchStatement_bodyBegin] for details.
///
/// [exceptionVariable] should be the exception variable declared by the catch
/// clause, or `null` if there is no exception variable. Similar for
/// [stackTraceVariable].
void tryCatchStatement_catchBegin(
Variable? exceptionVariable, Variable? stackTraceVariable);
/// Call this method just after visiting a catch clause of a "try/catch"
/// statement. See [tryCatchStatement_bodyBegin] for details.
void tryCatchStatement_catchEnd();
/// Call this method just after visiting a "try/catch" statement. See
/// [tryCatchStatement_bodyBegin] for details.
void tryCatchStatement_end();
/// Call this method just before visiting the body of a "try/finally"
/// statement.
///
/// The order of visiting a "try/finally" statement should be:
/// - Call [tryFinallyStatement_bodyBegin]
/// - Visit the try block
/// - Call [tryFinallyStatement_finallyBegin]
/// - Visit the finally block
/// - Call [tryFinallyStatement_end]
///
/// See [tryCatchStatement_bodyBegin] for the order of visiting a
/// "try/catch/finally" statement.
void tryFinallyStatement_bodyBegin();
/// Call this method just after visiting a "try/finally" statement.
/// See [tryFinallyStatement_bodyBegin] for details.
void tryFinallyStatement_end();
/// Call this method just before visiting the finally block of a "try/finally"
/// statement. See [tryFinallyStatement_bodyBegin] for details.
///
/// [body] should be the same node that was passed to
/// [AssignedVariables.endNode] for the "try" part of the try/finally
/// statement.
void tryFinallyStatement_finallyBegin(Node body);
/// Call this method when encountering an expression that reads the value of
/// a variable.
///
/// If the variable's type is currently promoted, the promoted type is
/// returned. Otherwise `null` is returned.
Type? variableRead(Expression expression, Variable variable);
/// Call this method after visiting the condition part of a "while" statement.
/// [whileStatement] should be the full while statement. [condition] should
/// be the condition part of the while statement.
void whileStatement_bodyBegin(Statement whileStatement, Expression condition);
/// Call this method before visiting the condition part of a "while"
/// statement.
///
/// [node] should be the same node that was passed to
/// [AssignedVariables.endNode] for the while statement.
void whileStatement_conditionBegin(Node node);
/// Call this method after visiting a "while" statement.
void whileStatement_end();
/// Call this method when an error occurs that may be due to a lack of type
/// promotion, to retrieve information about why [target] was not promoted.
/// This call must be made right after visiting [target].
///
/// The returned value is a function yielding a map whose keys are types that
/// the user might have been expecting the target to be promoted to, and whose
/// values are reasons why the corresponding promotion did not occur. The
/// caller is expected to select which non-promotion reason to report to the
/// user by seeing which promotion would have prevented the error. (For
/// example, if an error occurs due to the target having a nullable type, the
/// caller should report a non-promotion reason associated with non-promotion
/// to a non-nullable type).
///
/// This method is expected to execute fairly efficiently; the bulk of the
/// expensive computation is deferred to the function it returns. The reason
/// for this is that in certain cases, it's not possible to know whether "why
/// not promoted" information will be needed until long after visiting a node.
/// (For example, in resolving a call like
/// `(x as Future<T>).then(y, onError: z)`, we don't know whether an error
/// should be reported at `y` until we've inferred the type argument to
/// `then`, which doesn't occur until after visiting `z`). So the caller may
/// freely call this method after any expression for which an error *might*
/// need to be generated, and then defer invoking the returned function until
/// it is determined that an error actually occurred.
Map<Type, NonPromotionReason> Function() whyNotPromoted(Expression target);
/// Call this method when an error occurs that may be due to a lack of type
/// promotion, to retrieve information about why an implicit reference to
/// `this` was not promoted. [staticType] is the (unpromoted) type of `this`.
///
/// The returned value is a function yielding a map whose keys are types that
/// the user might have been expecting `this` to be promoted to, and whose
/// values are reasons why the corresponding promotion did not occur. The
/// caller is expected to select which non-promotion reason to report to the
/// user by seeing which promotion would have prevented the error. (For
/// example, if an error occurs due to the target having a nullable type, the
/// caller should report a non-promotion reason associated with non-promotion
/// to a non-nullable type).
///
/// This method is expected to execute fairly efficiently; the bulk of the
/// expensive computation is deferred to the function it returns. The reason
/// for this is that in certain cases, it's not possible to know whether "why
/// not promoted" information will be needed until long after visiting a node.
/// (For example, in resolving a call like
/// `(x as Future<T>).then(y, onError: z)`, we don't know whether an error
/// should be reported at `y` until we've inferred the type argument to
/// `then`, which doesn't occur until after visiting `z`). So the caller may
/// freely call this method after any expression for which an error *might*
/// need to be generated, and then defer invoking the returned function until
/// it is determined that an error actually occurred.
Map<Type, NonPromotionReason> Function() whyNotPromotedImplicitThis(
Type staticType);
/// Register write of the given [variable] in the current state.
/// [writtenType] should be the type of the value that was written.
/// [node] should be the syntactic construct performing the write.
/// [writtenExpression] should be the expression that was written, or `null`
/// if the expression that was written is not directly represented in the
/// source code (this happens, for example, with compound assignments and with
/// for-each loops).
///
/// This method should not be used for the implicit write to a non-final
/// variable in its initializer; in that case, use [initialize] instead.
void write(Node node, Variable variable, Type writtenType,
Expression? writtenExpression);
/// Prints out a summary of the current state of flow analysis, intended for
/// debugging use only.
void _dumpState();
}
/// Alternate implementation of [FlowAnalysis] that prints out inputs and output
/// at the API boundary, for assistance in debugging.
class FlowAnalysisDebug<Node extends Object, Statement extends Node,
Expression extends Node, Variable extends Object, Type extends Object>
implements FlowAnalysis<Node, Statement, Expression, Variable, Type> {
static int _nextCallbackId = 0;
static Expando<String> _description = new Expando<String>();
FlowAnalysis<Node, Statement, Expression, Variable, Type> _wrapped;
bool _exceptionOccurred = false;
factory FlowAnalysisDebug(FlowAnalysisOperations<Variable, Type> operations,
AssignedVariables<Node, Variable> assignedVariables,
{required bool respectImplicitlyTypedVarInitializers,
required bool fieldPromotionEnabled}) {
print('FlowAnalysisDebug()');
return new FlowAnalysisDebug._(new _FlowAnalysisImpl(
operations, assignedVariables,
respectImplicitlyTypedVarInitializers:
respectImplicitlyTypedVarInitializers,
fieldPromotionEnabled: fieldPromotionEnabled));
}
factory FlowAnalysisDebug.legacy(
FlowAnalysisOperations<Variable, Type> operations,
AssignedVariables<Node, Variable> assignedVariables) {
print('FlowAnalysisDebug.legacy()');
return new FlowAnalysisDebug._(
new _LegacyTypePromotion(operations, assignedVariables));
}
FlowAnalysisDebug._(this._wrapped);
@override
bool get isReachable =>
_wrap('isReachable', () => _wrapped.isReachable, isQuery: true);
@override
FlowAnalysisOperations<Variable, Type> get operations => _wrapped.operations;
@override
void asExpression_end(Expression subExpression, Type type) {
_wrap('asExpression_end($subExpression, $type)',
() => _wrapped.asExpression_end(subExpression, type));
}
@override
void assert_afterCondition(Expression condition) {
_wrap('assert_afterCondition($condition)',
() => _wrapped.assert_afterCondition(condition));
}
@override
void assert_begin() {
_wrap('assert_begin()', () => _wrapped.assert_begin());
}
@override
void assert_end() {
_wrap('assert_end()', () => _wrapped.assert_end());
}
@override
void assignedVariablePattern(Node node, Variable variable, Type writtenType) {
_wrap('assignedVariablePattern($node, $variable, $writtenType)',
() => _wrapped.assignedVariablePattern(node, variable, writtenType));
}
@override
void assignMatchedPatternVariable(Variable variable, int promotionKey) {
_wrap('assignMatchedPatternVariable($variable, $promotionKey)',
() => _wrapped.assignMatchedPatternVariable(variable, promotionKey));
}
@override
void booleanLiteral(Expression expression, bool value) {
_wrap('booleanLiteral($expression, $value)',
() => _wrapped.booleanLiteral(expression, value));
}
@override
Type cascadeExpression_afterTarget(Expression target, Type targetType,
{required bool isNullAware}) {
return _wrap(
'cascadeExpression_afterTarget($target, $targetType, '
'isNullAware: $isNullAware)',
() => _wrapped.cascadeExpression_afterTarget(target, targetType,
isNullAware: isNullAware),
isQuery: true,
isPure: false);
}
@override
void cascadeExpression_end(Expression wholeExpression) {
_wrap('cascadeExpression_end($wholeExpression)',
() => _wrapped.cascadeExpression_end(wholeExpression));
}
@override
void conditional_conditionBegin() {
_wrap('conditional_conditionBegin()',
() => _wrapped.conditional_conditionBegin());
}
@override
void conditional_elseBegin(Expression thenExpression, Type thenType) {
_wrap('conditional_elseBegin($thenExpression, $thenType)',
() => _wrapped.conditional_elseBegin(thenExpression, thenType));
}
@override
void conditional_end(
Expression conditionalExpression,
Type conditionalExpressionType,
Expression elseExpression,
Type elseType) {
_wrap(
'conditional_end($conditionalExpression, $conditionalExpressionType, '
'$elseExpression, $elseType)',
() => _wrapped.conditional_end(conditionalExpression,
conditionalExpressionType, elseExpression, elseType));
}
@override
void conditional_thenBegin(Expression condition, Node conditionalExpression) {
_wrap('conditional_thenBegin($condition, $conditionalExpression)',
() => _wrapped.conditional_thenBegin(condition, conditionalExpression));
}
@override
void constantPattern_end(Expression expression, Type type,
{required bool patternsEnabled, required Type matchedValueType}) {
_wrap(
'constantPattern_end($expression, $type, '
'patternsEnabled: $patternsEnabled, '
'matchedValueType: $matchedValueType)',
() => _wrapped.constantPattern_end(expression, type,
patternsEnabled: patternsEnabled,
matchedValueType: matchedValueType));
}
@override
void copyPromotionData(
{required int sourceKey, required int destinationKey}) {
_wrap(
'copyPromotionData(sourceKey: $sourceKey, '
'destinationKey: $destinationKey)',
() => _wrapped.copyPromotionData(
sourceKey: sourceKey, destinationKey: destinationKey));
}
@override
void declare(Variable variable, Type staticType,
{required bool initialized, bool skipDuplicateCheck = false}) {
_wrap(
'declare($variable, $staticType, '
'initialized: $initialized, skipDuplicateCheck: $skipDuplicateCheck)',
() => _wrapped.declare(variable, staticType,
initialized: initialized, skipDuplicateCheck: skipDuplicateCheck));
}
@override
int declaredVariablePattern(
{required Type matchedType,
required Type staticType,
bool isFinal = false,
bool isLate = false,
required bool isImplicitlyTyped}) {
return _wrap(
'declaredVariablePattern(matchedType: $matchedType, '
'staticType: $staticType, isFinal: $isFinal, '
'isLate: $isLate, isImplicitlyTyped: $isImplicitlyTyped)',
() => _wrapped.declaredVariablePattern(
matchedType: matchedType,
staticType: staticType,
isFinal: isFinal,
isLate: isLate,
isImplicitlyTyped: isImplicitlyTyped),
isQuery: true,
isPure: false);
}
@override
void doStatement_bodyBegin(Statement doStatement) {
return _wrap('doStatement_bodyBegin($doStatement)',
() => _wrapped.doStatement_bodyBegin(doStatement));
}
@override
void doStatement_conditionBegin() {
return _wrap('doStatement_conditionBegin()',
() => _wrapped.doStatement_conditionBegin());
}
@override
void doStatement_end(Expression condition) {
return _wrap('doStatement_end($condition)',
() => _wrapped.doStatement_end(condition));
}
@override
ExpressionInfo<Type>? equalityOperand_end(Expression operand, Type type) =>
_wrap('equalityOperand_end($operand, $type)',
() => _wrapped.equalityOperand_end(operand, type),
isQuery: true);
@override
void equalityOperation_end(
Expression wholeExpression,
ExpressionInfo<Type>? leftOperandInfo,
ExpressionInfo<Type>? rightOperandInfo,
{bool notEqual = false}) {
_wrap(
'equalityOperation_end($wholeExpression, $leftOperandInfo, '
'$rightOperandInfo, notEqual: $notEqual)',
() => _wrapped.equalityOperation_end(
wholeExpression, leftOperandInfo, rightOperandInfo,
notEqual: notEqual));
}
@override
void equalityRelationalPattern_end(Expression operand, Type operandType,
{bool notEqual = false, required Type matchedValueType}) {
_wrap(
'equalityRelationalPattern_end($operand, $operandType, '
'notEqual: $notEqual, matchedValueType: $matchedValueType)',
() => _wrapped.equalityRelationalPattern_end(operand, operandType,
notEqual: notEqual, matchedValueType: matchedValueType));
}
@override
ExpressionInfo<Type>? expressionInfoForTesting(Expression target) {
return _wrap('expressionInfoForTesting($target)',
() => _wrapped.expressionInfoForTesting(target),
isQuery: true);
}
@override
void finish() {
if (_exceptionOccurred) {
_wrap('finish() (skipped)', () {}, isPure: true);
} else {
_wrap('finish()', () => _wrapped.finish(), isPure: true);
}
}
@override
void for_bodyBegin(Statement? node, Expression? condition) {
_wrap('for_bodyBegin($node, $condition)',
() => _wrapped.for_bodyBegin(node, condition));
}
@override
void for_conditionBegin(Node node) {
_wrap('for_conditionBegin($node)', () => _wrapped.for_conditionBegin(node));
}
@override
void for_end() {
_wrap('for_end()', () => _wrapped.for_end());
}
@override
void for_updaterBegin() {
_wrap('for_updaterBegin()', () => _wrapped.for_updaterBegin());
}
@override
void forEach_bodyBegin(Node node) {
return _wrap(
'forEach_bodyBegin($node)', () => _wrapped.forEach_bodyBegin(node));
}
@override
void forEach_end() {
return _wrap('forEach_end()', () => _wrapped.forEach_end());
}
@override
void forwardExpression(Expression newExpression, Expression oldExpression) {
return _wrap('forwardExpression($newExpression, $oldExpression)',
() => _wrapped.forwardExpression(newExpression, oldExpression));
}
@override
void functionExpression_begin(Node node) {
_wrap('functionExpression_begin($node)',
() => _wrapped.functionExpression_begin(node));
}
@override
void functionExpression_end() {
_wrap('functionExpression_end()', () => _wrapped.functionExpression_end());
}
@override
Type getMatchedValueType() {
return _wrap('getMatchedValueType()', () => _wrapped.getMatchedValueType(),
isQuery: true);
}
@override
void handleBreak(Statement? target) {
_wrap('handleBreak($target)', () => _wrapped.handleBreak(target));
}
@override
void handleContinue(Statement? target) {
_wrap('handleContinue($target)', () => _wrapped.handleContinue(target));
}
@override
void handleExit() {
_wrap('handleExit()', () => _wrapped.handleExit());
}
@override
void ifCaseStatement_afterExpression(
Expression scrutinee, Type scrutineeType) {
_wrap(
'ifCaseStatement_afterExpression($scrutinee, $scrutineeType)',
() =>
_wrapped.ifCaseStatement_afterExpression(scrutinee, scrutineeType));
}
@override
void ifCaseStatement_begin() {
_wrap('ifCaseStatement_begin()', () => _wrapped.ifCaseStatement_begin());
}
@override
void ifCaseStatement_thenBegin(Expression? guard) {
_wrap('ifCaseStatement_thenBegin($guard)',
() => _wrapped.ifCaseStatement_thenBegin(guard));
}
@override
void ifNullExpression_end() {
return _wrap(
'ifNullExpression_end()', () => _wrapped.ifNullExpression_end());
}
@override
void ifNullExpression_rightBegin(
Expression leftHandSide, Type leftHandSideType) {
_wrap(
'ifNullExpression_rightBegin($leftHandSide, $leftHandSideType)',
() => _wrapped.ifNullExpression_rightBegin(
leftHandSide, leftHandSideType));
}
@override
void ifStatement_conditionBegin() {
return _wrap('ifStatement_conditionBegin()',
() => _wrapped.ifStatement_conditionBegin());
}
@override
void ifStatement_elseBegin() {
return _wrap(
'ifStatement_elseBegin()', () => _wrapped.ifStatement_elseBegin());
}
@override
void ifStatement_end(bool hasElse) {
_wrap('ifStatement_end($hasElse)', () => _wrapped.ifStatement_end(hasElse));
}
@override
void ifStatement_thenBegin(Expression condition, Node ifNode) {
_wrap('ifStatement_thenBegin($condition, $ifNode)',
() => _wrapped.ifStatement_thenBegin(condition, ifNode));
}
@override
void initialize(
Variable variable, Type matchedType, Expression? initializerExpression,
{required bool isFinal,
required bool isLate,
required bool isImplicitlyTyped}) {
_wrap(
'initialize($variable, $matchedType, $initializerExpression, '
'isFinal: $isFinal, isLate: $isLate, '
'isImplicitlyTyped: $isImplicitlyTyped)',
() => _wrapped.initialize(variable, matchedType, initializerExpression,
isFinal: isFinal,
isLate: isLate,
isImplicitlyTyped: isImplicitlyTyped));
}
@override
bool isAssigned(Variable variable) {
return _wrap('isAssigned($variable)', () => _wrapped.isAssigned(variable),
isQuery: true);
}
@override
void isExpression_end(Expression isExpression, Expression subExpression,
bool isNot, Type type) {
_wrap(
'isExpression_end($isExpression, $subExpression, $isNot, $type)',
() => _wrapped.isExpression_end(
isExpression, subExpression, isNot, type));
}
@override
bool isUnassigned(Variable variable) {
return _wrap(
'isUnassigned($variable)', () => _wrapped.isUnassigned(variable),
isQuery: true);
}
@override
void labeledStatement_begin(Statement node) {
return _wrap('labeledStatement_begin($node)',
() => _wrapped.labeledStatement_begin(node));
}
@override
void labeledStatement_end() {
return _wrap(
'labeledStatement_end()', () => _wrapped.labeledStatement_end());
}
@override
void lateInitializer_begin(Node node) {
_wrap('lateInitializer_begin($node)',
() => _wrapped.lateInitializer_begin(node));
}
@override
void lateInitializer_end() {
_wrap('lateInitializer_end()', () => _wrapped.lateInitializer_end());
}
@override
void logicalBinaryOp_begin() {
_wrap('logicalBinaryOp_begin()', () => _wrapped.logicalBinaryOp_begin());
}
@override
void logicalBinaryOp_end(Expression wholeExpression, Expression rightOperand,
{required bool isAnd}) {
_wrap(
'logicalBinaryOp_end($wholeExpression, $rightOperand, isAnd: $isAnd)',
() => _wrapped.logicalBinaryOp_end(wholeExpression, rightOperand,
isAnd: isAnd));
}
@override
void logicalBinaryOp_rightBegin(Expression leftOperand, Node wholeExpression,
{required bool isAnd}) {
_wrap(
'logicalBinaryOp_rightBegin($leftOperand, $wholeExpression, '
'isAnd: $isAnd)',
() => _wrapped.logicalBinaryOp_rightBegin(leftOperand, wholeExpression,
isAnd: isAnd));
}
@override
void logicalNot_end(Expression notExpression, Expression operand) {
return _wrap('logicalNot_end($notExpression, $operand)',
() => _wrapped.logicalNot_end(notExpression, operand));
}
@override
void logicalOrPattern_afterLhs() {
_wrap('logicalOrPattern_afterLhs()',
() => _wrapped.logicalOrPattern_afterLhs());
}
@override
void logicalOrPattern_begin() {
_wrap('logicalOrPattern_begin()', () => _wrapped.logicalOrPattern_begin());
}
@override
void logicalOrPattern_end() {
_wrap('logicalOrPattern_end()', () => _wrapped.logicalOrPattern_end());
}
@override
void nonEqualityRelationalPattern_end() {
_wrap('nonEqualityRelationalPattern_end()',
() => _wrapped.nonEqualityRelationalPattern_end());
}
@override
void nonNullAssert_end(Expression operand) {
return _wrap('nonNullAssert_end($operand)',
() => _wrapped.nonNullAssert_end(operand));
}
@override
void nullAwareAccess_end() {
_wrap('nullAwareAccess_end()', () => _wrapped.nullAwareAccess_end());
}
@override
void nullAwareAccess_rightBegin(Expression? target, Type targetType) {
_wrap('nullAwareAccess_rightBegin($target, $targetType)',
() => _wrapped.nullAwareAccess_rightBegin(target, targetType));
}
@override
void nullAwareMapEntry_end({required bool isKeyNullAware}) {
return _wrap('nullAwareMapEntry_end(isKeyNullAware: $isKeyNullAware)',
() => _wrapped.nullAwareMapEntry_end(isKeyNullAware: isKeyNullAware));
}
@override
void nullAwareMapEntry_valueBegin(Expression key, Type keyType,
{required bool isKeyNullAware}) {
_wrap(
'nullAwareMapEntry_valueBegin($key, $keyType, '
'isKeyNullAware: $isKeyNullAware)',
() => _wrapped.nullAwareMapEntry_valueBegin(key, keyType,
isKeyNullAware: isKeyNullAware));
}
@override
bool nullCheckOrAssertPattern_begin(
{required bool isAssert, required Type matchedValueType}) {
return _wrap(
'nullCheckOrAssertPattern_begin(isAssert: $isAssert, '
'matchedValueType: $matchedValueType)',
() => _wrapped.nullCheckOrAssertPattern_begin(
isAssert: isAssert, matchedValueType: matchedValueType),
isQuery: true,
isPure: false);
}
@override
void nullCheckOrAssertPattern_end() {
_wrap('nullCheckOrAssertPattern_end()',
() => _wrapped.nullCheckOrAssertPattern_end());
}
@override
void nullLiteral(Expression expression, Type type) {
_wrap('nullLiteral($expression, $type)',
() => _wrapped.nullLiteral(expression, type));
}
@override
void parenthesizedExpression(
Expression outerExpression, Expression innerExpression) {
_wrap(
'parenthesizedExpression($outerExpression, $innerExpression)',
() =>
_wrapped.parenthesizedExpression(outerExpression, innerExpression));
}
@override
void patternAssignment_afterRhs(Expression rhs, Type rhsType) {
_wrap('patternAssignment_afterRhs($rhs, $rhsType)',
() => _wrapped.patternAssignment_afterRhs(rhs, rhsType));
}
@override
void patternAssignment_end() {
_wrap('patternAssignment_end()', () => _wrapped.patternAssignment_end());
}
@override
void patternForIn_afterExpression(Type elementType) {
_wrap(
'patternForIn_afterExpression($elementType)',
() => _wrapped.patternForIn_afterExpression(elementType),
);
}
@override
void patternForIn_end() {
_wrap('patternForIn_end()', () => _wrapped.patternForIn_end());
}
@override
void patternVariableDeclaration_afterInitializer(
Expression initializer, Type initializerType) {
_wrap(
'patternVariableDeclaration_afterInitializer($initializer, '
'$initializerType)',
() => _wrapped.patternVariableDeclaration_afterInitializer(
initializer, initializerType));
}
@override
void patternVariableDeclaration_end() {
_wrap('patternVariableDeclaration_end()',
() => _wrapped.patternVariableDeclaration_end());
}
@override
void popPropertySubpattern() {
_wrap('popPropertySubpattern()', () => _wrapped.popPropertySubpattern());
}
@override
void popSubpattern() {
_wrap('popSubpattern()', () => _wrapped.popSubpattern());
}
@override
Type? promotedPropertyType(PropertyTarget<Expression> target,
String propertyName, Object? propertyMember, Type unpromotedType) {
return _wrap(
'promotedPropertyType($target, $propertyName, $propertyMember, '
'$unpromotedType)',
() => _wrapped.promotedPropertyType(
target, propertyName, propertyMember, unpromotedType),
isQuery: true);
}
@override
Type? promotedType(Variable variable) {
return _wrap(
'promotedType($variable)', () => _wrapped.promotedType(variable),
isQuery: true);
}
@override
bool promoteForPattern(
{required Type matchedType,
required Type knownType,
bool matchFailsIfWrongType = true,
bool matchMayFailEvenIfCorrectType = false}) {
return _wrap(
'patternRequiredType(matchedType: $matchedType, '
'requiredType: $knownType, '
'matchFailsIfWrongType: $matchFailsIfWrongType, '
'matchMayFailEvenIfCorrectType: $matchMayFailEvenIfCorrectType)',
() => _wrapped.promoteForPattern(
matchedType: matchedType,
knownType: knownType,
matchFailsIfWrongType: matchFailsIfWrongType,
matchMayFailEvenIfCorrectType: matchMayFailEvenIfCorrectType),
isQuery: true,
isPure: false);
}
@override
Type? propertyGet(
Expression? wholeExpression,
PropertyTarget<Expression> target,
String propertyName,
Object? propertyMember,
Type unpromotedType) {
return _wrap(
'propertyGet($wholeExpression, $target, $propertyName, '
'$propertyMember, $unpromotedType)',
() => _wrapped.propertyGet(wholeExpression, target, propertyName,
propertyMember, unpromotedType),
isQuery: true,
isPure: false);
}
@override
Type? pushPropertySubpattern(
String propertyName, Object? propertyMember, Type unpromotedType) {
return _wrap(
'pushPropertySubpattern($propertyName, $propertyMember, '
'$unpromotedType)',
() => _wrapped.pushPropertySubpattern(
propertyName, propertyMember, unpromotedType),
isQuery: true,
isPure: false);
}
@override
void pushSubpattern(Type matchedType) {
_wrap('pushSubpattern($matchedType)',
() => _wrapped.pushSubpattern(matchedType));
}
@override
SsaNode<Type>? ssaNodeForTesting(Variable variable) {
return _wrap('ssaNodeForTesting($variable)',
() => _wrapped.ssaNodeForTesting(variable),
isQuery: true);
}
@override
bool switchStatement_afterCase() {
return _wrap('switchStatement_afterCase()',
() => _wrapped.switchStatement_afterCase(),
isPure: false, isQuery: true);
}
@override
void switchStatement_beginAlternative() {
_wrap('switchStatement_beginAlternative()',
() => _wrapped.switchStatement_beginAlternative());
}
@override
void switchStatement_beginAlternatives() {
_wrap('switchStatement_beginAlternatives()',
() => _wrapped.switchStatement_beginAlternatives());
}
@override
bool switchStatement_end(bool isExhaustive) {
return _wrap('switchStatement_end($isExhaustive)',
() => _wrapped.switchStatement_end(isExhaustive),
isQuery: true, isPure: false);
}
@override
void switchStatement_endAlternative(
Expression? guard, Map<String, Variable> variables) {
_wrap('switchStatement_endAlternative($guard, $variables)',
() => _wrapped.switchStatement_endAlternative(guard, variables));
}
@override
PatternVariableInfo<Variable> switchStatement_endAlternatives(Statement? node,
{required bool hasLabels}) {
return _wrap(
'switchStatement_endAlternatives($node, hasLabels: $hasLabels)',
() => _wrapped.switchStatement_endAlternatives(node,
hasLabels: hasLabels),
isQuery: true,
isPure: false);
}
@override
void switchStatement_expressionEnd(
Statement? switchStatement, Expression scrutinee, Type scrutineeType) {
_wrap(
'switchStatement_expressionEnd($switchStatement, $scrutinee, '
'$scrutineeType)',
() => _wrapped.switchStatement_expressionEnd(
switchStatement, scrutinee, scrutineeType));
}
@override
void thisOrSuper(Expression expression, Type staticType,
{required bool isSuper}) {
return _wrap('thisOrSuper($expression, $staticType, isSuper: $isSuper)',
() => _wrapped.thisOrSuper(expression, staticType, isSuper: isSuper));
}
@override
void tryCatchStatement_bodyBegin() {
return _wrap('tryCatchStatement_bodyBegin()',
() => _wrapped.tryCatchStatement_bodyBegin());
}
@override
void tryCatchStatement_bodyEnd(Node body) {
return _wrap('tryCatchStatement_bodyEnd($body)',
() => _wrapped.tryCatchStatement_bodyEnd(body));
}
@override
void tryCatchStatement_catchBegin(
Variable? exceptionVariable, Variable? stackTraceVariable) {
return _wrap(
'tryCatchStatement_catchBegin($exceptionVariable, $stackTraceVariable)',
() => _wrapped.tryCatchStatement_catchBegin(
exceptionVariable, stackTraceVariable));
}
@override
void tryCatchStatement_catchEnd() {
return _wrap('tryCatchStatement_catchEnd()',
() => _wrapped.tryCatchStatement_catchEnd());
}
@override
void tryCatchStatement_end() {
return _wrap(
'tryCatchStatement_end()', () => _wrapped.tryCatchStatement_end());
}
@override
void tryFinallyStatement_bodyBegin() {
return _wrap('tryFinallyStatement_bodyBegin()',
() => _wrapped.tryFinallyStatement_bodyBegin());
}
@override
void tryFinallyStatement_end() {
return _wrap(
'tryFinallyStatement_end()', () => _wrapped.tryFinallyStatement_end());
}
@override
void tryFinallyStatement_finallyBegin(Node body) {
return _wrap('tryFinallyStatement_finallyBegin($body)',
() => _wrapped.tryFinallyStatement_finallyBegin(body));
}
@override
Type? variableRead(Expression expression, Variable variable) {
return _wrap('variableRead($expression, $variable)',
() => _wrapped.variableRead(expression, variable),
isQuery: true, isPure: false);
}
@override
void whileStatement_bodyBegin(
Statement whileStatement, Expression condition) {
return _wrap('whileStatement_bodyBegin($whileStatement, $condition)',
() => _wrapped.whileStatement_bodyBegin(whileStatement, condition));
}
@override
void whileStatement_conditionBegin(Node node) {
return _wrap('whileStatement_conditionBegin($node)',
() => _wrapped.whileStatement_conditionBegin(node));
}
@override
void whileStatement_end() {
return _wrap('whileStatement_end()', () => _wrapped.whileStatement_end());
}
@override
Map<Type, NonPromotionReason> Function() whyNotPromoted(Expression target) {
return _wrap('whyNotPromoted($target)',
() => _trackWhyNotPromoted(_wrapped.whyNotPromoted(target)),
isQuery: true);
}
@override
Map<Type, NonPromotionReason> Function() whyNotPromotedImplicitThis(
Type staticType) {
return _wrap(
'whyNotPromotedImplicitThis($staticType)',
() => _trackWhyNotPromoted(
_wrapped.whyNotPromotedImplicitThis(staticType)),
isQuery: true);
}
@override
void write(Node node, Variable variable, Type writtenType,
Expression? writtenExpression) {
_wrap('write($node, $variable, $writtenType, $writtenExpression)',
() => _wrapped.write(node, variable, writtenType, writtenExpression));
}
@override
void _dumpState() => _wrapped._dumpState();
/// Wraps [callback] so that when it is called, the call (and its return
/// value) will be printed to the console. Also registers the wrapped
/// callback in [_description] so that it will be given a unique identifier
/// when printed to the console.
Map<Type, NonPromotionReason> Function() _trackWhyNotPromoted(
Map<Type, NonPromotionReason> Function() callback) {
String callbackToString = '#CALLBACK${_nextCallbackId++}';
Map<Type, NonPromotionReason> Function() wrappedCallback =
() => _wrap('$callbackToString()', callback, isQuery: true);
_description[wrappedCallback] = callbackToString;
return wrappedCallback;
}
T _wrap<T>(String description, T callback(),
{bool isQuery = false, bool? isPure}) {
isPure ??= isQuery;
print(description);
T result;
try {
result = callback();
} catch (e, st) {
print(' => EXCEPTION $e');
print(' ' + st.toString().replaceAll('\n', '\n '));
_exceptionOccurred = true;
rethrow;
}
if (!isPure) {
_wrapped._dumpState();
}
if (isQuery) {
print(' => ${_describe(result)}');
}
return result;
}
static String _describe(Object? value) {
if (value != null && value is! String && value is! num && value is! bool) {
String? description = _description[value];
if (description != null) return description;
}
return value.toString();
}
}
/// An instance of the [FlowModel] class represents the information gathered by
/// flow analysis at a single point in the control flow of the function or
/// method being analyzed.
///
/// Instances of this class are immutable, so the methods below that "update"
/// the state actually leave `this` unchanged and return a new state object.
@visibleForTesting
class FlowModel<Type extends Object> {
final Reachability reachable;
/// [PromotionInfo] object tracking the [PromotionModel]s for each promotable
/// thing being tracked by flow analysis.
final PromotionInfo<Type>? promotionInfo;
/// Creates a state object with the given [reachable] status. All variables
/// are assumed to be unpromoted and already assigned, so joining another
/// state with this one will have no effect on it.
FlowModel(Reachability reachable) : this.withInfo(reachable, null);
@visibleForTesting
FlowModel.withInfo(this.reachable, this.promotionInfo);
/// Computes the effect of executing a try/finally's `try` and `finally`
/// blocks in sequence. `this` is the flow analysis state from the end of the
/// `try` block; [beforeFinally] and [afterFinally] are the flow analysis
/// states from the top and bottom of the `finally` block, respectively.
///
/// Initially the `finally` block is analyzed under the conservative
/// assumption that the `try` block might have been interrupted at any point
/// by an exception occurring, therefore no variable assignments or promotions
/// that occurred in the `try` block can be relied upon. As a result, when we
/// get to the end of processing the `finally` block, the only promotions and
/// variable assignments accounted for by flow analysis are the ones performed
/// within the `finally` block itself. However, when we analyze code that
/// follows the `finally` block, we know that the `try` block did *not* throw
/// an exception, so we want to reinstate the results of any promotions and
/// assignments that occurred during the `try` block, to the extent that they
/// weren't invalidated by later assignments in the `finally` block.
FlowModel<Type> attachFinally(FlowModelHelper<Type> helper,
{required FlowModel<Type> beforeFinally,
required FlowModel<Type> afterFinally,
required FlowModel<Type> ancestor}) {
// If nothing happened in the `finally` block, then nothing needs to be
// done.
if (beforeFinally == afterFinally) return this;
// If nothing happened in the `try` block, then no rebase is needed.
if (beforeFinally == ancestor && ancestor == this) return afterFinally;
// Code that follows the `try/finally` is reachable iff the end of the `try`
// block is reachable _and_ the end of the `finally` block is reachable.
Reachability newReachable = afterFinally.reachable.rebaseForward(reachable);
// Consider each promotion key that is common to all three models.
FlowModel<Type> result = setReachability(newReachable);
List<(SsaNode<Type>?, SsaNode<Type>?)> fieldPromotionsToReapply = [];
var (
ancestor: PromotionInfo<Type>? ancestorInfo,
:List<FlowLinkDiffEntry<PromotionInfo<Type>>> entries
) = helper.reader.diff(promotionInfo, afterFinally.promotionInfo);
assert(ancestor.promotionInfo == ancestorInfo);
for (var FlowLinkDiffEntry(
key: int promotionKey,
:PromotionInfo<Type>? left,
:PromotionInfo<Type>? right
) in entries) {
PromotionModel<Type>? thisModel = left?.model;
PromotionModel<Type>? beforeFinallyModel =
beforeFinally.promotionInfo?.get(helper, promotionKey);
PromotionModel<Type>? afterFinallyModel = right?.model;
if (thisModel == null) {
if (afterFinallyModel == null) {
// This should never happen, because we are iterating through
// promotion keys that are different between the `this` and
// `afterFinally` models.
assert(false);
continue;
}
// The promotion key is in the `afterFinally` model but not in `this`
// model. This happens when either:
// - There is a variable declared inside the `finally` block, or:
// - A field is promoted inside the `finally` block that wasn't
// previously promoted (and isn't promoted in the `try` block).
//
// In the first case, it doesn't matter what we do, because the variable
// won't be in scope after the try/finally statement. But in the second
// case, we need to preserve the promotion from the `finally` block.
result =
result.updatePromotionInfo(helper, promotionKey, afterFinallyModel);
continue;
}
if (afterFinallyModel == null) {
// The promotion key is in `this` model but not in the `afterFinally`
// model. This happens when either:
// - There is a variable declared inside the `try` block, or:
// - A field is promoted inside the `try` block that wasn't previously
// promoted (and isn't promoted in the `finally` block).
//
// In the first case, it doesn't matter what we do, because the variable
// won't be in scope after the try/finally statement. But in the second
// case, we need to preserve the promotion from the `try` block.
result = result.updatePromotionInfo(helper, promotionKey, thisModel);
continue;
}
// We can just use the "write captured" state from the `finally` block,
// because any write captures in the `try` block are conservatively
// considered to take effect in the `finally` block too.
List<Type>? newPromotedTypes;
SsaNode<Type>? newSsaNode;
if (beforeFinallyModel == null ||
beforeFinallyModel.ssaNode == afterFinallyModel.ssaNode) {
// The promotion key is in `this` model and in the `afterFinally` model,
// and either:
// - It is absent from the `beforeFinally` model. This means that there
// is a field that is accessed (and possibly promoted) in both the
// `try` and `finally` blocks, but wasn't known about before the
// try/finally statement, OR:
// - It is present in the `beforeFinally` model, and has the same SSA
// node in both the `beforeFinally` and `afterFinally` models. This
// means that there is either a variable, or a field of a variable,
// that is accessed (and possibly promoted) in both the `try` and
// `finally` blocks, which *was* known about before the try/finally
// statement, and furthermore, if it was a variable, the variable
// wasn't assigned within the `finally` block.
//
// In all of these cases, the correct thing to do is to keep all
// promotions that were done in both the `try` and `finally` blocks.
newPromotedTypes = PromotionModel.rebasePromotedTypes(
helper.typeOperations,
thisModel.promotedTypes,
afterFinallyModel.promotedTypes);
// And we can safely restore the SSA node from the end of the try block.
newSsaNode = thisModel.ssaNode;
if (newSsaNode != afterFinallyModel.ssaNode) {
// The `try` block did write to the variable, so any field promotions
// that were applied in the finally block need to be re-applied to the
// new promotion keys. We postpone that until after everything else is
// done so that when we re-apply the promotions, we'll be applying
// them to the state established by the `try` block.
fieldPromotionsToReapply.add((newSsaNode, afterFinallyModel.ssaNode));
}
} else {
// A write to the variable occurred in the finally block, so promotions
// from the try block aren't necessarily valid.
newPromotedTypes = afterFinallyModel.promotedTypes;
// And we can't safely restore the SSA node from the end of the try
// block; we need to keep the one from the end of the finally block.
newSsaNode = afterFinallyModel.ssaNode;
}
// The `finally` block inherited all tests from the `try` block so we can
// just inherit tests from it.
List<Type> newTested = afterFinallyModel.tested;
// The variable is definitely assigned if it was definitely assigned in
// either the `try` or the `finally` block.
bool newAssigned = thisModel.assigned || afterFinallyModel.assigned;
// The `finally` block inherited the "unassigned" state from the `try`
// block so we can just inherit from it.
bool newUnassigned = afterFinallyModel.unassigned;
PromotionModel<Type> newModel = PromotionModel._identicalOrNew(
thisModel,
afterFinallyModel,
newPromotedTypes,
newTested,
newAssigned,
newUnassigned,
newSsaNode);
result = result.updatePromotionInfo(helper, promotionKey, newModel);
}
for (var (SsaNode<Type>? thisSsaNode, SsaNode<Type>? afterFinallySsaNode)
in fieldPromotionsToReapply) {
if (thisSsaNode == null || afterFinallySsaNode == null) {
// Variable was write-captured, so no fields can be promoted anymore.
continue;
}
result = thisSsaNode._applyPropertyPromotions(
helper,
thisSsaNode,
afterFinallySsaNode,
beforeFinally.promotionInfo,
afterFinally.promotionInfo,
result);
}
return result;
}
/// Updates the state to indicate that the given [writtenVariables] are no
/// longer promoted and are no longer definitely unassigned, and the given
/// [capturedVariables] have been captured by closures.
///
/// This is used at the top of loops to conservatively cancel the promotion of
/// variables that are modified within the loop, so that we correctly analyze
/// code like the following:
///
/// if (x is int) {
/// x.isEven; // OK, promoted to int
/// while (true) {
/// x.isEven; // ERROR: promotion lost
/// x = 'foo';
/// }
/// }
///
/// Note that a more accurate analysis would be to iterate to a fixed point,
/// and only remove promotions if it can be shown that they aren't restored
/// later in the loop body. If we switch to a fixed point analysis, we should
/// be able to remove this method.
FlowModel<Type> conservativeJoin(FlowModelHelper<Type> helper,
Iterable<int> writtenVariables, Iterable<int> capturedVariables) {
FlowModel<Type> result = this;
for (int variableKey in writtenVariables) {
PromotionModel<Type>? info =
result.promotionInfo?.get(helper, variableKey);
if (info == null) continue;
PromotionModel<Type> newInfo =
info.discardPromotionsAndMarkNotUnassigned();
if (!identical(info, newInfo)) {
result = result.updatePromotionInfo(helper, variableKey, newInfo);
}
}
for (int variableKey in capturedVariables) {
PromotionModel<Type>? info =
result.promotionInfo?.get(helper, variableKey);
if (info == null) continue;
if (!info.writeCaptured) {
result = result.updatePromotionInfo(
helper, variableKey, info.writeCapture());
// Note: there's no need to discard dependent property promotions,
// because when deciding whether a property is promoted,
// [_FlowAnalysisImpl._handleProperty] checks whether the variable is
// captured.
}
}
return result;
}
/// Register a declaration of the variable whose key is [variableKey].
/// Should also be called for function parameters.
///
/// A local variable is [initialized] if its declaration has an initializer.
/// A function parameter is always initialized, so [initialized] is `true`.
FlowModel<Type> declare(
FlowModelHelper<Type> helper, int variableKey, bool initialized) {
PromotionModel<Type> newInfoForVar = new PromotionModel.fresh(
assigned: initialized, ssaNode: new SsaNode<Type>(null));
return updatePromotionInfo(helper, variableKey, newInfoForVar);
}
/// Gets the info for the given [promotionKey], creating it if it doesn't
/// exist.
///
/// If new info must be created, [ssaNode] is used as its SSA node. This
/// allows the caller to ensure that when the promotion key represents a
/// promotable property, the SSA node will match the [_PropertySsaNode] found
/// in the target's [SsaNode._promotableProperties] map.
PromotionModel<Type> infoFor(FlowModelHelper<Type> helper, int promotionKey,
{required SsaNode<Type> ssaNode}) =>
promotionInfo?.get(helper, promotionKey) ??
new PromotionModel.fresh(ssaNode: ssaNode);
/// Builds a [FlowModel] based on `this`, but extending the `tested` set to
/// include types from [other]. This is used at the bottom of certain kinds
/// of loops, to ensure that types tested within the body of the loop are
/// consistently treated as "of interest" in code that follows the loop,
/// regardless of the type of loop.
@visibleForTesting
FlowModel<Type> inheritTested(
FlowModelHelper<Type> helper, FlowModel<Type> other) {
FlowModel<Type> result = this;
for (var FlowLinkDiffEntry(
key: int promotionKey,
:PromotionInfo<Type>? left,
:PromotionInfo<Type>? right
) in helper.reader.diff(promotionInfo, other.promotionInfo).entries) {
PromotionModel<Type>? promotionModel = left?.model;
if (promotionModel == null) continue;
PromotionModel<Type>? otherPromotionModel = right?.model;
PromotionModel<Type> newPromotionModel = otherPromotionModel == null
? promotionModel
: PromotionModel.inheritTested(helper.typeOperations, promotionModel,
otherPromotionModel.tested);
if (!identical(newPromotionModel, promotionModel)) {
result =
result.updatePromotionInfo(helper, promotionKey, newPromotionModel);
}
}
return result;
}
/// Updates `this` flow model to account for any promotions and assignments
/// present in [base].
///
/// This is called "rebasing" the flow model by analogy to "git rebase"; in
/// effect, it rewinds any flow analysis state present in `this` but not in
/// the history of [base], and then reapplies that state using [base] as a
/// starting point, to the extent possible without creating unsoundness. For
/// example, if a variable is promoted in `this` but not in [base], then it
/// will be promoted in the output model, provided that hasn't been reassigned
/// since then (which would make the promotion unsound).
FlowModel<Type> rebaseForward(
FlowModelHelper<Type> helper, FlowModel<Type> base) {
// The rebased model is reachable iff both `this` and the new base are
// reachable.
Reachability newReachable = reachable.rebaseForward(base.reachable);
FlowModel<Type> result = base.setReachability(newReachable);
var (
:PromotionInfo<Type>? ancestor,
:List<FlowLinkDiffEntry<PromotionInfo<Type>>> entries
) = helper.reader.diff(promotionInfo, base.promotionInfo);
// If `this` matches the ancestor, then there are no state changes that need
// to be rewound and applied to `base`.
if (ancestor == promotionInfo) {
return result;
}
// If `base` matches the ancestor, then the act of rewinding `this` back to
// the ancestor, and then reapplying the rewound changes to `base`,
// reproduces `this` exactly (assuming reachability matches up properly).
if (base.promotionInfo == ancestor && reachable == newReachable) {
return this;
}
// Consider each promotion key in the new base model.
for (var FlowLinkDiffEntry(
key: int promotionKey,
:PromotionInfo<Type>? left,
:PromotionInfo<Type>? right
) in entries) {
PromotionModel<Type>? thisModel = left?.model;
if (thisModel == null) {
// Either this promotion key represents a variable that has newly come
// into scope since `thisModel`, or it represents a property that flow
// analysis became aware of since `thisModel`. In either case, the
// information in `baseModel` is up to date.
continue;
}
PromotionModel<Type>? baseModel = right?.model;
if (baseModel == null) {
// The promotion key exists in `this` model but not in the new `base`
// model. This happens when either:
// - The promotion key is associated with a local variable that was in
// scope at the time `this` model was created, but is no longer in
// scope as of the `base` model, or:
// - The promotion key is associated with a property that was promoted
// in `this` model.
//
// In the first case, it doesn't matter what we do, because the variable
// is no longer in scope. But in the second case, we need to preserve
// the promotion.
result = result.updatePromotionInfo(helper, promotionKey, thisModel);
continue;
}
// If the variable was write captured in either `this` or the new base,
// it's captured now.
bool newWriteCaptured =
thisModel.writeCaptured || baseModel.writeCaptured;
List<Type>? newPromotedTypes;
if (newWriteCaptured) {
// Write captured variables can't be promoted.
newPromotedTypes = null;
} else if (baseModel.ssaNode != thisModel.ssaNode) {
// The variable may have been written to since `thisModel`, so we can't
// use any of the promotions from `thisModel`.
newPromotedTypes = baseModel.promotedTypes;
} else {
// The variable hasn't been written to since `thisModel`, so we can keep
// all of the promotions from `thisModel`, provided that we retain the
// usual "promotion chain" invariant (each promoted type is a subtype of
// the previous).
newPromotedTypes = PromotionModel.rebasePromotedTypes(
helper.typeOperations,
thisModel.promotedTypes,
baseModel.promotedTypes);
}
// Tests are kept regardless of whether they are in `this` model or the
// new base model.
List<Type> newTested = PromotionModel.joinTested(
thisModel.tested, baseModel.tested, helper.typeOperations);
// The variable is definitely assigned if it was definitely assigned
// either in `this` model or the new base model.
bool newAssigned = thisModel.assigned || baseModel.assigned;
// The variable is definitely unassigned if it was definitely unassigned
// in both `this` model and the new base model.
bool newUnassigned = thisModel.unassigned && baseModel.unassigned;
PromotionModel<Type> newModel = PromotionModel._identicalOrNew(
thisModel,
baseModel,
newPromotedTypes,
newTested,
newAssigned,
newUnassigned,
newWriteCaptured ? null : baseModel.ssaNode);
result = result.updatePromotionInfo(helper, promotionKey, newModel);
}
return result;
}
FlowModel<Type> setReachability(Reachability reachable) {
if (this.reachable == reachable) return this;
return new FlowModel<Type>.withInfo(reachable, promotionInfo);
}
/// Updates the state to indicate that the control flow path is unreachable.
FlowModel<Type> setUnreachable() {
if (!reachable.locallyReachable) return this;
return new FlowModel<Type>.withInfo(
reachable.setUnreachable(), promotionInfo);
}
/// Returns a [FlowModel] indicating the result of creating a control flow
/// split. See [Reachability.split] for more information.
FlowModel<Type> split() =>
new FlowModel<Type>.withInfo(reachable.split(), promotionInfo);
@override
String toString() => '($reachable, $promotionInfo)';
/// Returns an [ExpressionInfo] indicating the result of checking whether the
/// given [reference] is non-null.
///
/// Note that the state is only changed if the previous type of [reference]
/// was potentially nullable.
ExpressionInfo<Type> tryMarkNonNullable(
FlowModelHelper<Type> helper, _Reference<Type> reference) {
PromotionModel<Type> info =
infoFor(helper, reference.promotionKey, ssaNode: reference.ssaNode);
if (info.writeCaptured) {
return new ExpressionInfo<Type>.trivial(
model: this, type: helper.boolType);
}
Type previousType = reference._type;
Type newType = helper.typeOperations.promoteToNonNull(previousType);
if (newType == previousType) {
return new ExpressionInfo<Type>.trivial(
model: this, type: helper.boolType);
}
assert(helper.typeOperations.isSubtypeOf(newType, previousType));
FlowModel<Type> ifTrue =
_finishTypeTest(helper, reference, info, null, newType);
return new ExpressionInfo<Type>(
type: helper.boolType, ifTrue: ifTrue, ifFalse: this);
}
/// Returns an [ExpressionInfo] indicating the result of casting the given
/// [reference] to the given [type], as a consequence of an `as` expression.
///
/// Note that the state is only changed if [type] is a subtype of the
/// reference's previous (possibly promoted) type.
///
/// TODO(paulberry): if the type is non-nullable, should this method mark the
/// variable as definitely assigned? Does it matter?
FlowModel<Type> tryPromoteForTypeCast(
FlowModelHelper<Type> helper, _Reference<Type> reference, Type type) {
PromotionModel<Type> info =
infoFor(helper, reference.promotionKey, ssaNode: reference.ssaNode);
if (info.writeCaptured) {
return this;
}
Type previousType = reference._type;
Type? newType = helper.typeOperations.tryPromoteToType(type, previousType);
if (newType == null || newType == previousType) {
return this;
}
assert(helper.typeOperations.isSubtypeOf(newType, previousType),
"Expected $newType to be a subtype of $previousType.");
return _finishTypeTest(helper, reference, info, type, newType);
}
/// Returns an [ExpressionInfo] indicating the result of checking whether the
/// given [reference] satisfies the given [type], e.g. as a consequence of an
/// `is` expression as the condition of an `if` statement.
///
/// Note that the "ifTrue" state is only changed if [type] is a subtype of
/// the variable's previous (possibly promoted) type.
///
/// TODO(paulberry): if the type is non-nullable, should this method mark the
/// variable as definitely assigned? Does it matter?
ExpressionInfo<Type> tryPromoteForTypeCheck(
FlowModelHelper<Type> helper, _Reference<Type> reference, Type type) {
PromotionModel<Type> info =
infoFor(helper, reference.promotionKey, ssaNode: reference.ssaNode);
if (info.writeCaptured) {
return new ExpressionInfo<Type>.trivial(
model: this, type: helper.boolType);
}
Type previousType = reference._type;
FlowModel<Type> ifTrue = this;
Type? typeIfSuccess =
helper.typeOperations.tryPromoteToType(type, previousType);
if (typeIfSuccess != null && typeIfSuccess != previousType) {
assert(helper.typeOperations.isSubtypeOf(typeIfSuccess, previousType),
"Expected $typeIfSuccess to be a subtype of $previousType.");
ifTrue = _finishTypeTest(helper, reference, info, type, typeIfSuccess);
}
Type factoredType = helper.typeOperations.factor(previousType, type);
Type? typeIfFalse;
if (helper.typeOperations.isNever(factoredType)) {
// Promoting to `Never` would mark the code as unreachable. But it might
// be reachable due to mixed mode unsoundness. So don't promote.
typeIfFalse = null;
} else if (factoredType == previousType) {
// No change to the type, so don't promote.
typeIfFalse = null;
} else {
typeIfFalse = factoredType;
}
FlowModel<Type> ifFalse =
_finishTypeTest(helper, reference, info, type, typeIfFalse);
return new ExpressionInfo<Type>(
type: helper.boolType, ifTrue: ifTrue, ifFalse: ifFalse);
}
/// Returns a [FlowModel] indicating the result of removing a control flow
/// split. See [Reachability.unsplit] for more information.
FlowModel<Type> unsplit() =>
new FlowModel<Type>.withInfo(reachable.unsplit(), promotionInfo);
/// Removes control flow splits until a [FlowModel] is obtained whose
/// reachability has the given [parent].
FlowModel<Type> unsplitTo(Reachability parent) {
if (identical(this.reachable.parent, parent)) return this;
Reachability reachable = this.reachable.unsplit();
while (!identical(reachable.parent, parent)) {
reachable = reachable.unsplit();
}
return new FlowModel<Type>.withInfo(reachable, promotionInfo);
}
/// Returns a new [FlowModel] where the information for [promotionKey] is
/// replaced with [model].
@visibleForTesting
FlowModel<Type> updatePromotionInfo(FlowModelHelper<Type> helper,
int promotionKey, PromotionModel<Type> model) {
PromotionInfo<Type> newPromotionInfo = new PromotionInfo._(model,
key: promotionKey,
previous: promotionInfo,
previousForKey: helper.reader.get(promotionInfo, promotionKey));
return new FlowModel.withInfo(reachable, newPromotionInfo);
}
/// Updates the state to indicate that an assignment was made to [Variable],
/// whose key is [variableKey]. The variable is marked as definitely
/// assigned, and any previous type promotion is removed.
///
/// If there is any chance that the write will cause a demotion, the caller
/// must pass in a non-null value for [nonPromotionReason] describing the
/// reason for any potential demotion.
FlowModel<Type> write<Variable extends Object>(
FlowModelHelper<Type> helper,
NonPromotionReason? nonPromotionReason,
int variableKey,
Type writtenType,
SsaNode<Type> newSsaNode,
FlowAnalysisOperations<Variable, Type> operations,
{bool promoteToTypeOfInterest = true,
required Type unpromotedType}) {
FlowModel<Type>? newModel;
PromotionModel<Type>? infoForVar = promotionInfo?.get(helper, variableKey);
if (infoForVar != null) {
PromotionModel<Type> newInfoForVar = infoForVar.write(
nonPromotionReason, variableKey, writtenType, operations, newSsaNode,
promoteToTypeOfInterest: promoteToTypeOfInterest,
unpromotedType: unpromotedType);
if (!identical(newInfoForVar, infoForVar)) {
newModel = updatePromotionInfo(helper, variableKey, newInfoForVar);
}
}
return newModel ?? this;
}
/// Common algorithm for [tryMarkNonNullable], [tryPromoteForTypeCast],
/// and [tryPromoteForTypeCheck]. Builds a [FlowModel] object describing the
/// effect of updating the [reference] by adding the [testedType] to the
/// list of tested types (if not `null`, and not there already), adding the
/// [promotedType] to the chain of promoted types.
///
/// Preconditions:
/// - [info] should be the result of calling [infoFor] on the reference.
/// - [promotedType] should be a subtype of the currently-promoted type (i.e.
/// no redundant or side-promotions)
/// - If the reference is a variable, it should not be write-captured.
FlowModel<Type> _finishTypeTest(
FlowModelHelper<Type> helper,
_Reference<Type> reference,
PromotionModel<Type> info,
Type? testedType,
Type? promotedType) {
List<Type> newTested = info.tested;
if (testedType != null) {
newTested = PromotionModel._addTypeToUniqueList(
info.tested, testedType, helper.typeOperations);
}
List<Type>? newPromotedTypes = info.promotedTypes;
if (promotedType != null) {
newPromotedTypes =
PromotionModel._addToPromotedTypes(info.promotedTypes, promotedType);
}
return identical(newTested, info.tested) &&
identical(newPromotedTypes, info.promotedTypes)
? this
: updatePromotionInfo(
helper,
reference.promotionKey,
new PromotionModel<Type>(
promotedTypes: newPromotedTypes,
tested: newTested,
assigned: info.assigned,
unassigned: info.unassigned,
ssaNode: info.ssaNode,
nonPromotionHistory: info.nonPromotionHistory));
}
/// Forms a new state to reflect a control flow path that might have come from
/// either the [first] or [second] state.
///
/// The control flow path is considered reachable if either of the input
/// states is reachable. Variables are considered definitely assigned if they
/// were definitely assigned in both of the input states. Promotions are kept
/// only if they are common to both input states; if a reference is promoted
/// to one type in one state and a subtype in the other state, the less
/// specific type promotion is kept.
static FlowModel<Type> join<Type extends Object>(
FlowModelHelper<Type> helper,
FlowModel<Type>? first,
FlowModel<Type>? second,
) {
if (first == null) return second!;
if (second == null) return first;
assert(identical(first.reachable.parent, second.reachable.parent));
if (first.reachable.locallyReachable &&
!second.reachable.locallyReachable) {
return first;
}
if (!first.reachable.locallyReachable &&
second.reachable.locallyReachable) {
return second;
}
// first.reachable and second.reachable are equivalent, so we don't need to
// join reachabilities.
assert(
first.reachable.locallyReachable == second.reachable.locallyReachable);
assert(first.reachable.parent == second.reachable.parent);
return FlowModel.joinPromotionInfo(helper, first, second);
}
/// Joins two "promotion info" maps. See [join] for details.
@visibleForTesting
static FlowModel<Type> joinPromotionInfo<Type extends Object>(
FlowModelHelper<Type> helper,
FlowModel<Type> first,
FlowModel<Type> second) {
if (identical(first, second)) return first;
if (first.promotionInfo == null) return first;
if (second.promotionInfo == null) return second;
var (
:PromotionInfo<Type>? ancestor,
:List<FlowLinkDiffEntry<PromotionInfo<Type>>> entries
) = helper.reader.diff(first.promotionInfo, second.promotionInfo);
FlowModel<Type> newFlowModel =
new FlowModel.withInfo(first.reachable, ancestor);
for (var FlowLinkDiffEntry(
key: int promotionKey,
left: PromotionInfo<Type>? leftInfo,
right: PromotionInfo<Type>? rightInfo
) in entries) {
PromotionModel<Type>? firstModel = leftInfo?.model;
if (firstModel == null) {
continue;
}
PromotionModel<Type>? secondModel = rightInfo?.model;
if (secondModel == null) {
continue;
}
PromotionModel<Type> joined;
(joined, newFlowModel) = PromotionModel.join<Type>(helper, firstModel,
first.promotionInfo, secondModel, second.promotionInfo, newFlowModel);
newFlowModel =
newFlowModel.updatePromotionInfo(helper, promotionKey, joined);
}
return newFlowModel;
}
}
/// Convenience methods used by [FlowModel] and [_Reference] methods to access
/// variables in [_FlowAnalysisImpl].
@visibleForTesting
mixin FlowModelHelper<Type extends Object> {
/// [FlowLinkReader] object for efficiently looking up [PromotionModel]
/// objects in [FlowModel.promotionInfo] structures, or for computing the
/// difference between two [FlowModel.promotionInfo] structures.
final FlowLinkReader<PromotionInfo<Type>> reader =
new FlowLinkReader<PromotionInfo<Type>>();
/// Returns the client's representation of the type `bool`.
Type get boolType;
/// The [PromotionKeyStore], which tracks the unique integer assigned to
/// everything in the control flow that might be promotable.
@visibleForTesting
PromotionKeyStore<Object> get promotionKeyStore;
/// The [FlowAnalysisTypeOperations], used to access types and check
/// subtyping.
@visibleForTesting
FlowAnalysisTypeOperations<Type> get typeOperations;
}
/// Documentation links that might be presented to the user to accompany a "why
/// not promoted" context message.
enum NonPromotionDocumentationLink {
/// The expression in question is a reference to a private final field, but it
/// couldn't be promoted because there is another class in the same library
/// containing a concrete getter with the same name.
conflictingGetter('http://dart.dev/go/non-promo-conflicting-getter'),
/// The expression in question is a reference to a private final field, but it
/// couldn't be promoted because there is another class in the same library
/// containing a field with the same name that's not promotable (either
/// because it's not final or because it's external).
conflictingNonPromotableField(
'http://dart.dev/go/non-promo-conflicting-non-promotable-field'),
/// The expression in question is a reference to a private final field, but it
/// couldn't be promoted because there is a concrete class `C` in the library
/// whose interface contains a getter with the same name, but `C` does not
/// have an implementation of that getter (and hence it forwards to
/// `noSuchMethod`).
conflictingNoSuchMethodForwarder(
'http://dart.dev/go/non-promo-conflicting-noSuchMethod-forwarder'),
/// The expression in question is a reference to a private field, but it
/// couldn't be promoted because it's external.
externalField('http://dart.dev/go/non-promo-external-field'),
/// The expression in question is a reference to a private field, but it
/// couldn't be promoted because the Dart language version for this library is
/// prior to field promotion support.
fieldPromotionUnavailable(
'http://dart.dev/go/non-promo-field-promotion-unavailable'),
/// The expression in question is a property get, but it couldn't be promoted
/// because it doesn't refer to a field (it might refer to a getter or it
/// might be a tear-off of a method).
nonField('http://dart.dev/go/non-promo-non-field'),
/// The expression in question is a reference to a private field, but it
/// couldn't be promoted because it's not final.
nonFinalField('http://dart.dev/go/non-promo-non-final-field'),
/// The expression in question is a property get. It couldn't be promoted
/// because promotion of property gets is not supported.
///
/// This link is no longer used, but it was used in Dart versions 3.1 and
/// earlier (so the documentation web site should continue to support it until
/// most users have upgraded to 3.2 or later).
@deprecated
property('http://dart.dev/go/non-promo-property'),
/// The expression in question is a reference to a field, but it couldn't be
/// promoted because it's not private.
publicField('http://dart.dev/go/non-promo-public-field'),
/// The expression in question is `this`. It couldn't be promoted because
/// promotion of `this` is not yet supported.
this_('http://dart.dev/go/non-promo-this'),
/// The expression in question is a reference to a local variable. It couldn't
/// be promoted because the variable was written to between the type test and
/// the usage.
write('http://dart.dev/go/non-promo-write');
/// The link URL, as a text string.
final String url;
const NonPromotionDocumentationLink(this.url);
@override
String toString() => url;
}
/// Linked list node representing a set of reasons why a given expression was
/// not promoted.
///
/// We use a linked list representation because it is very efficient to build;
/// this means that in the "happy path" where no error occurs (so non-promotion
/// history is not needed) we do a minimal amount of work.
class NonPromotionHistory<Type> {
/// The type that was not promoted to.
final Type type;
/// The reason why the promotion didn't occur.
final NonPromotionReason nonPromotionReason;
/// The previous link in the list.
final NonPromotionHistory<Type>? previous;
NonPromotionHistory(this.type, this.nonPromotionReason, this.previous);
@override
String toString() {
List<String> items = <String>[];
for (NonPromotionHistory<Type>? link = this;
link != null;
link = link.previous) {
items.add('${link.type}: ${link.nonPromotionReason}');
}
return items.toString();
}
}
/// Abstract class representing a reason why something was not promoted.
abstract class NonPromotionReason {
/// Link to documentation describing this non-promotion reason; this should be
/// presented to the user as a source of additional information about the
/// error.
///
/// In certain circumstances this link may be `null`, in which case the client
/// needs to supply a documentation link from the
/// [NonPromotionDocumentationLink] enum.
NonPromotionDocumentationLink? get documentationLink;
/// Short text description of this non-promotion reason; intended for ID
/// testing.
String get shortName;
/// Implementation of the visitor pattern for non-promotion reasons.
R accept<R, Node extends Object, Variable extends Object,
Type extends Object>(
NonPromotionReasonVisitor<R, Node, Variable, Type> visitor);
}
/// Implementation of the visitor pattern for non-promotion reasons.
abstract class NonPromotionReasonVisitor<R, Node extends Object,
Variable extends Object, Type extends Object> {
NonPromotionReasonVisitor._() : assert(false, 'Do not extend this class');
R visitDemoteViaExplicitWrite(DemoteViaExplicitWrite<Variable> reason);
R visitPropertyNotPromotedForInherentReason(
PropertyNotPromotedForInherentReason<Type> reason);
R visitPropertyNotPromotedForNonInherentReason(
PropertyNotPromotedForNonInherentReason<Type> reason);
R visitThisNotPromoted(ThisNotPromoted reason);
}
/// Data structure describing the relationship among variables defined by
/// patterns in the various alternatives of a set of switch cases that share a
/// body.
class PatternVariableInfo<Variable> {
/// Map from variable name to a list of the variables with this name defined
/// in each case.
final Map<String, List<Variable>> componentVariables = {};
/// Map from variable name to the promotion key used by flow analysis to track
/// the merged variable.
final Map<String, int> patternVariablePromotionKeys = {};
}
/// Map-like data structure recording the [PromotionModel]s for each promotable
/// thing (variable, property, `this`, or `super`) being tracked by flow
/// analysis.
///
/// Each instance of [PromotionInfo] is an immutable key/value pair binding a
/// single promotion [key] (a unique integer assigned by [PromotionKeyStore] to
/// track a particular promotable thing) with an instance of [PromotionModel]
/// describing the promotion state of that thing.
///
/// Please see the documentation for [FlowLink] for more information about how
/// this data structure works.
///
/// Flow analysis has no awareness of scope, so variables that are out of
/// scope are retained in the map until such time as their declaration no
/// longer dominates the control flow. So, for example, if a variable is
/// declared inside the `then` branch of an `if` statement, and the `else`
/// branch of the `if` statement ends in a `return` statement, then the
/// variable remains in the map after the `if` statement ends, even though the
/// variable is not in scope anymore. This should not have any effect on
/// analysis results for error-free code, because it is an error to refer to a
/// variable that is no longer in scope.
@visibleForTesting
base class PromotionInfo<Type extends Object>
extends FlowLink<PromotionInfo<Type>> {
/// The [PromotionModel] associated with [key].
@visibleForTesting
final PromotionModel<Type> model;
PromotionInfo._(this.model,
{required super.key,
required super.previous,
required super.previousForKey});
/// Looks up the [PromotionModel] associated with [promotionKey] by walking
/// the linked list formed by [previous] to find the nearest link whose [key]
/// matches [promotionKey].
@visibleForTesting
PromotionModel<Type>? get(FlowModelHelper<Type> helper, int promotionKey) =>
helper.reader.get(this, promotionKey)?.model;
}
/// An instance of the [PromotionModel] class represents the information
/// gathered by flow analysis for a single variable or property at a single
/// point in the control flow of the function or method being analyzed.
///
/// Instances of this class are immutable, so the methods below that "update"
/// the state actually leave `this` unchanged and return a new state object.
@visibleForTesting
class PromotionModel<Type extends Object> {
/// Sequence of types that the variable or property has been promoted to,
/// where each element of the sequence is a subtype of the previous. Null if
/// the variable or property hasn't been promoted.
final List<Type>? promotedTypes;
/// List of types that the variable has been tested against in all code paths
/// leading to the given point in the source code. Not relevant for
/// properties.
final List<Type> tested;
/// Indicates whether the variable has definitely been assigned. Not relevant
/// for properties.
final bool assigned;
/// Indicates whether the variable is unassigned. Not relevant for properties.
final bool unassigned;
/// SSA node associated with this variable. Every time the variable's value
/// potentially changes (either through an explicit write or a join with a
/// control flow path that contains a write), this field is updated to point
/// to a fresh node. Thus, it can be used to detect whether a variable's
/// value has changed since a time in the past.
///
/// `null` if the variable has been write captured.
///
/// For promotable properties, this is is the [_PropertySsaNode] found in the
/// target's [SsaNode._promotableProperties] map.
final SsaNode<Type>? ssaNode;
/// Non-promotion history of this variable. Not relevant for properties.
final NonPromotionHistory<Type>? nonPromotionHistory;
PromotionModel(
{required this.promotedTypes,
required this.tested,
required this.assigned,
required this.unassigned,
required this.ssaNode,
this.nonPromotionHistory}) {
assert(!(assigned && unassigned),
"Can't be both definitely assigned and unassigned");
assert(promotedTypes == null || promotedTypes!.isNotEmpty);
assert(!writeCaptured || promotedTypes == null,
"Write-captured variables can't be promoted");
assert(!(writeCaptured && unassigned),
"Write-captured variables can't be definitely unassigned");
// ignore:unnecessary_null_comparison
assert(tested != null);
}
/// Creates a [PromotionModel] representing a variable or property that's
/// never been seen before.
PromotionModel.fresh({this.assigned = false, required this.ssaNode})
: promotedTypes = null,
tested = const [],
unassigned = !assigned,
nonPromotionHistory = null;
/// Indicates whether the variable has been write captured. Not relevant for
/// properties.
bool get writeCaptured => ssaNode == null;
/// Returns a new [PromotionModel] in which any promotions present have been
/// dropped, and the variable has been marked as "not unassigned".
///
/// Used by [FlowModel.conservativeJoin] to update the state of variables at
/// the top of loops whose bodies write to them.
PromotionModel<Type> discardPromotionsAndMarkNotUnassigned() {
return new PromotionModel<Type>(
promotedTypes: null,
tested: tested,
assigned: assigned,
unassigned: false,
ssaNode: writeCaptured ? null : new SsaNode<Type>(null));
}
@override
String toString() {
List<String> parts = [ssaNode.toString()];
if (promotedTypes != null) {
parts.add('promotedTypes: $promotedTypes');
}
if (tested.isNotEmpty) {
parts.add('tested: $tested');
}
if (assigned) {
parts.add('assigned: true');
}
if (!unassigned) {
parts.add('unassigned: false');
}
if (writeCaptured) {
parts.add('writeCaptured: true');
}
if (nonPromotionHistory != null) {
parts.add('nonPromotionHistory: $nonPromotionHistory');
}
return 'PromotionModel(${parts.join(', ')})';
}
/// Returns a new [PromotionModel] reflecting the fact that the variable was
/// just written to.
///
/// If there is any chance that the write will cause a demotion, the caller
/// must pass in a non-null value for [nonPromotionReason] describing the
/// reason for any potential demotion.
PromotionModel<Type> write<Variable extends Object>(
NonPromotionReason? nonPromotionReason,
int variableKey,
Type writtenType,
FlowAnalysisOperations<Variable, Type> operations,
SsaNode<Type> newSsaNode,
{required bool promoteToTypeOfInterest,
required Type unpromotedType}) {
if (writeCaptured) {
return new PromotionModel<Type>(
promotedTypes: promotedTypes,
tested: tested,
assigned: true,
unassigned: false,
ssaNode: null);
}
_DemotionResult<Type> demotionResult =
_demoteViaAssignment(writtenType, operations, nonPromotionReason);
List<Type>? newPromotedTypes = demotionResult.promotedTypes;
if (promoteToTypeOfInterest) {
newPromotedTypes = _tryPromoteToTypeOfInterest(
operations, unpromotedType, newPromotedTypes, writtenType);
}
// TODO(paulberry): remove demotions from demotionResult.nonPromotionHistory
// that are no longer in effect due to re-promotion.
if (identical(promotedTypes, newPromotedTypes) && assigned) {
return new PromotionModel<Type>(
promotedTypes: promotedTypes,
tested: tested,
assigned: assigned,
unassigned: unassigned,
ssaNode: newSsaNode);
}
List<Type> newTested;
if (newPromotedTypes == null && promotedTypes != null) {
newTested = const [];
} else {
newTested = tested;
}
return new PromotionModel<Type>(
promotedTypes: newPromotedTypes,
tested: newTested,
assigned: true,
unassigned: false,
ssaNode: newSsaNode,
nonPromotionHistory: demotionResult.nonPromotionHistory);
}
/// Returns a new [PromotionModel] reflecting the fact that the variable has
/// been write-captured.
PromotionModel<Type> writeCapture() {
return new PromotionModel<Type>(
promotedTypes: null,
tested: const [],
assigned: assigned,
unassigned: false,
ssaNode: null);
}
/// Computes the result of demoting this variable due to writing a value of
/// type [writtenType].
///
/// If there is any chance that the write will cause an actual demotion to
/// occur, the caller must pass in a non-null value for [nonPromotionReason]
/// describing the reason for the potential demotion.
_DemotionResult<Type> _demoteViaAssignment(
Type writtenType,
FlowAnalysisTypeOperations<Type> typeOperations,
NonPromotionReason? nonPromotionReason) {
List<Type>? promotedTypes = this.promotedTypes;
if (promotedTypes == null) {
return new _DemotionResult<Type>(null, nonPromotionHistory);
}
int numElementsToKeep = promotedTypes.length;
NonPromotionHistory<Type>? newNonPromotionHistory = nonPromotionHistory;
List<Type>? newPromotedTypes;
for (;; numElementsToKeep--) {
if (numElementsToKeep == 0) {
break;
}
Type promoted = promotedTypes[numElementsToKeep - 1];
if (typeOperations.isSubtypeOf(writtenType, promoted)) {
if (numElementsToKeep == promotedTypes.length) {
newPromotedTypes = promotedTypes;
break;
}
newPromotedTypes = promotedTypes.sublist(0, numElementsToKeep);
break;
}
if (nonPromotionReason == null) {
assert(false, 'Demotion occurred but nonPromotionReason is null');
} else {
newNonPromotionHistory = new NonPromotionHistory<Type>(
promoted, nonPromotionReason, newNonPromotionHistory);
}
}
return new _DemotionResult<Type>(newPromotedTypes, newNonPromotionHistory);
}
/// Returns a promotion model that is the same as this one, but with the
/// variable definitely assigned.
PromotionModel<Type> _setAssigned() => assigned
? this
: new PromotionModel(
promotedTypes: promotedTypes,
tested: tested,
assigned: true,
unassigned: false,
ssaNode: ssaNode,
nonPromotionHistory: nonPromotionHistory);
/// Determines whether a variable with the given [promotedTypes] should be
/// promoted to [writtenType] based on types of interest. If it should,
/// returns an updated promotion chain; otherwise returns [promotedTypes]
/// unchanged.
///
/// Note that since promotion chains are considered immutable, if promotion
/// is required, a new promotion chain will be created and returned.
List<Type>? _tryPromoteToTypeOfInterest(
FlowAnalysisTypeOperations<Type> typeOperations,
Type declaredType,
List<Type>? promotedTypes,
Type writtenType) {
assert(!writeCaptured);
// Figure out if we have any promotion candidates (types that are a
// supertype of writtenType and a proper subtype of the currently-promoted
// type). If at any point we find an exact match, we take it immediately.
Type? currentlyPromotedType = promotedTypes?.last;
List<Type>? result;
List<Type>? candidates = null;
void handleTypeOfInterest(Type type) {