blob: afd3020a2139060141e28b0183f0cc6d55992cb5 [file] [log] [blame]
// Copyright (c) 2020, 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.
/// This file implements the AST of a Dart-like language suitable for testing
/// flow analysis. Callers may use the top level methods in this file to create
/// AST nodes and then feed them to [Harness.run] to run them through flow
/// analysis testing.
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:test/test.dart';
Expression get nullLiteral => new _NullLiteral();
Statement assert_(Expression condition, [Expression? message]) =>
new _Assert(condition, message);
Statement block(List<Statement> statements) => new _Block(statements);
Expression booleanLiteral(bool value) => _BooleanLiteral(value);
/// Wrapper allowing creation of a statement that can be used as the target of
/// `break` or `continue` statements. [callback] will be invoked to create the
/// statement, and it will be passed a [BranchTargetPlaceholder] that can be
/// passed to [break_] or [continue_].
Statement branchTarget(Statement Function(BranchTargetPlaceholder) callback) {
var branchTargetPlaceholder = BranchTargetPlaceholder._();
var stmt = callback(branchTargetPlaceholder);
branchTargetPlaceholder._target = stmt;
return stmt;
}
Statement break_(BranchTargetPlaceholder branchTargetPlaceholder) =>
new _Break(branchTargetPlaceholder);
SwitchCase case_(List<Statement> body, {bool hasLabel = false}) =>
SwitchCase._(hasLabel, body);
CatchClause catch_(
{Var? exception, Var? stackTrace, required List<Statement> body}) =>
CatchClause._(body, exception, stackTrace);
/// Creates a pseudo-statement whose function is to verify that flow analysis
/// considers [variable]'s assigned state to be [expectedAssignedState].
Statement checkAssigned(Var variable, bool expectedAssignedState) =>
new _CheckAssigned(variable, expectedAssignedState);
/// Creates a pseudo-statement whose function is to verify that flow analysis
/// considers [variable] to be un-promoted.
Statement checkNotPromoted(Var variable) => new _CheckPromoted(variable, null);
/// Creates a pseudo-statement whose function is to verify that flow analysis
/// considers [variable]'s assigned state to be promoted to [expectedTypeStr].
Statement checkPromoted(Var variable, String? expectedTypeStr) =>
new _CheckPromoted(variable, expectedTypeStr);
/// Creates a pseudo-statement whose function is to verify that flow analysis
/// considers the current location's reachability state to be
/// [expectedReachable].
Statement checkReachable(bool expectedReachable) =>
new _CheckReachable(expectedReachable);
/// Creates a pseudo-statement whose function is to verify that flow analysis
/// considers [variable]'s unassigned state to be [expectedUnassignedState].
Statement checkUnassigned(Var variable, bool expectedUnassignedState) =>
new _CheckUnassigned(variable, expectedUnassignedState);
Statement continue_(BranchTargetPlaceholder branchTargetPlaceholder) =>
new _Continue(branchTargetPlaceholder);
Statement declare(Var variable,
{required bool initialized,
bool isFinal = false,
bool isLate = false}) =>
new _Declare(variable, initialized ? expr(variable.type.type) : null,
isFinal, isLate);
Statement declareInitialized(Var variable, Expression initializer,
{bool isFinal = false, bool isLate = false}) =>
new _Declare(variable, initializer, isFinal, isLate);
Statement do_(List<Statement> body, Expression condition) =>
_Do(body, condition);
/// Creates a pseudo-expression having type [typeStr] that otherwise has no
/// effect on flow analysis.
Expression expr(String typeStr) =>
new _PlaceholderExpression(new Type(typeStr));
/// Creates a conventional `for` statement. Optional boolean [forCollection]
/// indicates that this `for` statement is actually a collection element, so
/// `null` should be passed to [for_bodyBegin].
Statement for_(Statement? initializer, Expression? condition,
Expression? updater, List<Statement> body,
{bool forCollection = false}) =>
new _For(initializer, condition, updater, body, forCollection);
/// Creates a "for each" statement where the identifier being assigned to by the
/// iteration is not a local variable.
///
/// This models code like:
/// var x; // Top level variable
/// f(Iterable iterable) {
/// for (x in iterable) { ... }
/// }
Statement forEachWithNonVariable(Expression iterable, List<Statement> body) =>
new _ForEach(null, iterable, body, false);
/// Creates a "for each" statement where the identifier being assigned to by the
/// iteration is a variable that is being declared by the "for each" statement.
///
/// This models code like:
/// f(Iterable iterable) {
/// for (var x in iterable) { ... }
/// }
Statement forEachWithVariableDecl(
Var variable, Expression iterable, List<Statement> body) {
// ignore: unnecessary_null_comparison
assert(variable != null);
return new _ForEach(variable, iterable, body, true);
}
/// Creates a "for each" statement where the identifier being assigned to by the
/// iteration is a local variable that is declared elsewhere in the function.
///
/// This models code like:
/// f(Iterable iterable) {
/// var x;
/// for (x in iterable) { ... }
/// }
Statement forEachWithVariableSet(
Var variable, Expression iterable, List<Statement> body) {
// ignore: unnecessary_null_comparison
assert(variable != null);
return new _ForEach(variable, iterable, body, false);
}
/// Creates a [Statement] that, when analyzed, will cause [callback] to be
/// passed an [SsaNodeHarness] allowing the test to examine the values of
/// variables' SSA nodes.
Statement getSsaNodes(void Function(SsaNodeHarness) callback) =>
new _GetSsaNodes(callback);
Statement if_(Expression condition, List<Statement> ifTrue,
[List<Statement>? ifFalse]) =>
new _If(condition, ifTrue, ifFalse);
Statement implicitThis_whyNotPromoted(String staticType,
void Function(Map<Type, NonPromotionReason>) callback) =>
new _WhyNotPromoted_ImplicitThis(Type(staticType), callback);
Statement labeled(Statement body) => new _LabeledStatement(body);
Statement localFunction(List<Statement> body) => _LocalFunction(body);
Statement return_() => new _Return();
Statement switch_(Expression expression, List<SwitchCase> cases,
{required bool isExhaustive}) =>
new _Switch(expression, cases, isExhaustive);
Expression this_(String type) => new _This(Type(type));
Expression thisOrSuperPropertyGet(String name, {String type = 'Object?'}) =>
new _ThisOrSuperPropertyGet(name, Type(type));
Expression throw_(Expression operand) => new _Throw(operand);
Statement tryCatch(List<Statement> body, List<CatchClause> catches) =>
new _TryCatch(body, catches);
Statement tryFinally(List<Statement> body, List<Statement> finally_) =>
new _TryFinally(body, finally_);
Statement while_(Expression condition, List<Statement> body) =>
new _While(condition, body);
/// Placeholder used by [branchTarget] to tie `break` and `continue` statements
/// to their branch targets.
class BranchTargetPlaceholder {
late Statement _target;
BranchTargetPlaceholder._();
}
/// Representation of a single catch clause in a try/catch statement. Use
/// [catch_] to create instances of this class.
class CatchClause implements _Visitable<void> {
final List<Statement> _body;
final Var? _exception;
final Var? _stackTrace;
CatchClause._(this._body, this._exception, this._stackTrace);
String toString() {
String initialPart;
if (_stackTrace != null) {
initialPart = 'catch (${_exception!.name}, ${_stackTrace!.name})';
} else if (_exception != null) {
initialPart = 'catch (${_exception!.name})';
} else {
initialPart = 'on ...';
}
return '$initialPart ${block(_body)}';
}
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
_body._preVisit(assignedVariables);
}
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.tryCatchStatement_catchBegin(_exception, _stackTrace);
_body._visit(h, flow);
flow.tryCatchStatement_catchEnd();
}
}
/// Representation of an expression in the pseudo-Dart language used for flow
/// analysis testing. Methods in this class may be used to create more complex
/// expressions based on this one.
abstract class Expression extends Node implements _Visitable<Type> {
Expression() : super._();
/// If `this` is an expression `x`, creates the expression `x!`.
Expression get nonNullAssert => new _NonNullAssert(this);
/// If `this` is an expression `x`, creates the expression `!x`.
Expression get not => new _Not(this);
/// If `this` is an expression `x`, creates the expression `(x)`.
Expression get parenthesized => new _ParenthesizedExpression(this);
/// If `this` is an expression `x`, creates the statement `x;`.
Statement get stmt => new _ExpressionStatement(this);
/// If `this` is an expression `x`, creates the expression `x && other`.
Expression and(Expression other) => new _Logical(this, other, isAnd: true);
/// If `this` is an expression `x`, creates the expression `x as typeStr`.
Expression as_(String typeStr) => new _As(this, Type(typeStr));
/// If `this` is an expression `x`, creates the expression
/// `x ? ifTrue : ifFalse`.
Expression conditional(Expression ifTrue, Expression ifFalse) =>
new _Conditional(this, ifTrue, ifFalse);
/// If `this` is an expression `x`, creates the expression `x == other`.
Expression eq(Expression other) => new _Equal(this, other, false);
/// Creates an [Expression] that, when analyzed, will behave the same as
/// `this`, but after visiting it, will cause [callback] to be passed the
/// [ExpressionInfo] associated with it. If the expression has no flow
/// analysis information associated with it, `null` will be passed to
/// [callback].
Expression getExpressionInfo(
void Function(ExpressionInfo<Var, Type>?) callback) =>
new _GetExpressionInfo(this, callback);
/// If `this` is an expression `x`, creates the expression `x ?? other`.
Expression ifNull(Expression other) => new _IfNull(this, other);
/// If `this` is an expression `x`, creates the expression `x is typeStr`.
///
/// With [isInverted] set to `true`, creates the expression `x is! typeStr`.
Expression is_(String typeStr, {bool isInverted = false}) =>
new _Is(this, Type(typeStr), isInverted);
/// If `this` is an expression `x`, creates the expression `x is! typeStr`.
Expression isNot(String typeStr) => _Is(this, Type(typeStr), true);
/// If `this` is an expression `x`, creates the expression `x != other`.
Expression notEq(Expression other) => _Equal(this, other, true);
/// If `this` is an expression `x`, creates the expression `x?.other`.
///
/// Note that in the real Dart language, the RHS of a null aware access isn't
/// strictly speaking an expression. However for flow analysis it suffices to
/// model it as an expression.
Expression nullAwareAccess(Expression other, {bool isCascaded = false}) =>
_NullAwareAccess(this, other, isCascaded);
/// If `this` is an expression `x`, creates the expression `x || other`.
Expression or(Expression other) => new _Logical(this, other, isAnd: false);
/// If `this` is an expression `x`, creates the expression `x.name`.
Expression propertyGet(String name, {String type = 'Object?'}) =>
new _PropertyGet(this, name, Type(type));
/// If `this` is an expression `x`, creates a pseudo-expression that models
/// evaluation of `x` followed by execution of [stmt]. This can be used to
/// test that flow analysis is in the correct state after an expression is
/// visited.
Expression thenStmt(Statement stmt) =>
new _WrappedExpression(null, this, stmt);
/// Creates an [Expression] that, when analyzed, will behave the same as
/// `this`, but after visiting it, will cause [callback] to be passed the
/// non-promotion info associated with it. If the expression has no
/// non-promotion info, an empty map will be passed to [callback].
Expression whyNotPromoted(
void Function(Map<Type, NonPromotionReason>) callback) =>
new _WhyNotPromoted(this, callback);
}
/// Test harness for creating flow analysis tests. This class implements all
/// the [TypeOperations] needed by flow analysis, as well as other methods
/// needed for testing.
class Harness extends TypeOperations<Var, Type> {
static const Map<String, bool> _coreSubtypes = const {
'bool <: int': false,
'bool <: Object': true,
'double <: Object': true,
'double <: num': true,
'double <: num?': true,
'double <: int': false,
'double <: int?': false,
'int <: double': false,
'int <: int?': true,
'int <: Iterable': false,
'int <: List': false,
'int <: Null': false,
'int <: num': true,
'int <: num?': true,
'int <: num*': true,
'int <: Never?': false,
'int <: Object': true,
'int <: Object?': true,
'int <: String': false,
'int? <: int': false,
'int? <: Null': false,
'int? <: num': false,
'int? <: num?': true,
'int? <: Object': false,
'int? <: Object?': true,
'Never <: Object?': true,
'Null <: int': false,
'Null <: Object': false,
'Null <: Object?': true,
'num <: int': false,
'num <: Iterable': false,
'num <: List': false,
'num <: num?': true,
'num <: num*': true,
'num <: Object': true,
'num <: Object?': true,
'num? <: int?': false,
'num? <: num': false,
'num? <: num*': true,
'num? <: Object': false,
'num? <: Object?': true,
'num* <: num': true,
'num* <: num?': true,
'num* <: Object': true,
'num* <: Object?': true,
'Iterable <: int': false,
'Iterable <: num': false,
'Iterable <: Object': true,
'Iterable <: Object?': true,
'List <: int': false,
'List <: Iterable': true,
'List <: Object': true,
'Never <: int': true,
'Never <: int?': true,
'Never <: Null': true,
'Never? <: int': false,
'Never? <: int?': true,
'Never? <: num?': true,
'Never? <: Object?': true,
'Null <: int?': true,
'Object <: int': false,
'Object <: int?': false,
'Object <: List': false,
'Object <: Null': false,
'Object <: num': false,
'Object <: num?': false,
'Object <: Object?': true,
'Object <: String': false,
'Object? <: Object': false,
'Object? <: int': false,
'Object? <: int?': false,
'Object? <: Null': false,
'String <: int': false,
'String <: int?': false,
'String <: num?': false,
'String <: Object': true,
'String <: Object?': true,
};
static final Map<String, Type> _coreFactors = {
'Object? - int': Type('Object?'),
'Object? - int?': Type('Object'),
'Object? - Never': Type('Object?'),
'Object? - Null': Type('Object'),
'Object? - num?': Type('Object'),
'Object? - Object?': Type('Never?'),
'Object? - String': Type('Object?'),
'Object - bool': Type('Object'),
'Object - int': Type('Object'),
'Object - String': Type('Object'),
'int - Object': Type('Never'),
'int - String': Type('int'),
'int - int': Type('Never'),
'int - int?': Type('Never'),
'int? - int': Type('Never?'),
'int? - int?': Type('Never'),
'int? - String': Type('int?'),
'Null - int': Type('Null'),
'num - int': Type('num'),
'num? - num': Type('Never?'),
'num? - int': Type('num?'),
'num? - int?': Type('num'),
'num? - Object': Type('Never?'),
'num? - String': Type('num?'),
'Object - int?': Type('Object'),
'Object - num': Type('Object'),
'Object - num?': Type('Object'),
'Object - num*': Type('Object'),
'Object - Iterable': Type('Object'),
'Object? - Object': Type('Never?'),
'Object? - Iterable': Type('Object?'),
'Object? - num': Type('Object?'),
'Iterable - List': Type('Iterable'),
'num* - Object': Type('Never'),
};
final bool legacy;
final Map<String, bool> _subtypes = Map.of(_coreSubtypes);
final Map<String, Type> _factorResults = Map.of(_coreFactors);
Node? _currentSwitch;
Map<String, Map<String, String>> _promotionExceptions = {};
Harness({this.legacy = false});
/// Updates the harness so that when a [factor] query is invoked on types
/// [from] and [what], [result] will be returned.
void addFactor(String from, String what, String result) {
var query = '$from - $what';
_factorResults[query] = Type(result);
}
void addPromotionException(String from, String to, String result) {
(_promotionExceptions[from] ??= {})[to] = result;
}
/// Updates the harness so that when an [isSubtypeOf] query is invoked on
/// types [leftType] and [rightType], [isSubtype] will be returned.
void addSubtype(String leftType, String rightType, bool isSubtype) {
var query = '$leftType <: $rightType';
_subtypes[query] = isSubtype;
}
@override
TypeClassification classifyType(Type type) {
if (isSubtypeOf(type, Type('Object'))) {
return TypeClassification.nonNullable;
} else if (isSubtypeOf(type, Type('Null'))) {
return TypeClassification.nullOrEquivalent;
} else {
return TypeClassification.potentiallyNullable;
}
}
@override
Type factor(Type from, Type what) {
var query = '$from - $what';
return _factorResults[query] ?? fail('Unknown factor query: $query');
}
@override
bool isNever(Type type) {
return type.type == 'Never';
}
@override
bool isSameType(Type type1, Type type2) {
return type1.type == type2.type;
}
@override
bool isSubtypeOf(Type leftType, Type rightType) {
if (leftType.type == rightType.type) return true;
var query = '$leftType <: $rightType';
return _subtypes[query] ?? fail('Unknown subtype query: $query');
}
@override
Type promoteToNonNull(Type type) {
if (type.type.endsWith('?')) {
return Type(type.type.substring(0, type.type.length - 1));
} else if (type.type == 'Null') {
return Type('Never');
} else {
return type;
}
}
/// Runs the given [statements] through flow analysis, checking any assertions
/// they contain.
void run(List<Statement> statements) {
var assignedVariables = AssignedVariables<Node, Var>();
statements._preVisit(assignedVariables);
var flow = legacy
? FlowAnalysis<Node, Statement, Expression, Var, Type>.legacy(
this, assignedVariables)
: FlowAnalysis<Node, Statement, Expression, Var, Type>(
this, assignedVariables);
statements._visit(this, flow);
flow.finish();
}
@override
Type? tryPromoteToType(Type to, Type from) {
var exception = (_promotionExceptions[from.type] ?? {})[to.type];
if (exception != null) {
return Type(exception);
}
if (isSubtypeOf(to, from)) {
return to;
} else {
return null;
}
}
@override
Type variableType(Var variable) {
return variable.type;
}
Type _getIteratedType(Type iterableType) {
var typeStr = iterableType.type;
if (typeStr.startsWith('List<') && typeStr.endsWith('>')) {
return Type(typeStr.substring(5, typeStr.length - 1));
} else {
throw UnimplementedError('TODO(paulberry): getIteratedType($typeStr)');
}
}
Type _lub(Type type1, Type type2) {
if (isSameType(type1, type2)) {
return type1;
} else if (isSameType(promoteToNonNull(type1), type2)) {
return type1;
} else if (isSameType(promoteToNonNull(type2), type1)) {
return type2;
} else if (type1.type == 'Null' &&
!isSameType(promoteToNonNull(type2), type2)) {
// type2 is already nullable
return type2;
} else if (type2.type == 'Null' &&
!isSameType(promoteToNonNull(type1), type1)) {
// type1 is already nullable
return type1;
} else if (type1.type == 'Never') {
return type2;
} else if (type2.type == 'Never') {
return type1;
} else {
throw UnimplementedError(
'TODO(paulberry): least upper bound of $type1 and $type2');
}
}
}
/// Representation of an expression or statement in the pseudo-Dart language
/// used for flow analysis testing.
class Node {
static int _nextId = 0;
final int id;
Node._() : id = _nextId++;
String toString() => 'Node#$id';
}
/// Helper class allowing tests to examine the values of variables' SSA nodes.
class SsaNodeHarness {
final FlowAnalysis<Node, Statement, Expression, Var, Type> _flow;
SsaNodeHarness(this._flow);
/// Gets the SSA node associated with [variable] at the current point in
/// control flow, or `null` if the variable has been write captured.
SsaNode<Var, Type>? operator [](Var variable) =>
_flow.ssaNodeForTesting(variable);
}
/// Representation of a statement in the pseudo-Dart language used for flow
/// analysis testing.
abstract class Statement extends Node implements _Visitable<void> {
Statement._() : super._();
/// If `this` is a statement `x`, creates a pseudo-expression that models
/// execution of `x` followed by evaluation of [expr]. This can be used to
/// test that flow analysis is in the correct state before an expression is
/// visited.
Expression thenExpr(Expression expr) => _WrappedExpression(this, expr, null);
}
/// Representation of a single case clause in a switch statement. Use [case_]
/// to create instances of this class.
class SwitchCase implements _Visitable<void> {
final bool _hasLabel;
final List<Statement> _body;
SwitchCase._(this._hasLabel, this._body);
String toString() =>
[if (_hasLabel) '<label>:', 'case <value>:', ..._body].join(' ');
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
_body._preVisit(assignedVariables);
}
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.switchStatement_beginCase(_hasLabel, h._currentSwitch!);
_body._visit(h, flow);
}
}
/// Representation of a type in the pseudo-Dart language used for flow analysis
/// testing. This is essentially a thin wrapper around a string representation
/// of the type.
class Type {
static bool _allowComparisons = false;
final String type;
Type(this.type);
@override
int get hashCode {
if (!_allowComparisons) {
// The flow analysis engine should not hash types using hashCode. It
// should compare them using TypeOperations.
fail('Unexpected use of operator== on types');
}
return type.hashCode;
}
@override
bool operator ==(Object other) {
if (!_allowComparisons) {
// The flow analysis engine should not compare types using operator==. It
// should compare them using TypeOperations.
fail('Unexpected use of operator== on types');
}
return other is Type && this.type == other.type;
}
@override
String toString() => type;
}
/// Representation of a local variable in the pseudo-Dart language used for flow
/// analysis testing.
class Var {
final String name;
final Type type;
Var(this.name, String typeStr) : type = Type(typeStr);
/// Creates an expression representing a read of this variable.
Expression get read => new _VariableRead(this, null);
/// Creates an expression representing a read of this variable, which as a
/// side effect will call the given callback with the returned promoted type.
Expression readAndCheckPromotedType(void Function(Type?) callback) =>
new _VariableRead(this, callback);
@override
String toString() => '$type $name';
/// Creates an expression representing a write to this variable.
Expression write(Expression? value) => new _Write(this, value);
}
class _As extends Expression {
final Expression target;
final Type type;
_As(this.target, this.type);
@override
String toString() => '$target as $type';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
target._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
target._visit(h, flow);
flow.asExpression_end(target, type);
return type;
}
}
class _Assert extends Statement {
final Expression condition;
final Expression? message;
_Assert(this.condition, this.message) : super._();
@override
String toString() =>
'assert($condition${message == null ? '' : ', $message'});';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
condition._preVisit(assignedVariables);
message?._preVisit(assignedVariables);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.assert_begin();
flow.assert_afterCondition(condition.._visit(h, flow));
message?._visit(h, flow);
flow.assert_end();
}
}
class _Block extends Statement {
final List<Statement> statements;
_Block(this.statements) : super._();
@override
String toString() =>
statements.isEmpty ? '{}' : '{ ${statements.join(' ')} }';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
statements._preVisit(assignedVariables);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
statements._visit(h, flow);
}
}
class _BooleanLiteral extends Expression {
final bool value;
_BooleanLiteral(this.value);
@override
String toString() => '$value';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.booleanLiteral(this, value);
return Type('bool');
}
}
class _Break extends Statement {
final BranchTargetPlaceholder branchTargetPlaceholder;
_Break(this.branchTargetPlaceholder) : super._();
@override
String toString() => 'break;';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
// ignore: unnecessary_null_comparison
assert(branchTargetPlaceholder._target != null);
flow.handleBreak(branchTargetPlaceholder._target);
}
}
class _CheckAssigned extends Statement {
final Var variable;
final bool expectedAssignedState;
_CheckAssigned(this.variable, this.expectedAssignedState) : super._();
@override
String toString() {
var verb = expectedAssignedState ? 'is' : 'is not';
return 'check $variable $verb definitely assigned;';
}
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
expect(flow.isAssigned(variable), expectedAssignedState);
}
}
class _CheckPromoted extends Statement {
final Var variable;
final String? expectedTypeStr;
_CheckPromoted(this.variable, this.expectedTypeStr) : super._();
@override
String toString() {
var predicate = expectedTypeStr == null
? 'not promoted'
: 'promoted to $expectedTypeStr';
return 'check $variable $predicate;';
}
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var promotedType = flow.promotedType(variable);
if (expectedTypeStr == null) {
expect(promotedType, isNull);
} else {
expect(promotedType?.type, expectedTypeStr);
}
}
}
class _CheckReachable extends Statement {
final bool expectedReachable;
_CheckReachable(this.expectedReachable) : super._();
@override
String toString() => 'check reachable;';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
expect(flow.isReachable, expectedReachable);
}
}
class _CheckUnassigned extends Statement {
final Var variable;
final bool expectedUnassignedState;
_CheckUnassigned(this.variable, this.expectedUnassignedState) : super._();
@override
String toString() {
var verb = expectedUnassignedState ? 'is' : 'is not';
return 'check $variable $verb definitely unassigned;';
}
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
expect(flow.isUnassigned(variable), expectedUnassignedState);
}
}
class _Conditional extends Expression {
final Expression condition;
final Expression ifTrue;
final Expression ifFalse;
_Conditional(this.condition, this.ifTrue, this.ifFalse);
@override
String toString() => '$condition ? $ifTrue : $ifFalse';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
condition._preVisit(assignedVariables);
assignedVariables.beginNode();
ifTrue._preVisit(assignedVariables);
assignedVariables.endNode(this);
ifFalse._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.conditional_conditionBegin();
flow.conditional_thenBegin(condition.._visit(h, flow), this);
var ifTrueType = ifTrue._visit(h, flow);
flow.conditional_elseBegin(ifTrue);
var ifFalseType = ifFalse._visit(h, flow);
flow.conditional_end(this, ifFalse);
return h._lub(ifTrueType, ifFalseType);
}
}
class _Continue extends Statement {
final BranchTargetPlaceholder branchTargetPlaceholder;
_Continue(this.branchTargetPlaceholder) : super._();
@override
String toString() => 'continue;';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
// ignore: unnecessary_null_comparison
assert(branchTargetPlaceholder._target != null);
flow.handleContinue(branchTargetPlaceholder._target);
}
}
class _Declare extends Statement {
final Var variable;
final Expression? initializer;
final bool isFinal;
final bool isLate;
_Declare(this.variable, this.initializer, this.isFinal, this.isLate)
: super._();
@override
String toString() {
var latePart = isLate ? 'late ' : '';
var finalPart = isFinal ? 'final ' : '';
var initializerPart = initializer != null ? ' = $initializer' : '';
return '$latePart$finalPart$variable${initializerPart};';
}
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
initializer?._preVisit(assignedVariables);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var initializer = this.initializer;
if (initializer == null) {
flow.declare(variable, false);
} else {
var initializerType = initializer._visit(h, flow);
flow.declare(variable, true);
flow.initialize(variable, initializerType, initializer,
isFinal: isFinal, isLate: isLate);
}
}
}
class _Do extends Statement {
final List<Statement> body;
final Expression condition;
_Do(this.body, this.condition) : super._();
@override
String toString() => 'do ${block(body)} while ($condition);';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
assignedVariables.beginNode();
body._preVisit(assignedVariables);
condition._preVisit(assignedVariables);
assignedVariables.endNode(this);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.doStatement_bodyBegin(this);
body._visit(h, flow);
flow.doStatement_conditionBegin();
condition._visit(h, flow);
flow.doStatement_end(condition);
}
}
class _Equal extends Expression {
final Expression lhs;
final Expression rhs;
final bool isInverted;
_Equal(this.lhs, this.rhs, this.isInverted);
@override
String toString() => '$lhs ${isInverted ? '!=' : '=='} $rhs';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
lhs._preVisit(assignedVariables);
rhs._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var lhsType = lhs._visit(h, flow);
flow.equalityOp_rightBegin(lhs, lhsType);
var rhsType = rhs._visit(h, flow);
flow.equalityOp_end(this, rhs, rhsType, notEqual: isInverted);
return Type('bool');
}
}
class _ExpressionStatement extends Statement {
final Expression expr;
_ExpressionStatement(this.expr) : super._();
@override
String toString() => '$expr;';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
expr._preVisit(assignedVariables);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
expr._visit(h, flow);
}
}
class _For extends Statement {
final Statement? initializer;
final Expression? condition;
final Expression? updater;
final List<Statement> body;
final bool forCollection;
_For(this.initializer, this.condition, this.updater, this.body,
this.forCollection)
: super._();
@override
String toString() {
var buffer = StringBuffer('for (');
if (initializer == null) {
buffer.write(';');
} else {
buffer.write(initializer);
}
if (condition == null) {
buffer.write(';');
} else {
buffer.write(' $condition;');
}
if (updater != null) {
buffer.write(' $updater');
}
buffer.write(') ${block(body)}');
return buffer.toString();
}
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
initializer?._preVisit(assignedVariables);
assignedVariables.beginNode();
condition?._preVisit(assignedVariables);
body._preVisit(assignedVariables);
updater?._preVisit(assignedVariables);
assignedVariables.endNode(this);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
initializer?._visit(h, flow);
flow.for_conditionBegin(this);
condition?._visit(h, flow);
flow.for_bodyBegin(forCollection ? null : this, condition);
body._visit(h, flow);
flow.for_updaterBegin();
updater?._visit(h, flow);
flow.for_end();
}
}
class _ForEach extends Statement {
final Var? variable;
final Expression iterable;
final List<Statement> body;
final bool declaresVariable;
_ForEach(this.variable, this.iterable, this.body, this.declaresVariable)
: super._();
@override
String toString() {
String declarationPart;
if (variable == null) {
declarationPart = '<identifier>';
} else if (declaresVariable) {
declarationPart = variable.toString();
} else {
declarationPart = variable!.name;
}
return 'for ($declarationPart in $iterable) ${block(body)}';
}
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
iterable._preVisit(assignedVariables);
if (variable != null) {
if (declaresVariable) {
assignedVariables.declare(variable!);
} else {
assignedVariables.write(variable!);
}
}
assignedVariables.beginNode();
body._preVisit(assignedVariables);
assignedVariables.endNode(this);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var iteratedType = h._getIteratedType(iterable._visit(h, flow));
flow.forEach_bodyBegin(this, variable, iteratedType);
body._visit(h, flow);
flow.forEach_end();
}
}
class _GetExpressionInfo extends Expression {
final Expression target;
final void Function(ExpressionInfo<Var, Type>?) callback;
_GetExpressionInfo(this.target, this.callback);
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
target._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var type = target._visit(h, flow);
flow.forwardExpression(this, target);
callback(flow.expressionInfoForTesting(this));
return type;
}
}
class _GetSsaNodes extends Statement {
final void Function(SsaNodeHarness) callback;
_GetSsaNodes(this.callback) : super._();
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
callback(SsaNodeHarness(flow));
}
}
class _If extends Statement {
final Expression condition;
final List<Statement> ifTrue;
final List<Statement>? ifFalse;
_If(this.condition, this.ifTrue, this.ifFalse) : super._();
@override
String toString() =>
'if ($condition) ${block(ifTrue)}' +
(ifFalse == null ? '' : 'else ${block(ifFalse!)}');
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
condition._preVisit(assignedVariables);
assignedVariables.beginNode();
ifTrue._preVisit(assignedVariables);
assignedVariables.endNode(this);
ifFalse?._preVisit(assignedVariables);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.ifStatement_conditionBegin();
flow.ifStatement_thenBegin(condition.._visit(h, flow), this);
ifTrue._visit(h, flow);
if (ifFalse == null) {
flow.ifStatement_end(false);
} else {
flow.ifStatement_elseBegin();
ifFalse!._visit(h, flow);
flow.ifStatement_end(true);
}
}
}
class _IfNull extends Expression {
final Expression lhs;
final Expression rhs;
_IfNull(this.lhs, this.rhs);
@override
String toString() => '$lhs ?? $rhs';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
lhs._preVisit(assignedVariables);
rhs._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var lhsType = lhs._visit(h, flow);
flow.ifNullExpression_rightBegin(lhs, lhsType);
var rhsType = rhs._visit(h, flow);
flow.ifNullExpression_end();
return h._lub(h.promoteToNonNull(lhsType), rhsType);
}
}
class _Is extends Expression {
final Expression target;
final Type type;
final bool isInverted;
_Is(this.target, this.type, this.isInverted);
@override
String toString() => '$target is${isInverted ? '!' : ''} $type';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
target._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.isExpression_end(this, target.._visit(h, flow), isInverted, type);
return Type('bool');
}
}
class _LabeledStatement extends Statement {
final Statement body;
_LabeledStatement(this.body) : super._();
@override
String toString() => 'labeled: $body';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
body._preVisit(assignedVariables);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.labeledStatement_begin(this);
body._visit(h, flow);
flow.labeledStatement_end();
}
}
class _LocalFunction extends Statement {
final List<Statement> body;
_LocalFunction(this.body) : super._();
@override
String toString() => '() ${block(body)}';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
assignedVariables.beginNode();
body._preVisit(assignedVariables);
assignedVariables.endNode(this, isClosureOrLateVariableInitializer: true);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.functionExpression_begin(this);
body._visit(h, flow);
flow.functionExpression_end();
}
}
class _Logical extends Expression {
final Expression lhs;
final Expression rhs;
final bool isAnd;
_Logical(this.lhs, this.rhs, {required this.isAnd});
@override
String toString() => '$lhs ${isAnd ? '&&' : '||'} $rhs';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
lhs._preVisit(assignedVariables);
assignedVariables.beginNode();
rhs._preVisit(assignedVariables);
assignedVariables.endNode(this);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.logicalBinaryOp_begin();
flow.logicalBinaryOp_rightBegin(lhs.._visit(h, flow), this, isAnd: isAnd);
flow.logicalBinaryOp_end(this, rhs.._visit(h, flow), isAnd: isAnd);
return Type('bool');
}
}
class _NonNullAssert extends Expression {
final Expression operand;
_NonNullAssert(this.operand);
@override
String toString() => '$operand!';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
operand._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var type = operand._visit(h, flow);
flow.nonNullAssert_end(operand);
return h.promoteToNonNull(type);
}
}
class _Not extends Expression {
final Expression operand;
_Not(this.operand);
@override
String toString() => '!$operand';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
operand._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.logicalNot_end(this, operand.._visit(h, flow));
return Type('bool');
}
}
class _NullAwareAccess extends Expression {
final Expression lhs;
final Expression rhs;
final bool isCascaded;
_NullAwareAccess(this.lhs, this.rhs, this.isCascaded);
@override
String toString() => '$lhs?.${isCascaded ? '.' : ''}($rhs)';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
lhs._preVisit(assignedVariables);
rhs._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var lhsType = lhs._visit(h, flow);
flow.nullAwareAccess_rightBegin(isCascaded ? null : lhs, lhsType);
var rhsType = rhs._visit(h, flow);
flow.nullAwareAccess_end();
return h._lub(rhsType, Type('Null'));
}
}
class _NullLiteral extends Expression {
_NullLiteral();
@override
String toString() => 'null';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.nullLiteral(this);
return Type('Null');
}
}
class _ParenthesizedExpression extends Expression {
final Expression expr;
_ParenthesizedExpression(this.expr);
@override
String toString() => '($expr)';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
expr._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var type = expr._visit(h, flow);
flow.parenthesizedExpression(this, expr);
return type;
}
}
class _PlaceholderExpression extends Expression {
final Type type;
_PlaceholderExpression(this.type);
@override
String toString() => '(expr with type $type)';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
Type _visit(Harness h,
FlowAnalysis<Node, Statement, Expression, Var, Type> flow) =>
type;
}
class _PropertyGet extends Expression {
final Expression target;
final String propertyName;
final Type type;
_PropertyGet(this.target, this.propertyName, this.type);
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
target._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
target._visit(h, flow);
flow.propertyGet(this, target, propertyName, type);
return type;
}
}
class _Return extends Statement {
_Return() : super._();
@override
String toString() => 'return;';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.handleExit();
}
}
class _Switch extends Statement {
final Expression expression;
final List<SwitchCase> cases;
final bool isExhaustive;
_Switch(this.expression, this.cases, this.isExhaustive) : super._();
@override
String toString() {
var exhaustiveness = isExhaustive ? 'exhaustive' : 'non-exhaustive';
String body;
if (cases.isEmpty) {
body = '{}';
} else {
var contents = cases.join(' ');
body = '{ $contents }';
}
return 'switch<$exhaustiveness> ($expression) $body';
}
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
expression._preVisit(assignedVariables);
assignedVariables.beginNode();
cases._preVisit(assignedVariables);
assignedVariables.endNode(this);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
expression._visit(h, flow);
flow.switchStatement_expressionEnd(this);
var oldSwitch = h._currentSwitch;
h._currentSwitch = this;
cases._visit(h, flow);
h._currentSwitch = oldSwitch;
flow.switchStatement_end(isExhaustive);
}
}
class _This extends Expression {
final Type type;
_This(this.type);
@override
String toString() => 'this';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.thisOrSuper(this, type);
return type;
}
}
class _ThisOrSuperPropertyGet extends Expression {
final String propertyName;
final Type type;
_ThisOrSuperPropertyGet(this.propertyName, this.type);
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.thisOrSuperPropertyGet(this, propertyName, type);
return type;
}
}
class _Throw extends Expression {
final Expression operand;
_Throw(this.operand);
@override
String toString() => 'throw ...';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
operand._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
operand._visit(h, flow);
flow.handleExit();
return Type('Never');
}
}
class _TryCatch extends Statement {
final List<Statement> body;
final List<CatchClause> catches;
_TryCatch(this.body, this.catches) : super._();
@override
String toString() => 'try ${block(body)} ${catches.join(' ')}';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
assignedVariables.beginNode();
body._preVisit(assignedVariables);
assignedVariables.endNode(this);
catches._preVisit(assignedVariables);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.tryCatchStatement_bodyBegin();
body._visit(h, flow);
flow.tryCatchStatement_bodyEnd(this);
catches._visit(h, flow);
flow.tryCatchStatement_end();
}
}
class _TryFinally extends Statement {
final List<Statement> body;
final List<Statement> finally_;
final Node _bodyNode = Node._();
_TryFinally(this.body, this.finally_) : super._();
@override
String toString() => 'try ${block(body)} finally ${block(finally_)}';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
assignedVariables.beginNode();
body._preVisit(assignedVariables);
assignedVariables.endNode(_bodyNode);
finally_._preVisit(assignedVariables);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.tryFinallyStatement_bodyBegin();
body._visit(h, flow);
flow.tryFinallyStatement_finallyBegin(_bodyNode);
finally_._visit(h, flow);
flow.tryFinallyStatement_end();
}
}
class _VariableRead extends Expression {
final Var variable;
final void Function(Type?)? callback;
_VariableRead(this.variable, this.callback);
@override
String toString() => variable.name;
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
assignedVariables.read(variable);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var readResult = flow.variableRead(this, variable);
callback?.call(readResult);
return readResult ?? variable.type;
}
}
abstract class _Visitable<T> {
void _preVisit(AssignedVariables<Node, Var> assignedVariables);
T _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow);
}
class _While extends Statement {
final Expression condition;
final List<Statement> body;
_While(this.condition, this.body) : super._();
@override
String toString() => 'while ($condition) ${block(body)}';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
assignedVariables.beginNode();
condition._preVisit(assignedVariables);
body._preVisit(assignedVariables);
assignedVariables.endNode(this);
}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.whileStatement_conditionBegin(this);
condition._visit(h, flow);
flow.whileStatement_bodyBegin(this, condition);
body._visit(h, flow);
flow.whileStatement_end();
}
}
class _WhyNotPromoted extends Expression {
final Expression target;
final void Function(Map<Type, NonPromotionReason>) callback;
_WhyNotPromoted(this.target, this.callback);
@override
String toString() => '$target (whyNotPromoted)';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
target._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var type = target._visit(h, flow);
flow.forwardExpression(this, target);
assert(!Type._allowComparisons);
Type._allowComparisons = true;
try {
callback(flow.whyNotPromoted(this));
} finally {
Type._allowComparisons = false;
}
return type;
}
}
class _WhyNotPromoted_ImplicitThis extends Statement {
final Type staticType;
final void Function(Map<Type, NonPromotionReason>) callback;
_WhyNotPromoted_ImplicitThis(this.staticType, this.callback) : super._();
@override
String toString() => 'implicit this (whyNotPromoted)';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
assert(!Type._allowComparisons);
Type._allowComparisons = true;
try {
callback(flow.whyNotPromotedImplicitThis(staticType));
} finally {
Type._allowComparisons = false;
}
}
}
class _WrappedExpression extends Expression {
final Statement? before;
final Expression expr;
final Statement? after;
_WrappedExpression(this.before, this.expr, this.after);
@override
String toString() {
var s = StringBuffer('(');
if (before != null) {
s.write('($before) ');
}
s.write(expr);
if (after != null) {
s.write(' ($after)');
}
s.write(')');
return s.toString();
}
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
before?._preVisit(assignedVariables);
expr._preVisit(assignedVariables);
after?._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
before?._visit(h, flow);
var type = expr._visit(h, flow);
after?._visit(h, flow);
flow.forwardExpression(this, expr);
return type;
}
}
class _Write extends Expression {
final Var variable;
final Expression? rhs;
_Write(this.variable, this.rhs);
@override
String toString() => '${variable.name} = $rhs';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
assignedVariables.write(variable);
rhs?._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var rhs = this.rhs;
var type = rhs == null ? variable.type : rhs._visit(h, flow);
flow.write(this, variable, type, rhs);
return type;
}
}
extension on List<_Visitable<void>> {
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
for (var element in this) {
element._preVisit(assignedVariables);
}
}
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
for (var element in this) {
element._visit(h, flow);
}
}
}