| // 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) { |
|