4d06516832
This is a simple string-based intermediate representation for Dart code, similar to kernel but with a far simpler structure and a compact string encoding, suitable for use in unit tests. I've integrated this representation into mini_ast.dart in a preliminary way, so it is created as a by-product of running flow analysis tests. But as yet there are no tests that validate the correctness of the result. I plan to refine the representation, and test it fully, in follow-up CLs. Change-Id: Idda527efd005d0199ff88861c9e074eb651d82f0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/193881 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
2173 lines
61 KiB
Dart
2173 lines
61 KiB
Dart
// 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';
|
|
|
|
import 'mini_ir.dart';
|
|
import 'mini_types.dart';
|
|
|
|
Expression get nullLiteral => new _NullLiteral();
|
|
|
|
Expression get this_ => new _This();
|
|
|
|
Statement assert_(Expression condition, [Expression? message]) =>
|
|
new _Assert(condition, message);
|
|
|
|
Statement block(List<Statement> statements) => new _Block(statements);
|
|
|
|
Expression booleanLiteral(bool value) => _BooleanLiteral(value);
|
|
|
|
Statement break_([LabeledStatement? target]) => new _Break(target);
|
|
|
|
SwitchCase case_(List<Statement> body, {bool hasLabel = false}) =>
|
|
SwitchCase._(hasLabel, new _Block(body));
|
|
|
|
/// 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_() => new _Continue();
|
|
|
|
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(block(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, block(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, block(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, block(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, block(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, block(ifTrue), ifFalse == null ? null : block(ifFalse));
|
|
|
|
Statement implicitThis_whyNotPromoted(String staticType,
|
|
void Function(Map<Type, NonPromotionReason>) callback) =>
|
|
new _WhyNotPromoted_ImplicitThis(Type(staticType), callback);
|
|
|
|
Statement labeled(Statement Function(LabeledStatement) callback) {
|
|
var labeledStatement = LabeledStatement._();
|
|
labeledStatement._body = callback(labeledStatement);
|
|
return labeledStatement;
|
|
}
|
|
|
|
Statement localFunction(List<Statement> body) => _LocalFunction(block(body));
|
|
|
|
Statement return_() => new _Return();
|
|
|
|
Statement switch_(Expression expression, List<SwitchCase> cases,
|
|
{required bool isExhaustive}) =>
|
|
new _Switch(expression, cases, isExhaustive);
|
|
|
|
Expression thisOrSuperPropertyGet(String name) =>
|
|
new _ThisOrSuperPropertyGet(name);
|
|
|
|
Expression throw_(Expression operand) => new _Throw(operand);
|
|
|
|
TryBuilder try_(List<Statement> body) =>
|
|
new _TryStatement(block(body), [], null);
|
|
|
|
Statement while_(Expression condition, List<Statement> body) =>
|
|
new _While(condition, block(body));
|
|
|
|
/// 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 {
|
|
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 L-value `x.name`.
|
|
LValue property(String name) => new _Property(this, name);
|
|
|
|
/// 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);
|
|
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables);
|
|
|
|
Type _visit(Harness h, Type context);
|
|
}
|
|
|
|
/// 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'),
|
|
};
|
|
|
|
late final FlowAnalysis<Node, Statement, Expression, Var, Type> _flow;
|
|
|
|
final bool legacy;
|
|
|
|
final Type? thisType;
|
|
|
|
final Map<String, bool> _subtypes = Map.of(_coreSubtypes);
|
|
|
|
final Map<String, Type> _factorResults = Map.of(_coreFactors);
|
|
|
|
final Map<String, Type> _members = {};
|
|
|
|
Map<String, Map<String, String>> _promotionExceptions = {};
|
|
|
|
late final _typeAnalyzer = _MiniAstTypeAnalyzer(this);
|
|
|
|
Harness({this.legacy = false, String? thisType})
|
|
: thisType = thisType == null ? null : Type(thisType);
|
|
|
|
MiniIrBuilder get _irBuilder => _typeAnalyzer._irBuilder;
|
|
|
|
/// 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);
|
|
}
|
|
|
|
/// Updates the harness so that when member [memberName] is looked up on type
|
|
/// [targetType], a member is found having the given [type].
|
|
void addMember(String targetType, String memberName, String type) {
|
|
var query = '$targetType.$memberName';
|
|
_members[query] = Type(type);
|
|
}
|
|
|
|
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');
|
|
}
|
|
|
|
/// Attempts to look up a member named [memberName] in the given [type]. If
|
|
/// a member is found, returns its type. Otherwise the test fails.
|
|
Type getMember(Type type, String memberName) {
|
|
var query = '$type.$memberName';
|
|
return _members[query] ?? fail('Unknown member 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>();
|
|
var b = block(statements);
|
|
b._preVisit(assignedVariables);
|
|
_flow = legacy
|
|
? FlowAnalysis<Node, Statement, Expression, Var, Type>.legacy(
|
|
this, assignedVariables)
|
|
: FlowAnalysis<Node, Statement, Expression, Var, Type>(
|
|
this, assignedVariables);
|
|
_typeAnalyzer.dispatchStatement(b);
|
|
_typeAnalyzer.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');
|
|
}
|
|
}
|
|
}
|
|
|
|
class LabeledStatement extends Statement {
|
|
late final Statement _body;
|
|
|
|
LabeledStatement._() : super._();
|
|
|
|
@override
|
|
String toString() => 'labeled: $_body';
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
|
|
_body._preVisit(assignedVariables);
|
|
}
|
|
|
|
@override
|
|
void _visit(Harness h) {
|
|
h._typeAnalyzer.analyzeLabeledStatement(this, _body);
|
|
}
|
|
}
|
|
|
|
/// Representation of an expression that can appear on the left hand side of an
|
|
/// assignment (or as the target of `++` or `--`). Methods in this class may be
|
|
/// used to create more complex expressions based on this one.
|
|
abstract class LValue extends Expression {
|
|
LValue._();
|
|
|
|
/// Creates an expression representing a write to this L-value.
|
|
Expression write(Expression? value) => new _Write(this, value);
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables,
|
|
{_LValueDisposition disposition});
|
|
|
|
void _visitWrite(Harness h, Expression assignmentExpression, Type writtenType,
|
|
Expression? rhs);
|
|
}
|
|
|
|
/// 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 {
|
|
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);
|
|
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables);
|
|
|
|
void _visit(Harness h);
|
|
}
|
|
|
|
/// Representation of a single case clause in a switch statement. Use [case_]
|
|
/// to create instances of this class.
|
|
class SwitchCase {
|
|
final bool _hasLabel;
|
|
final _Block _body;
|
|
|
|
SwitchCase._(this._hasLabel, this._body);
|
|
|
|
String toString() => [
|
|
if (_hasLabel) '<label>:',
|
|
'case <value>:',
|
|
..._body.statements
|
|
].join(' ');
|
|
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
|
|
_body._preVisit(assignedVariables);
|
|
}
|
|
}
|
|
|
|
abstract class TryBuilder {
|
|
TryStatement catch_(
|
|
{Var? exception, Var? stackTrace, required List<Statement> body});
|
|
|
|
Statement finally_(List<Statement> statements);
|
|
}
|
|
|
|
abstract class TryStatement extends Statement implements TryBuilder {
|
|
TryStatement._() : super._();
|
|
}
|
|
|
|
/// 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 L-value representing a reference to this variable.
|
|
LValue get expr => new _VariableReference(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 _VariableReference(this, callback);
|
|
|
|
@override
|
|
String toString() => '$type $name';
|
|
|
|
/// Creates an expression representing a write to this variable.
|
|
Expression write(Expression? value) => expr.write(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, Type context) {
|
|
return h._typeAnalyzer.analyzeTypeCast(this, target, 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) {
|
|
h._typeAnalyzer.analyzeAssertStatement(condition, message);
|
|
h._irBuilder.apply('assert', 2);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
for (var statement in statements) {
|
|
statement._preVisit(assignedVariables);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void _visit(Harness h) {
|
|
h._typeAnalyzer.analyzeBlock(statements);
|
|
h._irBuilder.apply('block', statements.length);
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
var type = h._typeAnalyzer.analyzeBoolLiteral(this, value);
|
|
h._irBuilder.atom('$value');
|
|
return type;
|
|
}
|
|
}
|
|
|
|
class _Break extends Statement {
|
|
final LabeledStatement? target;
|
|
|
|
_Break(this.target) : super._();
|
|
|
|
@override
|
|
String toString() => 'break;';
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
|
|
|
|
@override
|
|
void _visit(Harness h) {
|
|
h._typeAnalyzer.analyzeBreakStatement(target);
|
|
h._irBuilder.apply('break', 0);
|
|
}
|
|
}
|
|
|
|
/// Representation of a single catch clause in a try/catch statement. Use
|
|
/// [catch_] to create instances of this class.
|
|
class _CatchClause {
|
|
final 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 $_body';
|
|
}
|
|
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
|
|
_body._preVisit(assignedVariables);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
expect(h._flow.isAssigned(variable), expectedAssignedState);
|
|
h._irBuilder.atom('null');
|
|
}
|
|
}
|
|
|
|
class _CheckPromoted extends Statement {
|
|
final Var variable;
|
|
final String? expectedTypeStr;
|
|
final StackTrace _creationTrace = StackTrace.current;
|
|
|
|
_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) {
|
|
var promotedType = h._flow.promotedType(variable);
|
|
expect(promotedType?.type, expectedTypeStr, reason: '$_creationTrace');
|
|
h._irBuilder.atom('null');
|
|
}
|
|
}
|
|
|
|
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) {
|
|
expect(h._flow.isReachable, expectedReachable);
|
|
h._irBuilder.atom('null');
|
|
}
|
|
}
|
|
|
|
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) {
|
|
expect(h._flow.isUnassigned(variable), expectedUnassignedState);
|
|
h._irBuilder.atom('null');
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
var type = h._typeAnalyzer
|
|
.analyzeConditionalExpression(this, condition, ifTrue, ifFalse);
|
|
h._irBuilder.apply('if', 3);
|
|
return type;
|
|
}
|
|
}
|
|
|
|
class _Continue extends Statement {
|
|
_Continue() : super._();
|
|
|
|
@override
|
|
String toString() => 'continue;';
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
|
|
|
|
@override
|
|
void _visit(Harness h) {
|
|
h._typeAnalyzer.analyzeContinueStatement();
|
|
h._irBuilder.apply('continue', 0);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
h._irBuilder.atom(variable.name);
|
|
h._typeAnalyzer.analyzeVariableDeclaration(
|
|
this, variable.type, variable, initializer,
|
|
isFinal: isFinal, isLate: isLate);
|
|
h._irBuilder.apply(
|
|
['declare', if (isLate) 'late', if (isFinal) 'final'].join('_'), 2);
|
|
}
|
|
}
|
|
|
|
class _Do extends Statement {
|
|
final Statement body;
|
|
final Expression condition;
|
|
|
|
_Do(this.body, this.condition) : super._();
|
|
|
|
@override
|
|
String toString() => 'do $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) {
|
|
h._typeAnalyzer.analyzeDoLoop(this, body, condition);
|
|
h._irBuilder.apply('do', 2);
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
var operatorName = isInverted ? '!=' : '==';
|
|
var type =
|
|
h._typeAnalyzer.analyzeBinaryExpression(this, lhs, operatorName, rhs);
|
|
h._irBuilder.apply(operatorName, 2);
|
|
return type;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
h._typeAnalyzer.analyzeExpressionStatement(expr);
|
|
}
|
|
}
|
|
|
|
class _For extends Statement {
|
|
final Statement? initializer;
|
|
final Expression? condition;
|
|
final Expression? updater;
|
|
final 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(') $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) {
|
|
if (initializer != null) {
|
|
h._typeAnalyzer.dispatchStatement(initializer!);
|
|
} else {
|
|
h._typeAnalyzer.handleNoInitializer();
|
|
}
|
|
h._flow.for_conditionBegin(this);
|
|
if (condition != null) {
|
|
h._typeAnalyzer.analyzeExpression(condition!);
|
|
} else {
|
|
h._typeAnalyzer.handleNoCondition();
|
|
}
|
|
h._flow.for_bodyBegin(forCollection ? null : this, condition);
|
|
h._typeAnalyzer._visitLoopBody(this, body);
|
|
h._flow.for_updaterBegin();
|
|
if (updater != null) {
|
|
h._typeAnalyzer.analyzeExpression(updater!);
|
|
} else {
|
|
h._typeAnalyzer.handleNoStatement();
|
|
}
|
|
h._flow.for_end();
|
|
h._irBuilder.apply('for', 4);
|
|
}
|
|
}
|
|
|
|
class _ForEach extends Statement {
|
|
final Var? variable;
|
|
final Expression iterable;
|
|
final 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) $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) {
|
|
var iteratedType =
|
|
h._getIteratedType(h._typeAnalyzer.analyzeExpression(iterable));
|
|
h._flow.forEach_bodyBegin(this);
|
|
var variable = this.variable;
|
|
if (variable != null && !declaresVariable) {
|
|
h._flow.write(this, variable, iteratedType, null);
|
|
}
|
|
h._typeAnalyzer._visitLoopBody(this, body);
|
|
h._flow.forEach_end();
|
|
h._irBuilder.apply('forEach', 2);
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
var type = h._typeAnalyzer.analyzeExpression(target);
|
|
h._flow.forwardExpression(this, target);
|
|
callback(h._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) {
|
|
callback(SsaNodeHarness(h._flow));
|
|
h._irBuilder.atom('null');
|
|
}
|
|
}
|
|
|
|
class _If extends Statement {
|
|
final Expression condition;
|
|
final Statement ifTrue;
|
|
final Statement? ifFalse;
|
|
|
|
_If(this.condition, this.ifTrue, this.ifFalse) : super._();
|
|
|
|
@override
|
|
String toString() =>
|
|
'if ($condition) $ifTrue' + (ifFalse == null ? '' : 'else $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) {
|
|
h._typeAnalyzer.analyzeIfStatement(this, condition, ifTrue, ifFalse);
|
|
h._irBuilder.apply('if', 3);
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
var type = h._typeAnalyzer.analyzeIfNullExpression(this, lhs, rhs);
|
|
h._irBuilder.apply('ifNull', 2);
|
|
return type;
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
return h._typeAnalyzer
|
|
.analyzeTypeTest(this, target, type, isInverted: isInverted);
|
|
}
|
|
}
|
|
|
|
class _LocalFunction extends Statement {
|
|
final Statement body;
|
|
|
|
_LocalFunction(this.body) : super._();
|
|
|
|
@override
|
|
String toString() => '() $body';
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
|
|
assignedVariables.beginNode();
|
|
body._preVisit(assignedVariables);
|
|
assignedVariables.endNode(this, isClosureOrLateVariableInitializer: true);
|
|
}
|
|
|
|
@override
|
|
void _visit(Harness h) {
|
|
h._flow.functionExpression_begin(this);
|
|
h._typeAnalyzer.dispatchStatement(body);
|
|
h._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, Type context) {
|
|
var operatorName = isAnd ? '&&' : '||';
|
|
var type =
|
|
h._typeAnalyzer.analyzeBinaryExpression(this, lhs, operatorName, rhs);
|
|
h._irBuilder.apply(operatorName, 2);
|
|
return type;
|
|
}
|
|
}
|
|
|
|
/// Enum representing the different ways an [LValue] might be used.
|
|
enum _LValueDisposition {
|
|
/// The [LValue] is being read from only, not written to. This happens if it
|
|
/// appears in a place where an ordinary expression is expected.
|
|
read,
|
|
|
|
/// The [LValue] is being written to only, not read from. This happens if it
|
|
/// appears on the left hand side of `=`.
|
|
write,
|
|
|
|
/// The [LValue] is being both read from and written to. This happens if it
|
|
/// appears on the left and side of `op=` (where `op` is some operator), or as
|
|
/// the target of `++` or `--`.
|
|
readWrite,
|
|
}
|
|
|
|
class _MiniAstTypeAnalyzer {
|
|
final Harness _harness;
|
|
|
|
Statement? _currentBreakTarget;
|
|
|
|
Statement? _currentContinueTarget;
|
|
|
|
final _irBuilder = MiniIrBuilder();
|
|
|
|
late final Type boolType = Type('bool');
|
|
|
|
late final Type neverType = Type('Never');
|
|
|
|
late final Type nullType = Type('Null');
|
|
|
|
late final Type unknownType = Type('?');
|
|
|
|
_MiniAstTypeAnalyzer(this._harness);
|
|
|
|
FlowAnalysis<Node, Statement, Expression, Var, Type> get flow =>
|
|
_harness._flow;
|
|
|
|
Type get thisType => _harness.thisType!;
|
|
|
|
void analyzeAssertStatement(Expression condition, Expression? message) {
|
|
flow.assert_begin();
|
|
analyzeExpression(condition);
|
|
flow.assert_afterCondition(condition);
|
|
if (message != null) {
|
|
analyzeExpression(message);
|
|
} else {
|
|
handleNoMessage();
|
|
}
|
|
flow.assert_end();
|
|
}
|
|
|
|
Type analyzeBinaryExpression(
|
|
Expression node, Expression lhs, String operatorName, Expression rhs) {
|
|
bool isEquals = false;
|
|
bool isNot = false;
|
|
bool isLogical = false;
|
|
bool isAnd = false;
|
|
switch (operatorName) {
|
|
case '==':
|
|
isEquals = true;
|
|
break;
|
|
case '!=':
|
|
isEquals = true;
|
|
isNot = true;
|
|
operatorName = '==';
|
|
break;
|
|
case '&&':
|
|
isLogical = true;
|
|
isAnd = true;
|
|
break;
|
|
case '||':
|
|
isLogical = true;
|
|
break;
|
|
}
|
|
if (operatorName == '==') {
|
|
isEquals = true;
|
|
} else if (operatorName == '!=') {
|
|
isEquals = true;
|
|
isNot = true;
|
|
operatorName = '==';
|
|
}
|
|
if (isLogical) {
|
|
flow.logicalBinaryOp_begin();
|
|
}
|
|
var leftType = analyzeExpression(lhs);
|
|
if (isEquals) {
|
|
flow.equalityOp_rightBegin(lhs, leftType);
|
|
} else if (isLogical) {
|
|
flow.logicalBinaryOp_rightBegin(lhs, node, isAnd: isAnd);
|
|
}
|
|
var rightType = analyzeExpression(rhs);
|
|
if (isEquals) {
|
|
flow.equalityOp_end(node, rhs, rightType, notEqual: isNot);
|
|
} else if (isLogical) {
|
|
flow.logicalBinaryOp_end(node, rhs, isAnd: isAnd);
|
|
}
|
|
return boolType;
|
|
}
|
|
|
|
void analyzeBlock(Iterable<Statement> statements) {
|
|
for (var statement in statements) {
|
|
dispatchStatement(statement);
|
|
}
|
|
}
|
|
|
|
Type analyzeBoolLiteral(Expression node, bool value) {
|
|
flow.booleanLiteral(node, value);
|
|
return boolType;
|
|
}
|
|
|
|
void analyzeBreakStatement(Statement? target) {
|
|
flow.handleBreak(target ?? _currentBreakTarget!);
|
|
}
|
|
|
|
Type analyzeConditionalExpression(Expression node, Expression condition,
|
|
Expression ifTrue, Expression ifFalse) {
|
|
flow.conditional_conditionBegin();
|
|
analyzeExpression(condition);
|
|
flow.conditional_thenBegin(condition, node);
|
|
var ifTrueType = analyzeExpression(ifTrue);
|
|
flow.conditional_elseBegin(ifTrue);
|
|
var ifFalseType = analyzeExpression(ifFalse);
|
|
flow.conditional_end(node, ifFalse);
|
|
return leastUpperBound(ifTrueType, ifFalseType);
|
|
}
|
|
|
|
void analyzeContinueStatement() {
|
|
flow.handleContinue(_currentContinueTarget!);
|
|
}
|
|
|
|
void analyzeDoLoop(Statement node, Statement body, Expression condition) {
|
|
flow.doStatement_bodyBegin(node);
|
|
_visitLoopBody(node, body);
|
|
flow.doStatement_conditionBegin();
|
|
analyzeExpression(condition);
|
|
flow.doStatement_end(condition);
|
|
}
|
|
|
|
Type analyzeExpression(Expression expression, [Type? context]) {
|
|
// TODO(paulberry): make the [context] argument required.
|
|
context ??= unknownType;
|
|
return dispatchExpression(expression, context);
|
|
}
|
|
|
|
void analyzeExpressionStatement(Expression expression) {
|
|
analyzeExpression(expression);
|
|
}
|
|
|
|
Type analyzeIfNullExpression(
|
|
Expression node, Expression lhs, Expression rhs) {
|
|
var leftType = analyzeExpression(lhs);
|
|
flow.ifNullExpression_rightBegin(lhs, leftType);
|
|
var rightType = analyzeExpression(rhs);
|
|
flow.ifNullExpression_end();
|
|
return leastUpperBound(
|
|
flow.typeOperations.promoteToNonNull(leftType), rightType);
|
|
}
|
|
|
|
void analyzeIfStatement(Statement node, Expression condition,
|
|
Statement ifTrue, Statement? ifFalse) {
|
|
flow.ifStatement_conditionBegin();
|
|
analyzeExpression(condition);
|
|
flow.ifStatement_thenBegin(condition, node);
|
|
dispatchStatement(ifTrue);
|
|
if (ifFalse == null) {
|
|
handleNoStatement();
|
|
flow.ifStatement_end(false);
|
|
} else {
|
|
flow.ifStatement_elseBegin();
|
|
dispatchStatement(ifFalse);
|
|
flow.ifStatement_end(true);
|
|
}
|
|
}
|
|
|
|
void analyzeLabeledStatement(Statement node, Statement body) {
|
|
flow.labeledStatement_begin(node);
|
|
dispatchStatement(body);
|
|
flow.labeledStatement_end();
|
|
}
|
|
|
|
Type analyzeLogicalNot(Expression node, Expression expression) {
|
|
analyzeExpression(expression);
|
|
flow.logicalNot_end(node, expression);
|
|
return boolType;
|
|
}
|
|
|
|
Type analyzeNonNullAssert(Expression node, Expression expression) {
|
|
var type = analyzeExpression(expression);
|
|
flow.nonNullAssert_end(expression);
|
|
return flow.typeOperations.promoteToNonNull(type);
|
|
}
|
|
|
|
Type analyzeNullLiteral(Expression node) {
|
|
flow.nullLiteral(node);
|
|
return nullType;
|
|
}
|
|
|
|
Type analyzeParenthesizedExpression(Expression node, Expression expression) {
|
|
var type = analyzeExpression(expression);
|
|
flow.parenthesizedExpression(node, expression);
|
|
return type;
|
|
}
|
|
|
|
Type analyzePropertyGet(
|
|
Expression node, Expression receiver, String propertyName) {
|
|
var receiverType = analyzeExpression(receiver);
|
|
var type = _lookupMember(node, receiverType, propertyName);
|
|
flow.propertyGet(node, receiver, propertyName, propertyName, type);
|
|
return type;
|
|
}
|
|
|
|
void analyzeReturnStatement() {
|
|
flow.handleExit();
|
|
}
|
|
|
|
void analyzeSwitchStatement(
|
|
_Switch node, Expression expression, List<SwitchCase> cases) {
|
|
analyzeExpression(expression);
|
|
flow.switchStatement_expressionEnd(node);
|
|
var previousBreakTarget = _currentBreakTarget;
|
|
_currentBreakTarget = node;
|
|
for (var case_ in cases) {
|
|
flow.switchStatement_beginCase(case_._hasLabel, node);
|
|
dispatchStatement(case_._body);
|
|
}
|
|
_currentBreakTarget = previousBreakTarget;
|
|
flow.switchStatement_end(isSwitchExhaustive(node));
|
|
}
|
|
|
|
Type analyzeThis(Expression node) {
|
|
var thisType = this.thisType;
|
|
flow.thisOrSuper(node, thisType);
|
|
return thisType;
|
|
}
|
|
|
|
Type analyzeThisPropertyGet(Expression node, String propertyName) {
|
|
var type = _lookupMember(node, thisType, propertyName);
|
|
flow.thisOrSuperPropertyGet(node, propertyName, propertyName, type);
|
|
return type;
|
|
}
|
|
|
|
Type analyzeThrow(Expression node, Expression expression) {
|
|
analyzeExpression(expression);
|
|
flow.handleExit();
|
|
return neverType;
|
|
}
|
|
|
|
void analyzeTryStatement(Statement node, Statement body,
|
|
Iterable<_CatchClause> catchClauses, Statement? finallyBlock) {
|
|
if (finallyBlock != null) {
|
|
flow.tryFinallyStatement_bodyBegin();
|
|
}
|
|
if (catchClauses.isNotEmpty) {
|
|
flow.tryCatchStatement_bodyBegin();
|
|
}
|
|
dispatchStatement(body);
|
|
if (catchClauses.isNotEmpty) {
|
|
flow.tryCatchStatement_bodyEnd(body);
|
|
for (var catch_ in catchClauses) {
|
|
flow.tryCatchStatement_catchBegin(
|
|
catch_._exception, catch_._stackTrace);
|
|
dispatchStatement(catch_._body);
|
|
flow.tryCatchStatement_catchEnd();
|
|
}
|
|
flow.tryCatchStatement_end();
|
|
}
|
|
if (finallyBlock != null) {
|
|
flow.tryFinallyStatement_finallyBegin(
|
|
catchClauses.isNotEmpty ? node : body);
|
|
dispatchStatement(finallyBlock);
|
|
flow.tryFinallyStatement_end();
|
|
} else {
|
|
handleNoStatement();
|
|
}
|
|
}
|
|
|
|
Type analyzeTypeCast(Expression node, Expression expression, Type type) {
|
|
analyzeExpression(expression);
|
|
flow.asExpression_end(expression, type);
|
|
return type;
|
|
}
|
|
|
|
Type analyzeTypeTest(Expression node, Expression expression, Type type,
|
|
{bool isInverted = false}) {
|
|
analyzeExpression(expression);
|
|
flow.isExpression_end(node, expression, isInverted, type);
|
|
return boolType;
|
|
}
|
|
|
|
void analyzeVariableDeclaration(
|
|
Statement node, Type type, Var variable, Expression? initializer,
|
|
{required bool isFinal, required bool isLate}) {
|
|
if (initializer == null) {
|
|
handleNoInitializer();
|
|
flow.declare(variable, false);
|
|
} else {
|
|
var initializerType = analyzeExpression(initializer);
|
|
flow.declare(variable, true);
|
|
flow.initialize(variable, initializerType, initializer,
|
|
isFinal: isFinal, isLate: isLate);
|
|
}
|
|
}
|
|
|
|
Type analyzeVariableGet(
|
|
Expression node, Var variable, void Function(Type?)? callback) {
|
|
var promotedType = flow.variableRead(node, variable);
|
|
callback?.call(promotedType);
|
|
return promotedType ?? variable.type;
|
|
}
|
|
|
|
void analyzeWhileLoop(Statement node, Expression condition, Statement body) {
|
|
flow.whileStatement_conditionBegin(node);
|
|
analyzeExpression(condition);
|
|
flow.whileStatement_bodyBegin(node, condition);
|
|
_visitLoopBody(node, body);
|
|
flow.whileStatement_end();
|
|
}
|
|
|
|
Type dispatchExpression(Expression expression, Type context) =>
|
|
_irBuilder.guard(expression, () => expression._visit(_harness, context));
|
|
|
|
void dispatchStatement(Statement statement) =>
|
|
_irBuilder.guard(statement, () => statement._visit(_harness));
|
|
|
|
void finish() {
|
|
flow.finish();
|
|
}
|
|
|
|
void handleNoCondition() {
|
|
_irBuilder.atom('true');
|
|
}
|
|
|
|
void handleNoInitializer() {
|
|
_irBuilder.atom('uninitialized');
|
|
}
|
|
|
|
void handleNoMessage() {
|
|
_irBuilder.atom('failure');
|
|
}
|
|
|
|
void handleNoStatement() {
|
|
_irBuilder.atom('noop');
|
|
}
|
|
|
|
bool isSwitchExhaustive(_Switch node) {
|
|
return node.isExhaustive;
|
|
}
|
|
|
|
Type leastUpperBound(Type t1, Type t2) => _harness._lub(t1, t2);
|
|
|
|
Type lookupInterfaceMember(Node node, Type receiverType, String memberName) {
|
|
return _harness.getMember(receiverType, memberName);
|
|
}
|
|
|
|
Type _lookupMember(Expression node, Type receiverType, String memberName) {
|
|
return lookupInterfaceMember(node, receiverType, memberName);
|
|
}
|
|
|
|
void _visitLoopBody(Statement loop, Statement body) {
|
|
var previousBreakTarget = _currentBreakTarget;
|
|
var previousContinueTarget = _currentContinueTarget;
|
|
_currentBreakTarget = loop;
|
|
_currentContinueTarget = loop;
|
|
dispatchStatement(body);
|
|
_currentBreakTarget = previousBreakTarget;
|
|
_currentContinueTarget = previousContinueTarget;
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
return h._typeAnalyzer.analyzeNonNullAssert(this, operand);
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
return h._typeAnalyzer.analyzeLogicalNot(this, operand);
|
|
}
|
|
}
|
|
|
|
class _NullAwareAccess extends Expression {
|
|
static String _fakeMethodName = 'm';
|
|
|
|
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, Type context) {
|
|
var lhsType = h._typeAnalyzer.analyzeExpression(lhs);
|
|
h._flow.nullAwareAccess_rightBegin(isCascaded ? null : lhs, lhsType);
|
|
var rhsType = h._typeAnalyzer.analyzeExpression(rhs);
|
|
h._flow.nullAwareAccess_end();
|
|
var type = h._lub(rhsType, Type('Null'));
|
|
h._irBuilder.apply(_fakeMethodName, 2);
|
|
return type;
|
|
}
|
|
}
|
|
|
|
class _NullLiteral extends Expression {
|
|
_NullLiteral();
|
|
|
|
@override
|
|
String toString() => 'null';
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
|
|
|
|
@override
|
|
Type _visit(Harness h, Type context) {
|
|
var type = h._typeAnalyzer.analyzeNullLiteral(this);
|
|
h._irBuilder.atom('null');
|
|
return type;
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
return h._typeAnalyzer.analyzeParenthesizedExpression(this, expr);
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
h._irBuilder.atom(type.type);
|
|
h._irBuilder.apply('expr', 1);
|
|
return type;
|
|
}
|
|
}
|
|
|
|
class _Property extends LValue {
|
|
final Expression target;
|
|
|
|
final String propertyName;
|
|
|
|
_Property(this.target, this.propertyName) : super._();
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables,
|
|
{_LValueDisposition disposition = _LValueDisposition.read}) {
|
|
target._preVisit(assignedVariables);
|
|
}
|
|
|
|
@override
|
|
Type _visit(Harness h, Type context) {
|
|
return h._typeAnalyzer.analyzePropertyGet(this, target, propertyName);
|
|
}
|
|
|
|
@override
|
|
void _visitWrite(Harness h, Expression assignmentExpression, Type writtenType,
|
|
Expression? rhs) {
|
|
// No flow analysis impact
|
|
}
|
|
}
|
|
|
|
class _Return extends Statement {
|
|
_Return() : super._();
|
|
|
|
@override
|
|
String toString() => 'return;';
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
|
|
|
|
@override
|
|
void _visit(Harness h) {
|
|
h._typeAnalyzer.analyzeReturnStatement();
|
|
h._irBuilder.apply('return', 0);
|
|
}
|
|
}
|
|
|
|
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();
|
|
for (var case_ in cases) {
|
|
case_._preVisit(assignedVariables);
|
|
}
|
|
assignedVariables.endNode(this);
|
|
}
|
|
|
|
@override
|
|
void _visit(Harness h) {
|
|
h._typeAnalyzer.analyzeSwitchStatement(this, expression, cases);
|
|
h._irBuilder.apply('switch', cases.length + 1);
|
|
}
|
|
}
|
|
|
|
class _This extends Expression {
|
|
@override
|
|
String toString() => 'this';
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
|
|
|
|
@override
|
|
Type _visit(Harness h, Type context) {
|
|
var type = h._typeAnalyzer.analyzeThis(this);
|
|
h._irBuilder.atom('this');
|
|
return type;
|
|
}
|
|
}
|
|
|
|
class _ThisOrSuperPropertyGet extends Expression {
|
|
final String propertyName;
|
|
|
|
_ThisOrSuperPropertyGet(this.propertyName);
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
|
|
|
|
@override
|
|
Type _visit(Harness h, Type context) {
|
|
var type = h._typeAnalyzer.analyzeThisPropertyGet(this, propertyName);
|
|
h._irBuilder.atom('this.$propertyName');
|
|
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, Type context) {
|
|
return h._typeAnalyzer.analyzeThrow(this, operand);
|
|
}
|
|
}
|
|
|
|
class _TryStatement extends TryStatement {
|
|
final Statement _body;
|
|
final List<_CatchClause> _catches;
|
|
final Statement? _finally;
|
|
|
|
_TryStatement(this._body, this._catches, this._finally) : super._();
|
|
|
|
@override
|
|
TryStatement catch_(
|
|
{Var? exception, Var? stackTrace, required List<Statement> body}) {
|
|
assert(_finally == null, 'catch after finally');
|
|
return _TryStatement(_body,
|
|
[..._catches, _CatchClause(block(body), exception, stackTrace)], null);
|
|
}
|
|
|
|
@override
|
|
Statement finally_(List<Statement> statements) {
|
|
assert(_finally == null, 'multiple finally clauses');
|
|
return _TryStatement(_body, _catches, block(statements));
|
|
}
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
|
|
if (_finally != null) {
|
|
assignedVariables.beginNode();
|
|
}
|
|
if (_catches.isNotEmpty) {
|
|
assignedVariables.beginNode();
|
|
}
|
|
_body._preVisit(assignedVariables);
|
|
assignedVariables.endNode(_body);
|
|
for (var catch_ in _catches) {
|
|
catch_._preVisit(assignedVariables);
|
|
}
|
|
if (_finally != null) {
|
|
if (_catches.isNotEmpty) {
|
|
assignedVariables.endNode(this);
|
|
}
|
|
_finally!._preVisit(assignedVariables);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void _visit(Harness h) {
|
|
h._typeAnalyzer.analyzeTryStatement(this, _body, _catches, _finally);
|
|
h._irBuilder.apply('try', 2 + _catches.length);
|
|
}
|
|
}
|
|
|
|
class _VariableReference extends LValue {
|
|
final Var variable;
|
|
|
|
final void Function(Type?)? callback;
|
|
|
|
_VariableReference(this.variable, this.callback) : super._();
|
|
|
|
@override
|
|
String toString() => variable.name;
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables,
|
|
{_LValueDisposition disposition = _LValueDisposition.read}) {
|
|
if (disposition != _LValueDisposition.write) {
|
|
assignedVariables.read(variable);
|
|
}
|
|
if (disposition != _LValueDisposition.read) {
|
|
assignedVariables.write(variable);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Type _visit(Harness h, Type context) {
|
|
var type = h._typeAnalyzer.analyzeVariableGet(this, variable, callback);
|
|
h._irBuilder.atom(variable.name);
|
|
return type;
|
|
}
|
|
|
|
@override
|
|
void _visitWrite(Harness h, Expression assignmentExpression, Type writtenType,
|
|
Expression? rhs) {
|
|
h._flow.write(assignmentExpression, variable, writtenType, rhs);
|
|
}
|
|
}
|
|
|
|
class _While extends Statement {
|
|
final Expression condition;
|
|
final Statement body;
|
|
|
|
_While(this.condition, this.body) : super._();
|
|
|
|
@override
|
|
String toString() => 'while ($condition) $body';
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
|
|
assignedVariables.beginNode();
|
|
condition._preVisit(assignedVariables);
|
|
body._preVisit(assignedVariables);
|
|
assignedVariables.endNode(this);
|
|
}
|
|
|
|
@override
|
|
void _visit(Harness h) {
|
|
h._typeAnalyzer.analyzeWhileLoop(this, condition, body);
|
|
h._irBuilder.apply('while', 2);
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
var type = h._typeAnalyzer.analyzeExpression(target);
|
|
h._flow.forwardExpression(this, target);
|
|
Type.withComparisonsAllowed(() {
|
|
callback(h._flow.whyNotPromoted(this)());
|
|
});
|
|
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) {
|
|
Type.withComparisonsAllowed(() {
|
|
callback(h._flow.whyNotPromotedImplicitThis(staticType)());
|
|
});
|
|
h._irBuilder.atom('noop');
|
|
}
|
|
}
|
|
|
|
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, Type context) {
|
|
late MiniIrTmp beforeTmp;
|
|
if (before != null) {
|
|
h._typeAnalyzer.dispatchStatement(before!);
|
|
beforeTmp = h._irBuilder.allocateTmp();
|
|
}
|
|
var type = h._typeAnalyzer.analyzeExpression(expr);
|
|
if (after != null) {
|
|
var exprTmp = h._irBuilder.allocateTmp();
|
|
h._typeAnalyzer.dispatchStatement(after!);
|
|
var afterTmp = h._irBuilder.allocateTmp();
|
|
h._irBuilder.readTmp(exprTmp);
|
|
h._irBuilder.let(afterTmp);
|
|
h._irBuilder.let(exprTmp);
|
|
}
|
|
h._flow.forwardExpression(this, expr);
|
|
if (before != null) {
|
|
h._irBuilder.let(beforeTmp);
|
|
}
|
|
return type;
|
|
}
|
|
}
|
|
|
|
class _Write extends Expression {
|
|
final LValue lhs;
|
|
final Expression? rhs;
|
|
|
|
_Write(this.lhs, this.rhs);
|
|
|
|
@override
|
|
String toString() => '$lhs = $rhs';
|
|
|
|
@override
|
|
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
|
|
lhs._preVisit(assignedVariables,
|
|
disposition: rhs == null
|
|
? _LValueDisposition.readWrite
|
|
: _LValueDisposition.write);
|
|
rhs?._preVisit(assignedVariables);
|
|
}
|
|
|
|
@override
|
|
Type _visit(Harness h, Type context) {
|
|
var rhs = this.rhs;
|
|
Type type;
|
|
if (rhs == null) {
|
|
// We are simulating an increment/decrement operation.
|
|
// TODO(paulberry): Make a separate node type for this.
|
|
type = h._typeAnalyzer.analyzeExpression(lhs);
|
|
} else {
|
|
type = h._typeAnalyzer.analyzeExpression(rhs);
|
|
}
|
|
lhs._visitWrite(h, this, type, rhs);
|
|
return type;
|
|
}
|
|
}
|