Version 2.13.0-231.0.dev
Merge commit '4d065168321b60005fc1c2cc63fd45268e21649e' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart
index 6ec266d..b8ce9e9 100644
--- a/pkg/_fe_analyzer_shared/test/mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart
@@ -9,6 +9,7 @@
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();
@@ -247,7 +248,7 @@
void _preVisit(AssignedVariables<Node, Var> assignedVariables);
- Type _visit(Harness h);
+ Type _visit(Harness h, Type context);
}
/// Test harness for creating flow analysis tests. This class implements all
@@ -390,6 +391,8 @@
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) {
@@ -679,7 +682,7 @@
}
@override
- Type _visit(Harness h) {
+ Type _visit(Harness h, Type context) {
return h._typeAnalyzer.analyzeTypeCast(this, target, type);
}
}
@@ -703,6 +706,7 @@
@override
void _visit(Harness h) {
h._typeAnalyzer.analyzeAssertStatement(condition, message);
+ h._irBuilder.apply('assert', 2);
}
}
@@ -725,6 +729,7 @@
@override
void _visit(Harness h) {
h._typeAnalyzer.analyzeBlock(statements);
+ h._irBuilder.apply('block', statements.length);
}
}
@@ -740,8 +745,10 @@
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
- Type _visit(Harness h) {
- return h._typeAnalyzer.analyzeBoolLiteral(this, value);
+ Type _visit(Harness h, Type context) {
+ var type = h._typeAnalyzer.analyzeBoolLiteral(this, value);
+ h._irBuilder.atom('$value');
+ return type;
}
}
@@ -759,6 +766,7 @@
@override
void _visit(Harness h) {
h._typeAnalyzer.analyzeBreakStatement(target);
+ h._irBuilder.apply('break', 0);
}
}
@@ -806,6 +814,7 @@
@override
void _visit(Harness h) {
expect(h._flow.isAssigned(variable), expectedAssignedState);
+ h._irBuilder.atom('null');
}
}
@@ -831,6 +840,7 @@
void _visit(Harness h) {
var promotedType = h._flow.promotedType(variable);
expect(promotedType?.type, expectedTypeStr, reason: '$_creationTrace');
+ h._irBuilder.atom('null');
}
}
@@ -848,6 +858,7 @@
@override
void _visit(Harness h) {
expect(h._flow.isReachable, expectedReachable);
+ h._irBuilder.atom('null');
}
}
@@ -869,6 +880,7 @@
@override
void _visit(Harness h) {
expect(h._flow.isUnassigned(variable), expectedUnassignedState);
+ h._irBuilder.atom('null');
}
}
@@ -892,9 +904,11 @@
}
@override
- Type _visit(Harness h) {
- return h._typeAnalyzer
+ Type _visit(Harness h, Type context) {
+ var type = h._typeAnalyzer
.analyzeConditionalExpression(this, condition, ifTrue, ifFalse);
+ h._irBuilder.apply('if', 3);
+ return type;
}
}
@@ -910,6 +924,7 @@
@override
void _visit(Harness h) {
h._typeAnalyzer.analyzeContinueStatement();
+ h._irBuilder.apply('continue', 0);
}
}
@@ -937,9 +952,12 @@
@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);
}
}
@@ -963,6 +981,7 @@
@override
void _visit(Harness h) {
h._typeAnalyzer.analyzeDoLoop(this, body, condition);
+ h._irBuilder.apply('do', 2);
}
}
@@ -983,10 +1002,12 @@
}
@override
- Type _visit(Harness h) {
+ Type _visit(Harness h, Type context) {
var operatorName = isInverted ? '!=' : '==';
- return h._typeAnalyzer
- .analyzeBinaryExpression(this, lhs, operatorName, rhs);
+ var type =
+ h._typeAnalyzer.analyzeBinaryExpression(this, lhs, operatorName, rhs);
+ h._irBuilder.apply(operatorName, 2);
+ return type;
}
}
@@ -1072,6 +1093,7 @@
h._typeAnalyzer.handleNoStatement();
}
h._flow.for_end();
+ h._irBuilder.apply('for', 4);
}
}
@@ -1123,6 +1145,7 @@
}
h._typeAnalyzer._visitLoopBody(this, body);
h._flow.forEach_end();
+ h._irBuilder.apply('forEach', 2);
}
}
@@ -1139,7 +1162,7 @@
}
@override
- Type _visit(Harness h) {
+ Type _visit(Harness h, Type context) {
var type = h._typeAnalyzer.analyzeExpression(target);
h._flow.forwardExpression(this, target);
callback(h._flow.expressionInfoForTesting(this));
@@ -1158,6 +1181,7 @@
@override
void _visit(Harness h) {
callback(SsaNodeHarness(h._flow));
+ h._irBuilder.atom('null');
}
}
@@ -1184,6 +1208,7 @@
@override
void _visit(Harness h) {
h._typeAnalyzer.analyzeIfStatement(this, condition, ifTrue, ifFalse);
+ h._irBuilder.apply('if', 3);
}
}
@@ -1203,8 +1228,10 @@
}
@override
- Type _visit(Harness h) {
- return h._typeAnalyzer.analyzeIfNullExpression(this, lhs, rhs);
+ Type _visit(Harness h, Type context) {
+ var type = h._typeAnalyzer.analyzeIfNullExpression(this, lhs, rhs);
+ h._irBuilder.apply('ifNull', 2);
+ return type;
}
}
@@ -1224,7 +1251,7 @@
}
@override
- Type _visit(Harness h) {
+ Type _visit(Harness h, Type context) {
return h._typeAnalyzer
.analyzeTypeTest(this, target, type, isInverted: isInverted);
}
@@ -1272,10 +1299,12 @@
}
@override
- Type _visit(Harness h) {
+ Type _visit(Harness h, Type context) {
var operatorName = isAnd ? '&&' : '||';
- return h._typeAnalyzer
- .analyzeBinaryExpression(this, lhs, operatorName, rhs);
+ var type =
+ h._typeAnalyzer.analyzeBinaryExpression(this, lhs, operatorName, rhs);
+ h._irBuilder.apply(operatorName, 2);
+ return type;
}
}
@@ -1302,12 +1331,16 @@
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 =>
@@ -1414,8 +1447,10 @@
flow.doStatement_end(condition);
}
- Type analyzeExpression(Expression expression) {
- return dispatchExpression(expression);
+ Type analyzeExpression(Expression expression, [Type? context]) {
+ // TODO(paulberry): make the [context] argument required.
+ context ??= unknownType;
+ return dispatchExpression(expression, context);
}
void analyzeExpressionStatement(Expression expression) {
@@ -1592,21 +1627,31 @@
flow.whileStatement_end();
}
- Type dispatchExpression(Expression expression) => expression._visit(_harness);
+ Type dispatchExpression(Expression expression, Type context) =>
+ _irBuilder.guard(expression, () => expression._visit(_harness, context));
- void dispatchStatement(Statement statement) => statement._visit(_harness);
+ void dispatchStatement(Statement statement) =>
+ _irBuilder.guard(statement, () => statement._visit(_harness));
void finish() {
flow.finish();
}
- void handleNoCondition() {}
+ void handleNoCondition() {
+ _irBuilder.atom('true');
+ }
- void handleNoInitializer() {}
+ void handleNoInitializer() {
+ _irBuilder.atom('uninitialized');
+ }
- void handleNoMessage() {}
+ void handleNoMessage() {
+ _irBuilder.atom('failure');
+ }
- void handleNoStatement() {}
+ void handleNoStatement() {
+ _irBuilder.atom('noop');
+ }
bool isSwitchExhaustive(_Switch node) {
return node.isExhaustive;
@@ -1647,7 +1692,7 @@
}
@override
- Type _visit(Harness h) {
+ Type _visit(Harness h, Type context) {
return h._typeAnalyzer.analyzeNonNullAssert(this, operand);
}
}
@@ -1666,12 +1711,14 @@
}
@override
- Type _visit(Harness h) {
+ 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;
@@ -1688,12 +1735,14 @@
}
@override
- Type _visit(Harness h) {
+ 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();
- return h._lub(rhsType, Type('Null'));
+ var type = h._lub(rhsType, Type('Null'));
+ h._irBuilder.apply(_fakeMethodName, 2);
+ return type;
}
}
@@ -1707,8 +1756,10 @@
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
- Type _visit(Harness h) {
- return h._typeAnalyzer.analyzeNullLiteral(this);
+ Type _visit(Harness h, Type context) {
+ var type = h._typeAnalyzer.analyzeNullLiteral(this);
+ h._irBuilder.atom('null');
+ return type;
}
}
@@ -1726,7 +1777,7 @@
}
@override
- Type _visit(Harness h) {
+ Type _visit(Harness h, Type context) {
return h._typeAnalyzer.analyzeParenthesizedExpression(this, expr);
}
}
@@ -1743,7 +1794,11 @@
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
- Type _visit(Harness h) => type;
+ Type _visit(Harness h, Type context) {
+ h._irBuilder.atom(type.type);
+ h._irBuilder.apply('expr', 1);
+ return type;
+ }
}
class _Property extends LValue {
@@ -1760,7 +1815,7 @@
}
@override
- Type _visit(Harness h) {
+ Type _visit(Harness h, Type context) {
return h._typeAnalyzer.analyzePropertyGet(this, target, propertyName);
}
@@ -1783,6 +1838,7 @@
@override
void _visit(Harness h) {
h._typeAnalyzer.analyzeReturnStatement();
+ h._irBuilder.apply('return', 0);
}
}
@@ -1819,6 +1875,7 @@
@override
void _visit(Harness h) {
h._typeAnalyzer.analyzeSwitchStatement(this, expression, cases);
+ h._irBuilder.apply('switch', cases.length + 1);
}
}
@@ -1830,8 +1887,10 @@
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
- Type _visit(Harness h) {
- return h._typeAnalyzer.analyzeThis(this);
+ Type _visit(Harness h, Type context) {
+ var type = h._typeAnalyzer.analyzeThis(this);
+ h._irBuilder.atom('this');
+ return type;
}
}
@@ -1844,8 +1903,10 @@
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
- Type _visit(Harness h) {
- return h._typeAnalyzer.analyzeThisPropertyGet(this, propertyName);
+ Type _visit(Harness h, Type context) {
+ var type = h._typeAnalyzer.analyzeThisPropertyGet(this, propertyName);
+ h._irBuilder.atom('this.$propertyName');
+ return type;
}
}
@@ -1863,7 +1924,7 @@
}
@override
- Type _visit(Harness h) {
+ Type _visit(Harness h, Type context) {
return h._typeAnalyzer.analyzeThrow(this, operand);
}
}
@@ -1913,6 +1974,7 @@
@override
void _visit(Harness h) {
h._typeAnalyzer.analyzeTryStatement(this, _body, _catches, _finally);
+ h._irBuilder.apply('try', 2 + _catches.length);
}
}
@@ -1938,8 +2000,10 @@
}
@override
- Type _visit(Harness h) {
- return h._typeAnalyzer.analyzeVariableGet(this, variable, callback);
+ Type _visit(Harness h, Type context) {
+ var type = h._typeAnalyzer.analyzeVariableGet(this, variable, callback);
+ h._irBuilder.atom(variable.name);
+ return type;
}
@override
@@ -1969,6 +2033,7 @@
@override
void _visit(Harness h) {
h._typeAnalyzer.analyzeWhileLoop(this, condition, body);
+ h._irBuilder.apply('while', 2);
}
}
@@ -1988,7 +2053,7 @@
}
@override
- Type _visit(Harness h) {
+ Type _visit(Harness h, Type context) {
var type = h._typeAnalyzer.analyzeExpression(target);
h._flow.forwardExpression(this, target);
Type.withComparisonsAllowed(() {
@@ -2016,6 +2081,7 @@
Type.withComparisonsAllowed(() {
callback(h._flow.whyNotPromotedImplicitThis(staticType)());
});
+ h._irBuilder.atom('noop');
}
}
@@ -2048,15 +2114,25 @@
}
@override
- Type _visit(Harness h) {
+ 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;
}
}
@@ -2080,7 +2156,7 @@
}
@override
- Type _visit(Harness h) {
+ Type _visit(Harness h, Type context) {
var rhs = this.rhs;
Type type;
if (rhs == null) {
diff --git a/pkg/_fe_analyzer_shared/test/mini_ir.dart b/pkg/_fe_analyzer_shared/test/mini_ir.dart
new file mode 100644
index 0000000..b3f0dfa
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/mini_ir.dart
@@ -0,0 +1,247 @@
+// Copyright (c) 2021, 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 a miniature string-based internal representation ("IR")
+// of Dart code suitable for use in unit testing.
+
+import 'package:test/test.dart';
+
+import 'mini_ast.dart';
+
+/// Stack-based builder class allowing construction of a miniature string-based
+/// internal representation ("IR") of Dart code suitable for use in unit
+/// testing.
+class MiniIrBuilder {
+ /// Set this to `true` to enable print-based tracing of stack operations.
+ static const bool _debug = false;
+
+ /// If [_debug] is enabled, number of outstanding calls to [guard]. This
+ /// controls indentation of debug output.
+ int _guardDepth = 0;
+
+ /// Number of labels allocated so far.
+ int _labelCounter = 0;
+
+ /// Size threshold for [_stack]. [_pop] and [_popList] will cause a test
+ /// failure if an attempt is made to reduce the stack size to less than this
+ /// amount. See [guard].
+ int _popLimit = 0;
+
+ /// Stack of partially built IR nodes.
+ final _stack = <String>[];
+
+ /// Number of temporaries allocated so far.
+ int _tmpCounter = 0;
+
+ /// Creates a fresh [MiniIrLabel] representing a label that can be used as a
+ /// break target.
+ ///
+ /// See [labeled].
+ MiniIrLabel allocateLabel() => MiniIrLabel._();
+
+ /// Pops the top node from the stack (which should represent an expression)
+ /// and creates a fresh [MiniIrTmp] representing a temporary variable whose
+ /// initializer is that expression.
+ ///
+ /// See [let].
+ MiniIrTmp allocateTmp() {
+ return MiniIrTmp._('t${_tmpCounter++}', _pop());
+ }
+
+ /// Pops the top [numArgs] nodes from the stack and pushes a node that
+ /// combines them using [name]. For example, if the stack contains `1, 2, 3`,
+ /// calling `apply('f', 2)` results in a stack of `1, f(2, 3)`.
+ void apply(String name, int numArgs) =>
+ _push('$name(${_popList(numArgs).join(', ')})');
+
+ /// Pushes a node on the stack representing a single atomic expression (for
+ /// example a literal value or a variable reference).
+ void atom(String name) => _push(name);
+
+ /// Verifies that the top node on the stack matches [expectedIr] exactly.
+ void check(String expectedIr) {
+ expect(_stack.last.toString(), expectedIr);
+ }
+
+ /// Pushes a node representing a `for-in` loop onto the stack, using a loop
+ /// variable, iterable expression, and loop body obtained from the stack.
+ ///
+ /// If [tmp] is non-null, it is used as the loop variable instead of obtaining
+ /// it from the stack.
+ void forIn(MiniIrTmp? tmp, {required bool isAsynchronous}) {
+ var name = isAsynchronous ? 'forIn_async' : 'forIn';
+ var body = _pop();
+ var iterable = _pop();
+ var variable = tmp == null ? _pop() : tmp._name;
+ _push('$name($variable, $iterable, $body)');
+ }
+
+ /// Executes [callback], checking that it leaves all nodes presently on the
+ /// stack untouched, and results in exactly one node being added to the stack.
+ T guard<T>(Node node, T Function() callback) {
+ if (_debug) {
+ print(' ' * _guardDepth++ + '$node');
+ }
+ int previousStackDepth = _stack.length;
+ int previousPopLimit = _popLimit;
+ _popLimit = previousStackDepth;
+ var result = callback();
+ var stackDelta = _stack.length - previousStackDepth;
+ if (stackDelta != 1) {
+ fail('Stack delta of $stackDelta while visiting '
+ '${node.runtimeType} $node\n'
+ 'Stack: $this');
+ }
+ if (_debug) {
+ print(' ' * --_guardDepth + '=> ${_stack.last}');
+ }
+ _popLimit = previousPopLimit;
+ return result;
+ }
+
+ /// Pushes a node representing an "if not null" check onto the stack, using
+ /// [tmp] and an expression obtained from the stack. The intended semantics
+ /// are `tmp == null ? null : <expression>`.
+ ///
+ /// This is intended to be used as a building block for null shorting
+ /// operations.
+ void ifNotNull(MiniIrTmp tmp) {
+ _push('if(==(${tmp._name}, null), null, ${_pop()})');
+ let(tmp);
+ }
+
+ /// Pushes a node representing an "if null" check onto the stack, using [tmp]
+ /// and two expressions obtained from the stack. The intended semantics
+ /// are `tmp == null ? <expression 1> : <expression 2>`.
+ ///
+ /// This is intended to be used as a building block for null `??` and `??=`
+ /// operations.
+ void ifNull(MiniIrTmp tmp) {
+ var ifNull = _pop();
+ var ifNotNull = _pop();
+ _push('if(==(${tmp._name}, null), $ifNull, $ifNotNull)');
+ let(tmp);
+ }
+
+ /// Pushes a node representing a call to `operator[]` onto the stack, using
+ /// a receiver and an index obtained from the stack.
+ void indexGet() => apply('[]', 2);
+
+ /// Pushes a node representing a call to `operator[]=` onto the stack, using
+ /// a receiver, index, and value obtained from the stack.
+ ///
+ /// If [receiverTmp] and/or [indexTmp] is non-null, they are used instead of
+ /// obtaining values from the stack.
+ void indexSet(MiniIrTmp? receiverTmp, MiniIrTmp? indexTmp) {
+ var value = _pop();
+ var index = indexTmp == null ? _pop() : indexTmp._name;
+ var receiver = receiverTmp == null ? _pop() : receiverTmp._name;
+ _push('[]=($receiver, $index, $value)');
+ }
+
+ /// Pushes a node representing a labeled statement onto the stack, using an
+ /// inner statement obtained from the stack.
+ ///
+ /// To build up a statement of the form `labeled(L0, stmt)` (where `stmt`
+ /// might refer to `L0`), do the following operations:
+ /// - Call [allocateLabel] to prepare the label.
+ /// - build `stmt` on the stack, using [referToLabel] to refer to label as
+ /// needed.
+ /// - Call [labeled] to build the final `labeled` statement.
+ void labeled(MiniIrLabel label) {
+ var name = label._name;
+ if (name != null) {
+ _push('labeled($name, ${_pop()})');
+ }
+ }
+
+ /// Pushes a node representing a `let` expression onto the stack, using a
+ /// value obtained from the stack.
+ ///
+ /// To build up an expression of the form `let(#0, value, expr)` (meaning
+ /// "let temporary variable #0 take on value while evaluating expr"), do the
+ /// following operations:
+ /// - Build `value` on the stack.
+ /// - Call [allocateTmp] to pop `value` off the stack and obtain a
+ /// [MiniIrTmp] object. This will assign the temporary variable a name that
+ /// doesn't conflict with any other outstanding temporary variables.
+ /// - Build `expr` on the stack, using [readTmp] to refer to the temporary
+ /// variable as needed.
+ /// - Call [let] to build the final `let` expression.
+ void let(MiniIrTmp tmp) {
+ _push('let(${tmp._name}, ${tmp._value}, ${_pop()})');
+ }
+
+ /// Pushes a node representing a property get onto the stack, using a receiver
+ /// obtained from the stack.
+ void propertyGet(String propertyName) => apply('get_$propertyName', 1);
+
+ /// Pushes a node representing a property set onto the stack, using a receiver
+ /// and value obtained from the stack.
+ ///
+ /// If [receiverTmp] is non-null, it is used as the receiver rather than
+ /// obtaining it from the stack.
+ void propertySet(MiniIrTmp? receiverTmp, String propertyName) {
+ var value = _pop();
+ var receiver = receiverTmp == null ? _pop() : receiverTmp._name;
+ _push('set_$propertyName($receiver, $value)');
+ }
+
+ /// Pushes a node representing a read of [tmp] onto the stack.
+ void readTmp(MiniIrTmp tmp) => _push(tmp._name);
+
+ /// Pushes a node representing a reference to [label] onto the stack.
+ void referToLabel(MiniIrLabel label) {
+ _push(label._name ??= 'L${_labelCounter++}');
+ }
+
+ @override
+ String toString() => _stack.join(', ');
+
+ /// Pushes a node representing a read of a local variable onto the stack.
+ void variableGet(Var v) => atom(v.name);
+
+ /// Pushes a node representing a set of a local variable onto the stack, using
+ /// a value obtained from the stack.
+ void variableSet(Var v) => apply('${v.name}=', 1);
+
+ /// Pops a single node off the stack.
+ String _pop() {
+ expect(_stack.length, greaterThan(_popLimit));
+ return _stack.removeLast();
+ }
+
+ /// Pops a list of nodes off the stack.
+ List<String> _popList(int count) {
+ var newLength = _stack.length - count;
+ expect(newLength, greaterThanOrEqualTo(_popLimit));
+ var result = _stack.sublist(newLength);
+ _stack.length = newLength;
+ return result;
+ }
+
+ /// Pushes a node onto the stack.
+ void _push(String node) => _stack.add(node);
+}
+
+/// Representation of a branch target label used by [MiniIrBuilder] when
+/// building up `labeled` statements.
+class MiniIrLabel {
+ /// The name of the label, or `null` if no name has been assigned yet.
+ String? _name;
+
+ MiniIrLabel._();
+}
+
+/// Representation of a temporary variable used by [MiniIrBuilder] when building
+/// up `let` expressions.
+class MiniIrTmp {
+ /// The name of the temporary variable.
+ final String _name;
+
+ /// The initial value of the temporary variable.
+ final String _value;
+
+ MiniIrTmp._(this._name, this._value);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 4ddc3aa..d3375eb 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 13
PATCH 0
-PRERELEASE 230
+PRERELEASE 231
PRERELEASE_PATCH 0
\ No newline at end of file