mirror of
https://github.com/dart-lang/sdk
synced 2024-11-05 18:22:09 +00:00
Shared type analysis: add support for when
clauses and fix label support.
Support for `when` clauses requires flow analysis integration, so that `when` clauses can promote variables, e.g.: f(int x, String? y) { switch (x) { case 0 when y != null: // y is known to be non-null here } } Support for labels in switch statements had a small flaw: we weren't reporting an error in the case where a label shared a case body with a pattern that tried to bind a variable, e.g.: f(int x) { switch (x) { L: // Error: does not mind the variable `y` case var y: ... } } Change-Id: I0b2bb4721a6b3a8f7898df682b24b75ddb6e44ae Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/256605 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
parent
ac86c134e9
commit
bd2d261bc6
6 changed files with 453 additions and 149 deletions
|
@ -530,6 +530,17 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
@visibleForTesting
|
||||
SsaNode<Type>? ssaNodeForTesting(Variable variable);
|
||||
|
||||
/// Call this method just after visiting a `when` part of a case clause. See
|
||||
/// [switchStatement_expressionEnd] for details.
|
||||
///
|
||||
/// [when] should be the expression following the `when` keyword.
|
||||
void switchStatement_afterWhen(Expression when);
|
||||
|
||||
/// Call this method just before visiting a sequence of two or more `case` or
|
||||
/// `default` clauses that share a body. See [switchStatement_expressionEnd]
|
||||
/// for details.`
|
||||
void switchStatement_beginAlternatives();
|
||||
|
||||
/// Call this method just before visiting one of the cases in the body of a
|
||||
/// switch statement. See [switchStatement_expressionEnd] for details.
|
||||
///
|
||||
|
@ -547,6 +558,16 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
/// were listed in cases.
|
||||
void switchStatement_end(bool isExhaustive);
|
||||
|
||||
/// Call this method just after visiting a `case` or `default` clause, if it
|
||||
/// shares a body with at least one other `case` or `default` clause. See
|
||||
/// [switchStatement_expressionEnd] for details.`
|
||||
void switchStatement_endAlternative();
|
||||
|
||||
/// Call this method just after visiting a sequence of two or more `case` or
|
||||
/// `default` clauses that share a body. See [switchStatement_expressionEnd]
|
||||
/// for details.`
|
||||
void switchStatement_endAlternatives();
|
||||
|
||||
/// Call this method just after visiting the expression part of a switch
|
||||
/// statement or expression. [switchStatement] should be the switch statement
|
||||
/// itself (or `null` if this is a switch expression).
|
||||
|
@ -554,9 +575,18 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
/// The order of visiting a switch statement should be:
|
||||
/// - Visit the switch expression.
|
||||
/// - Call [switchStatement_expressionEnd].
|
||||
/// - For each switch case (including the default case, if any):
|
||||
/// - For each case body:
|
||||
/// - Call [switchStatement_beginCase].
|
||||
/// - Visit the case.
|
||||
/// - If there is more than one `case` or `default` clause associated with
|
||||
/// this case body, call [switchStatement_beginAlternatives].
|
||||
/// - For each `case` or `default` clause associated with this case body:
|
||||
/// - If a `when` clause is present, visit it and then call
|
||||
/// [switchStatement_afterWhen].
|
||||
/// - If there is more than one `case` or `default` clause associated with
|
||||
/// this case body, call [switchStatement_endAlternative].
|
||||
/// - If there is more than one `case` or `default` clause associated with
|
||||
/// this case body, call [switchStatement_endAlternatives].
|
||||
/// - Visit the case body.
|
||||
/// - Call [switchStatement_end].
|
||||
void switchStatement_expressionEnd(Statement? switchStatement);
|
||||
|
||||
|
@ -1172,6 +1202,18 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
isQuery: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_afterWhen(Expression when) {
|
||||
_wrap('switchStatement_afterWhen($when)',
|
||||
() => _wrapped.switchStatement_afterWhen(when));
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_beginAlternatives() {
|
||||
_wrap('switchStatement_beginAlternatives()',
|
||||
() => _wrapped.switchStatement_beginAlternatives());
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_beginCase(bool hasLabel, Statement? node) {
|
||||
_wrap('switchStatement_beginCase($hasLabel, $node)',
|
||||
|
@ -1184,6 +1226,18 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
() => _wrapped.switchStatement_end(isExhaustive));
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_endAlternative() {
|
||||
_wrap('switchStatement_endAlternative()',
|
||||
() => _wrapped.switchStatement_endAlternative());
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_endAlternatives() {
|
||||
_wrap('switchStatement_endAlternatives()',
|
||||
() => _wrapped.switchStatement_endAlternatives());
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_expressionEnd(Statement? switchStatement) {
|
||||
_wrap('switchStatement_expressionEnd($switchStatement)',
|
||||
|
@ -3685,6 +3739,22 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
SsaNode<Type>? ssaNodeForTesting(Variable variable) => _current
|
||||
.variableInfo[promotionKeyStore.keyForVariable(variable)]?.ssaNode;
|
||||
|
||||
@override
|
||||
void switchStatement_afterWhen(Expression when) {
|
||||
ExpressionInfo<Type>? expressionInfo = _getExpressionInfo(when);
|
||||
if (expressionInfo != null) {
|
||||
_current = expressionInfo.ifTrue;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_beginAlternatives() {
|
||||
_current = _current.split();
|
||||
_SwitchAlternativesContext<Type> context =
|
||||
new _SwitchAlternativesContext<Type>(_current);
|
||||
_stack.add(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_beginCase(bool hasLabel, Statement? node) {
|
||||
_SimpleStatementContext<Type> context =
|
||||
|
@ -3714,6 +3784,21 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
_current = breakState.unsplit();
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_endAlternative() {
|
||||
_SwitchAlternativesContext<Type> context =
|
||||
_stack.last as _SwitchAlternativesContext<Type>;
|
||||
context._combinedModel = _join(context._combinedModel, _current);
|
||||
_current = context._previous;
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_endAlternatives() {
|
||||
_SwitchAlternativesContext<Type> context =
|
||||
_stack.removeLast() as _SwitchAlternativesContext<Type>;
|
||||
_current = context._combinedModel!.unsplit();
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_expressionEnd(Statement? switchStatement) {
|
||||
_current = _current.split();
|
||||
|
@ -4514,12 +4599,24 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
|
|||
throw new StateError('ssaNodeForTesting requires null-aware flow analysis');
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_afterWhen(Expression when) {}
|
||||
|
||||
@override
|
||||
void switchStatement_beginAlternatives() {}
|
||||
|
||||
@override
|
||||
void switchStatement_beginCase(bool hasLabel, Statement? node) {}
|
||||
|
||||
@override
|
||||
void switchStatement_end(bool isExhaustive) {}
|
||||
|
||||
@override
|
||||
void switchStatement_endAlternative() {}
|
||||
|
||||
@override
|
||||
void switchStatement_endAlternatives() {}
|
||||
|
||||
@override
|
||||
void switchStatement_expressionEnd(Statement? switchStatement) {}
|
||||
|
||||
|
@ -4767,6 +4864,14 @@ class _SimpleStatementContext<Type extends Object>
|
|||
'checkpoint: $_checkpoint)';
|
||||
}
|
||||
|
||||
class _SwitchAlternativesContext<Type extends Object> extends _FlowContext {
|
||||
final FlowModel<Type> _previous;
|
||||
|
||||
FlowModel<Type>? _combinedModel;
|
||||
|
||||
_SwitchAlternativesContext(this._previous);
|
||||
}
|
||||
|
||||
/// Specialization of [ExpressionInfo] for the case where the information we
|
||||
/// have about the expression is trivial (meaning we know by construction that
|
||||
/// the expression's [after], [ifTrue], and [ifFalse] models are all the same).
|
||||
|
|
|
@ -14,29 +14,36 @@ class ExpressionCaseInfo<Expression, Node> {
|
|||
/// For a `case` clause, the case pattern. For a `default` clause, `null`.
|
||||
final Node? pattern;
|
||||
|
||||
/// For a `case` clause that has a `when` part, the expression following
|
||||
/// `when`. Otherwise `null`.
|
||||
final Expression? when;
|
||||
|
||||
/// The body of the `case` or `default` clause.
|
||||
final Expression body;
|
||||
|
||||
ExpressionCaseInfo(this.pattern, this.body);
|
||||
ExpressionCaseInfo({required this.pattern, this.when, required this.body});
|
||||
}
|
||||
|
||||
/// Information supplied by the client to [TypeAnalyzer.analyzeSwitchStatement]
|
||||
/// about an individual `case` or `default` clause.
|
||||
///
|
||||
/// The client is free to `implement` or `extend` this class.
|
||||
class StatementCaseInfo<Statement, Node> {
|
||||
class StatementCaseInfo<Statement, Expression, Node> {
|
||||
/// The AST node for this `case` or `default` clause. This is used for error
|
||||
/// reporting, in case errors arise from mismatch among the variables bound by
|
||||
/// various cases that share a body.
|
||||
final Node node;
|
||||
|
||||
/// Indicates whether this `case` or `default` clause is preceded by one or
|
||||
/// more `goto` labels.
|
||||
final bool hasLabel;
|
||||
/// The labels preceding this `case` or `default` clause, if any.
|
||||
final List<Node> labels;
|
||||
|
||||
/// For a `case` clause, the case pattern. For a `default` clause, `null`.
|
||||
final Node? pattern;
|
||||
|
||||
/// For a `case` clause that has a `when` part, the expression following
|
||||
/// `when`. Otherwise `null`.
|
||||
final Expression? when;
|
||||
|
||||
/// The statements following this `case` or `default` clause. If this list is
|
||||
/// empty, and this is not the last `case` or `default` clause, this clause
|
||||
/// will be considered to share a body with the `case` or `default` clause
|
||||
|
@ -45,8 +52,9 @@ class StatementCaseInfo<Statement, Node> {
|
|||
|
||||
StatementCaseInfo(
|
||||
{required this.node,
|
||||
required this.hasLabel,
|
||||
this.labels = const [],
|
||||
required this.pattern,
|
||||
this.when,
|
||||
required this.body});
|
||||
}
|
||||
|
||||
|
@ -70,6 +78,9 @@ class StatementCaseInfo<Statement, Node> {
|
|||
mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||
Expression extends Node, Variable extends Object, Type extends Object>
|
||||
implements VariableBindingCallbacks<Node, Variable, Type> {
|
||||
/// Returns the type `bool`.
|
||||
Type get boolType;
|
||||
|
||||
/// Returns the type `double`.
|
||||
Type get doubleType;
|
||||
|
||||
|
@ -175,6 +186,8 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
|||
/// - [analyzeExpression]
|
||||
/// - For each `case` or `default` clause:
|
||||
/// - [dispatchPattern] if this is a `case` clause
|
||||
/// - [analyzeExpression] if this is a `case` clause with a `when` part
|
||||
/// - [handleCaseHead] if this is a `case` clause
|
||||
/// - [handleDefault] if this is a `default` clause
|
||||
/// - [handleCase_afterCaseHeads]
|
||||
/// - [analyzeExpression]
|
||||
|
@ -196,10 +209,17 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
|||
if (pattern != null) {
|
||||
dispatchPattern(pattern)
|
||||
.match(expressionType, bindings, isFinal: true, isLate: false);
|
||||
Expression? when = caseInfo.when;
|
||||
bool hasWhen = when != null;
|
||||
if (hasWhen) {
|
||||
analyzeExpression(when, boolType);
|
||||
flow?.switchStatement_afterWhen(when);
|
||||
}
|
||||
handleCaseHead(hasWhen: hasWhen);
|
||||
} else {
|
||||
handleDefault();
|
||||
}
|
||||
handleCase_afterCaseHeads(1);
|
||||
handleCase_afterCaseHeads(const [], 1);
|
||||
Type type = analyzeExpression(caseInfo.body, context);
|
||||
if (lubType == null) {
|
||||
lubType = type;
|
||||
|
@ -217,8 +237,11 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
|||
/// Invokes the following [TypeAnalyzer] methods (in order):
|
||||
/// - [dispatchExpression]
|
||||
/// - For each `case` or `default` body:
|
||||
/// - [dispatchPattern] for each `case` pattern associated with the body
|
||||
/// - [handleDefault] if a `default` clause is associated with the body
|
||||
/// - For each `case` or `default` clause associated with the body:
|
||||
/// - [dispatchPattern] if this is a `case` clause
|
||||
/// - [analyzeExpression] if this is a `case` clause with a `when` part
|
||||
/// - [handleCaseHead] if this is a `case` clause
|
||||
/// - [handleDefault] if this is a `default` clause
|
||||
/// - [handleCase_afterCaseHeads]
|
||||
/// - [dispatchStatement] for each statement in the body
|
||||
/// - [finishStatementCase]
|
||||
|
@ -228,42 +251,65 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
|||
/// length of [cases] because a case with no statements get merged into the
|
||||
/// case that follows).
|
||||
int analyzeSwitchStatement(Statement node, Expression scrutinee,
|
||||
List<StatementCaseInfo<Statement, Node>> cases) {
|
||||
List<StatementCaseInfo<Statement, Expression, Node>> cases) {
|
||||
Type expressionType = analyzeExpression(scrutinee, unknownType);
|
||||
flow?.switchStatement_expressionEnd(node);
|
||||
bool hasLabel = false;
|
||||
List<StatementCaseInfo<Statement, Node>>? casesInThisExecutionPath;
|
||||
List<Node> labels = [];
|
||||
List<StatementCaseInfo<Statement, Expression, Node>>?
|
||||
casesInThisExecutionPath;
|
||||
int numExecutionPaths = 0;
|
||||
for (int i = 0; i < cases.length; i++) {
|
||||
StatementCaseInfo<Statement, Node> caseInfo = cases[i];
|
||||
hasLabel = hasLabel || caseInfo.hasLabel;
|
||||
StatementCaseInfo<Statement, Expression, Node> caseInfo = cases[i];
|
||||
labels.addAll(caseInfo.labels);
|
||||
(casesInThisExecutionPath ??= []).add(caseInfo);
|
||||
if (i == cases.length - 1 || caseInfo.body.isNotEmpty) {
|
||||
numExecutionPaths++;
|
||||
flow?.switchStatement_beginCase(hasLabel, node);
|
||||
flow?.switchStatement_beginCase(labels.isNotEmpty, node);
|
||||
VariableBindings<Node, Variable, Type> bindings =
|
||||
new VariableBindings(this);
|
||||
bindings.startAlternatives();
|
||||
for (int i = 0; i < casesInThisExecutionPath.length; i++) {
|
||||
StatementCaseInfo<Statement, Node> caseInfo =
|
||||
// Labels count as empty patterns for the purposes of bindings.
|
||||
for (Node label in labels) {
|
||||
bindings.startAlternative(label);
|
||||
bindings.finishAlternative();
|
||||
}
|
||||
int numCasesInThisExecutionPath = casesInThisExecutionPath.length;
|
||||
if (numCasesInThisExecutionPath > 1) {
|
||||
flow?.switchStatement_beginAlternatives();
|
||||
}
|
||||
for (int i = 0; i < numCasesInThisExecutionPath; i++) {
|
||||
StatementCaseInfo<Statement, Expression, Node> caseInfo =
|
||||
casesInThisExecutionPath[i];
|
||||
bindings.startAlternative(caseInfo.node);
|
||||
Node? pattern = caseInfo.pattern;
|
||||
if (pattern != null) {
|
||||
dispatchPattern(pattern)
|
||||
.match(expressionType, bindings, isFinal: true, isLate: false);
|
||||
Expression? when = caseInfo.when;
|
||||
bool hasWhen = when != null;
|
||||
if (hasWhen) {
|
||||
analyzeExpression(when, boolType);
|
||||
flow?.switchStatement_afterWhen(when);
|
||||
}
|
||||
handleCaseHead(hasWhen: hasWhen);
|
||||
} else {
|
||||
handleDefault();
|
||||
}
|
||||
bindings.finishAlternative();
|
||||
if (numCasesInThisExecutionPath > 1) {
|
||||
flow?.switchStatement_endAlternative();
|
||||
}
|
||||
}
|
||||
bindings.finishAlternatives();
|
||||
handleCase_afterCaseHeads(casesInThisExecutionPath.length);
|
||||
if (numCasesInThisExecutionPath > 1) {
|
||||
flow?.switchStatement_endAlternatives();
|
||||
}
|
||||
handleCase_afterCaseHeads(labels, numCasesInThisExecutionPath);
|
||||
for (Statement statement in caseInfo.body) {
|
||||
dispatchStatement(statement);
|
||||
}
|
||||
finishStatementCase(node, i, caseInfo.body.length);
|
||||
hasLabel = false;
|
||||
labels.clear();
|
||||
casesInThisExecutionPath = null;
|
||||
}
|
||||
}
|
||||
|
@ -334,7 +380,10 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
|||
void finishStatementCase(Statement node, int caseIndex, int numStatements);
|
||||
|
||||
/// See [analyzeSwitchStatement] and [analyzeSwitchExpression].
|
||||
void handleCase_afterCaseHeads(int numHeads);
|
||||
void handleCase_afterCaseHeads(List<Node> labels, int numHeads);
|
||||
|
||||
/// See [analyzeSwitchStatement] and [analyzeSwitchExpression].
|
||||
void handleCaseHead({required bool hasWhen});
|
||||
|
||||
/// See [analyzeConstOrLiteralPattern].
|
||||
void handleConstOrLiteralPattern();
|
||||
|
|
|
@ -1628,10 +1628,11 @@ main() {
|
|||
|
||||
test('labeledBlock without break', () {
|
||||
var x = Var('x');
|
||||
var l = Label('l');
|
||||
h.run([
|
||||
declare(x, type: 'int?', initializer: expr('int?')),
|
||||
if_(x.expr.isNot('int'), [
|
||||
labeled((_) => return_()),
|
||||
l.thenStmt(return_()),
|
||||
]),
|
||||
checkPromoted(x, 'int'),
|
||||
]);
|
||||
|
@ -1639,15 +1640,16 @@ main() {
|
|||
|
||||
test('labeledBlock with break joins', () {
|
||||
var x = Var('x');
|
||||
var l = Label('l');
|
||||
h.run([
|
||||
declare(x, type: 'int?', initializer: expr('int?')),
|
||||
if_(x.expr.isNot('int'), [
|
||||
labeled((t) => block([
|
||||
if_(expr('bool'), [
|
||||
break_(t),
|
||||
]),
|
||||
return_(),
|
||||
])),
|
||||
l.thenStmt(block([
|
||||
if_(expr('bool'), [
|
||||
break_(l),
|
||||
]),
|
||||
return_(),
|
||||
])),
|
||||
]),
|
||||
checkNotPromoted(x),
|
||||
]);
|
||||
|
@ -1914,8 +1916,8 @@ main() {
|
|||
h.run([
|
||||
switchExpr(throw_(expr('C')), [
|
||||
caseExpr(intLiteral(0).pattern,
|
||||
checkReachable(false).thenExpr(intLiteral(1))),
|
||||
defaultExpr(checkReachable(false).thenExpr(intLiteral(2))),
|
||||
body: checkReachable(false).thenExpr(intLiteral(1))),
|
||||
defaultExpr(body: checkReachable(false).thenExpr(intLiteral(2))),
|
||||
]).stmt,
|
||||
checkReachable(false),
|
||||
]);
|
||||
|
@ -1924,8 +1926,8 @@ main() {
|
|||
test('switchExpression throw in case body has isolated effect', () {
|
||||
h.run([
|
||||
switchExpr(expr('int'), [
|
||||
caseExpr(intLiteral(0).pattern, throw_(expr('C'))),
|
||||
defaultExpr(checkReachable(true).thenExpr(intLiteral(2))),
|
||||
caseExpr(intLiteral(0).pattern, body: throw_(expr('C'))),
|
||||
defaultExpr(body: checkReachable(true).thenExpr(intLiteral(2))),
|
||||
]).stmt,
|
||||
checkReachable(true),
|
||||
]);
|
||||
|
@ -1934,8 +1936,8 @@ main() {
|
|||
test('switchExpression throw in all case bodies affects flow after', () {
|
||||
h.run([
|
||||
switchExpr(expr('int'), [
|
||||
caseExpr(intLiteral(0).pattern, throw_(expr('C'))),
|
||||
defaultExpr(throw_(expr('C'))),
|
||||
caseExpr(intLiteral(0).pattern, body: throw_(expr('C'))),
|
||||
defaultExpr(body: throw_(expr('C'))),
|
||||
]).stmt,
|
||||
checkReachable(false),
|
||||
]);
|
||||
|
@ -1952,7 +1954,7 @@ main() {
|
|||
h.run([
|
||||
switchExpr(expr('int'), [
|
||||
caseExpr(x.pattern(type: 'int?'),
|
||||
checkNotPromoted(x).thenExpr(nullLiteral)),
|
||||
body: checkNotPromoted(x).thenExpr(nullLiteral)),
|
||||
]).stmt,
|
||||
]);
|
||||
});
|
||||
|
@ -1962,10 +1964,10 @@ main() {
|
|||
switch_(
|
||||
throw_(expr('C')),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [
|
||||
case_(intLiteral(0).pattern, body: [
|
||||
checkReachable(false),
|
||||
]),
|
||||
case_(intLiteral(1).pattern, [
|
||||
case_(intLiteral(1).pattern, body: [
|
||||
checkReachable(false),
|
||||
]),
|
||||
],
|
||||
|
@ -1985,7 +1987,7 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(x.pattern(type: 'int?'), [
|
||||
case_(x.pattern(type: 'int?'), body: [
|
||||
checkNotPromoted(x),
|
||||
]),
|
||||
],
|
||||
|
@ -1993,6 +1995,31 @@ main() {
|
|||
]);
|
||||
});
|
||||
|
||||
test('switchStatement_afterWhen() promotes', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
switch_(
|
||||
expr('num'),
|
||||
[
|
||||
case_(x.pattern(), when: x.expr.is_('int'), body: [
|
||||
checkPromoted(x, 'int'),
|
||||
]),
|
||||
],
|
||||
isExhaustive: true),
|
||||
]);
|
||||
});
|
||||
|
||||
test('switchStatement_afterWhen() called for switch expressions', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
switchExpr(expr('num'), [
|
||||
caseExpr(x.pattern(),
|
||||
when: x.expr.is_('int'),
|
||||
body: checkPromoted(x, 'int').thenExpr(expr('String'))),
|
||||
]).stmt,
|
||||
]);
|
||||
});
|
||||
|
||||
test('switchStatement_beginCase(false) restores previous promotions', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
|
@ -2001,12 +2028,12 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [
|
||||
case_(intLiteral(0).pattern, body: [
|
||||
checkPromoted(x, 'int'),
|
||||
x.write(expr('int?')).stmt,
|
||||
checkNotPromoted(x),
|
||||
]),
|
||||
case_(intLiteral(1).pattern, [
|
||||
case_(intLiteral(1).pattern, body: [
|
||||
checkPromoted(x, 'int'),
|
||||
x.write(expr('int?')).stmt,
|
||||
checkNotPromoted(x),
|
||||
|
@ -2024,7 +2051,7 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [
|
||||
case_(intLiteral(0).pattern, body: [
|
||||
checkPromoted(x, 'int'),
|
||||
x.write(expr('int?')).stmt,
|
||||
checkNotPromoted(x),
|
||||
|
@ -2043,7 +2070,7 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [
|
||||
case_(intLiteral(0).pattern, body: [
|
||||
checkPromoted(x, 'int'),
|
||||
localFunction([
|
||||
x.write(expr('int?')).stmt,
|
||||
|
@ -2057,6 +2084,7 @@ main() {
|
|||
|
||||
test('switchStatement_beginCase(true) un-promotes', () {
|
||||
var x = Var('x');
|
||||
var l = Label('l');
|
||||
late SsaNode<Type> ssaBeforeSwitch;
|
||||
h.run([
|
||||
declare(x, type: 'int?', initializer: expr('int?')),
|
||||
|
@ -2067,16 +2095,13 @@ main() {
|
|||
getSsaNodes((nodes) => ssaBeforeSwitch = nodes[x]!),
|
||||
])),
|
||||
[
|
||||
case_(
|
||||
intLiteral(0).pattern,
|
||||
[
|
||||
checkNotPromoted(x),
|
||||
getSsaNodes(
|
||||
(nodes) => expect(nodes[x], isNot(ssaBeforeSwitch))),
|
||||
x.write(expr('int?')).stmt,
|
||||
checkNotPromoted(x),
|
||||
],
|
||||
hasLabel: true),
|
||||
l.thenCase(case_(intLiteral(0).pattern, body: [
|
||||
checkNotPromoted(x),
|
||||
getSsaNodes(
|
||||
(nodes) => expect(nodes[x], isNot(ssaBeforeSwitch))),
|
||||
x.write(expr('int?')).stmt,
|
||||
checkNotPromoted(x),
|
||||
])),
|
||||
],
|
||||
isExhaustive: false),
|
||||
]);
|
||||
|
@ -2084,23 +2109,21 @@ main() {
|
|||
|
||||
test('switchStatement_beginCase(true) handles write captures in cases', () {
|
||||
var x = Var('x');
|
||||
var l = Label('l');
|
||||
h.run([
|
||||
declare(x, type: 'int?', initializer: expr('int?')),
|
||||
x.expr.as_('int').stmt,
|
||||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(
|
||||
intLiteral(0).pattern,
|
||||
[
|
||||
x.expr.as_('int').stmt,
|
||||
checkNotPromoted(x),
|
||||
localFunction([
|
||||
x.write(expr('int?')).stmt,
|
||||
]),
|
||||
checkNotPromoted(x),
|
||||
],
|
||||
hasLabel: true),
|
||||
l.thenCase(case_(intLiteral(0).pattern, body: [
|
||||
x.expr.as_('int').stmt,
|
||||
checkNotPromoted(x),
|
||||
localFunction([
|
||||
x.write(expr('int?')).stmt,
|
||||
]),
|
||||
checkNotPromoted(x),
|
||||
])),
|
||||
],
|
||||
isExhaustive: false),
|
||||
]);
|
||||
|
@ -2119,7 +2142,7 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [
|
||||
case_(intLiteral(0).pattern, body: [
|
||||
x.expr.as_('int').stmt,
|
||||
y.write(expr('int?')).stmt,
|
||||
break_(),
|
||||
|
@ -2148,13 +2171,13 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [
|
||||
case_(intLiteral(0).pattern, body: [
|
||||
w.expr.as_('int').stmt,
|
||||
y.expr.as_('int').stmt,
|
||||
x.write(expr('int?')).stmt,
|
||||
break_(),
|
||||
]),
|
||||
default_([
|
||||
default_(body: [
|
||||
w.expr.as_('int').stmt,
|
||||
x.expr.as_('int').stmt,
|
||||
y.write(expr('int?')).stmt,
|
||||
|
@ -2176,17 +2199,41 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [
|
||||
case_(intLiteral(0).pattern, body: [
|
||||
x.expr.as_('int').stmt,
|
||||
break_(),
|
||||
]),
|
||||
default_([]),
|
||||
default_(body: []),
|
||||
],
|
||||
isExhaustive: true),
|
||||
checkNotPromoted(x),
|
||||
]);
|
||||
});
|
||||
|
||||
test('switchStatement_endAlternative() joins branches', () {
|
||||
var x = Var('x');
|
||||
var y = Var('y');
|
||||
var z = Var('z');
|
||||
h.run([
|
||||
declare(y, type: 'num'),
|
||||
declare(z, type: 'num'),
|
||||
switch_(
|
||||
expr('num'),
|
||||
[
|
||||
case_(x.pattern(),
|
||||
when: x.expr.is_('int').and(y.expr.is_('int')), body: []),
|
||||
case_(x.pattern(),
|
||||
when: y.expr.is_('int').and(z.expr.is_('int')),
|
||||
body: [
|
||||
checkNotPromoted(x),
|
||||
checkPromoted(y, 'int'),
|
||||
checkNotPromoted(z),
|
||||
]),
|
||||
],
|
||||
isExhaustive: true),
|
||||
]);
|
||||
});
|
||||
|
||||
test('tryCatchStatement_bodyEnd() restores pre-try state', () {
|
||||
var x = Var('x');
|
||||
var y = Var('y');
|
||||
|
|
|
@ -28,14 +28,15 @@ Statement block(List<Statement> statements) => new _Block(statements);
|
|||
|
||||
Expression booleanLiteral(bool value) => _BooleanLiteral(value);
|
||||
|
||||
Statement break_([LabeledStatement? target]) => new _Break(target);
|
||||
Statement break_([Label? target]) => new _Break(target);
|
||||
|
||||
StatementCase case_(Pattern pattern, List<Statement> body,
|
||||
{bool hasLabel = false}) =>
|
||||
StatementCase._(hasLabel, pattern, _Block(body));
|
||||
StatementCase case_(Pattern pattern,
|
||||
{Expression? when, required List<Statement> body}) =>
|
||||
StatementCase._(pattern, when, _Block(body));
|
||||
|
||||
ExpressionCase caseExpr(Pattern pattern, Expression expression) =>
|
||||
ExpressionCase._(pattern, expression);
|
||||
ExpressionCase caseExpr(Pattern pattern,
|
||||
{Expression? when, required Expression body}) =>
|
||||
ExpressionCase._(pattern, when, body);
|
||||
|
||||
/// Creates a pseudo-statement whose function is to verify that flow analysis
|
||||
/// considers [variable]'s assigned state to be [expectedAssignedState].
|
||||
|
@ -77,11 +78,11 @@ Statement declare(Var variable,
|
|||
isLate: isLate,
|
||||
isFinal: isFinal);
|
||||
|
||||
StatementCase default_(List<Statement> body, {bool hasLabel = false}) =>
|
||||
StatementCase._(hasLabel, null, _Block(body));
|
||||
StatementCase default_({required List<Statement> body}) =>
|
||||
StatementCase._(null, null, _Block(body));
|
||||
|
||||
ExpressionCase defaultExpr(Expression expression) =>
|
||||
ExpressionCase._(null, expression);
|
||||
ExpressionCase defaultExpr({required Expression body}) =>
|
||||
ExpressionCase._(null, null, body);
|
||||
|
||||
Statement do_(List<Statement> body, Expression condition) =>
|
||||
_Do(block(body), condition);
|
||||
|
@ -146,12 +147,6 @@ Statement if_(Expression condition, List<Statement> ifTrue,
|
|||
Literal intLiteral(int value, {bool? expectConversionToDouble}) =>
|
||||
new _IntLiteral(value, expectConversionToDouble: expectConversionToDouble);
|
||||
|
||||
Statement labeled(Statement Function(LabeledStatement) callback) {
|
||||
var labeledStatement = LabeledStatement._();
|
||||
labeledStatement._body = callback(labeledStatement);
|
||||
return labeledStatement;
|
||||
}
|
||||
|
||||
Statement localFunction(List<Statement> body) => _LocalFunction(block(body));
|
||||
|
||||
Statement match(Pattern pattern, Expression initializer,
|
||||
|
@ -279,13 +274,19 @@ class ExpressionCase extends Node
|
|||
@override
|
||||
final Pattern? pattern;
|
||||
|
||||
@override
|
||||
final Expression? when;
|
||||
|
||||
@override
|
||||
final Expression body;
|
||||
|
||||
ExpressionCase._(this.pattern, this.body) : super._();
|
||||
ExpressionCase._(this.pattern, this.when, this.body) : super._();
|
||||
|
||||
String toString() =>
|
||||
[pattern == null ? 'default:' : 'case $pattern:', '$body'].join(' ');
|
||||
String toString() => [
|
||||
pattern == null ? 'default' : 'case $pattern',
|
||||
if (when != null) ' when $when',
|
||||
': $body'
|
||||
].join('');
|
||||
|
||||
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
|
||||
pattern?.preVisit(assignedVariables);
|
||||
|
@ -642,23 +643,29 @@ class Harness
|
|||
}
|
||||
}
|
||||
|
||||
class LabeledStatement extends Statement {
|
||||
late final Statement _body;
|
||||
class Label extends Node {
|
||||
final String _name;
|
||||
|
||||
LabeledStatement._();
|
||||
late final Node _binding;
|
||||
|
||||
@override
|
||||
void preVisit(AssignedVariables<Node, Var> assignedVariables) {
|
||||
_body.preVisit(assignedVariables);
|
||||
Label(this._name) : super._();
|
||||
|
||||
StatementCase thenCase(StatementCase case_) {
|
||||
case_.labels.insert(0, this);
|
||||
return case_;
|
||||
}
|
||||
|
||||
Statement thenStmt(Statement statement) {
|
||||
if (statement is! _LabeledStatement) {
|
||||
statement = _LabeledStatement(statement);
|
||||
}
|
||||
statement._labels.insert(0, this);
|
||||
_binding = statement;
|
||||
return statement;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'labeled: $_body';
|
||||
|
||||
@override
|
||||
void visit(Harness h) {
|
||||
h.typeAnalyzer.analyzeLabeledStatement(this, _body);
|
||||
}
|
||||
String toString() => _name;
|
||||
}
|
||||
|
||||
abstract class Literal extends Expression {
|
||||
|
@ -765,16 +772,20 @@ abstract class Statement extends Node {
|
|||
|
||||
/// Representation of a single case clause in a switch statement. Use [case_]
|
||||
/// to create instances of this class.
|
||||
class StatementCase extends Node implements StatementCaseInfo<Statement, Node> {
|
||||
class StatementCase extends Node
|
||||
implements StatementCaseInfo<Statement, Expression, Node> {
|
||||
@override
|
||||
final bool hasLabel;
|
||||
final List<Label> labels = [];
|
||||
|
||||
@override
|
||||
final Pattern? pattern;
|
||||
|
||||
@override
|
||||
final Expression? when;
|
||||
|
||||
final _Block _statements;
|
||||
|
||||
StatementCase._(this.hasLabel, this.pattern, this._statements) : super._();
|
||||
StatementCase._(this.pattern, this.when, this._statements) : super._();
|
||||
|
||||
@override
|
||||
List<Statement> get body => _statements.statements;
|
||||
|
@ -783,7 +794,7 @@ class StatementCase extends Node implements StatementCaseInfo<Statement, Node> {
|
|||
Node get node => this;
|
||||
|
||||
String toString() => [
|
||||
if (hasLabel) '<label>:',
|
||||
for (var label in labels) '$label:',
|
||||
pattern == null ? 'default:' : 'case $pattern:',
|
||||
...body
|
||||
].join(' ');
|
||||
|
@ -945,7 +956,7 @@ class _BooleanLiteral extends Literal {
|
|||
}
|
||||
|
||||
class _Break extends Statement {
|
||||
final LabeledStatement? target;
|
||||
final Label? target;
|
||||
|
||||
_Break(this.target);
|
||||
|
||||
|
@ -957,7 +968,7 @@ class _Break extends Statement {
|
|||
|
||||
@override
|
||||
void visit(Harness h) {
|
||||
h.typeAnalyzer.analyzeBreakStatement(target);
|
||||
h.typeAnalyzer.analyzeBreakStatement(target?._binding as Statement?);
|
||||
h.irBuilder.apply('break', 0);
|
||||
}
|
||||
}
|
||||
|
@ -1619,6 +1630,27 @@ class _Is extends Expression {
|
|||
}
|
||||
}
|
||||
|
||||
class _LabeledStatement extends Statement {
|
||||
final List<Label> _labels = [];
|
||||
|
||||
final Statement _body;
|
||||
|
||||
_LabeledStatement(this._body);
|
||||
|
||||
@override
|
||||
void preVisit(AssignedVariables<Node, Var> assignedVariables) {
|
||||
_body.preVisit(assignedVariables);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => [..._labels, _body].join(': ');
|
||||
|
||||
@override
|
||||
void visit(Harness h) {
|
||||
h.typeAnalyzer.analyzeLabeledStatement(this, _body);
|
||||
}
|
||||
}
|
||||
|
||||
class _LocalFunction extends Statement {
|
||||
final Statement body;
|
||||
|
||||
|
@ -1752,6 +1784,7 @@ class _MiniAstTypeAnalyzer
|
|||
|
||||
final _irBuilder = MiniIrBuilder();
|
||||
|
||||
@override
|
||||
late final Type boolType = Type('bool');
|
||||
|
||||
@override
|
||||
|
@ -2083,8 +2116,16 @@ class _MiniAstTypeAnalyzer
|
|||
}
|
||||
|
||||
@override
|
||||
void handleCase_afterCaseHeads(int numHeads) {
|
||||
_irBuilder.apply('heads', numHeads);
|
||||
void handleCase_afterCaseHeads(List<Node> labels, int numHeads) {
|
||||
for (var label in labels) {
|
||||
_irBuilder.atom((label as Label)._name);
|
||||
}
|
||||
_irBuilder.apply('heads', numHeads + labels.length);
|
||||
}
|
||||
|
||||
@override
|
||||
void handleCaseHead({required bool hasWhen}) {
|
||||
_irBuilder.apply('head', hasWhen ? 2 : 1);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -90,7 +90,7 @@ main() {
|
|||
test('IR', () {
|
||||
h.run([
|
||||
switchExpr(expr('int'), [
|
||||
defaultExpr(intLiteral(0)),
|
||||
defaultExpr(body: intLiteral(0)),
|
||||
]).checkIr('switchExpr(expr(int), case(heads(default), 0))').stmt,
|
||||
]);
|
||||
});
|
||||
|
@ -98,7 +98,7 @@ main() {
|
|||
test('scrutinee expression context', () {
|
||||
h.run([
|
||||
switchExpr(expr('int').checkContext('?'), [
|
||||
defaultExpr(intLiteral(0)),
|
||||
defaultExpr(body: intLiteral(0)),
|
||||
]).inContext('num'),
|
||||
]);
|
||||
});
|
||||
|
@ -106,7 +106,7 @@ main() {
|
|||
test('body expression context', () {
|
||||
h.run([
|
||||
switchExpr(expr('int'), [
|
||||
defaultExpr(nullLiteral.checkContext('C?')),
|
||||
defaultExpr(body: nullLiteral.checkContext('C?')),
|
||||
]).inContext('C?'),
|
||||
]);
|
||||
});
|
||||
|
@ -114,11 +114,29 @@ main() {
|
|||
test('least upper bound behavior', () {
|
||||
h.run([
|
||||
switchExpr(expr('int'), [
|
||||
caseExpr(intLiteral(0).pattern, expr('int')),
|
||||
defaultExpr(expr('double')),
|
||||
caseExpr(intLiteral(0).pattern, body: expr('int')),
|
||||
defaultExpr(body: expr('double')),
|
||||
]).checkType('num').stmt
|
||||
]);
|
||||
});
|
||||
|
||||
test('when clause', () {
|
||||
var i = Var('i');
|
||||
h.run([
|
||||
switchExpr(expr('int'), [
|
||||
caseExpr(i.pattern(),
|
||||
when: i.expr
|
||||
.checkType('int')
|
||||
.eq(expr('num'))
|
||||
.checkContext('bool'),
|
||||
body: expr('String')),
|
||||
])
|
||||
.checkIr('switchExpr(expr(int), '
|
||||
'case(heads(head(varPattern(i, int), ==(i, expr(num)))), '
|
||||
'expr(String)))')
|
||||
.stmt,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -129,13 +147,13 @@ main() {
|
|||
switch_(
|
||||
expr('int').checkContext('?'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [
|
||||
case_(intLiteral(0).pattern, body: [
|
||||
break_(),
|
||||
]),
|
||||
],
|
||||
isExhaustive: false)
|
||||
.checkIr('switch(expr(int), '
|
||||
'case(heads(const(0)), block(break())))'),
|
||||
'case(heads(head(const(0))), block(break())))'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -146,13 +164,13 @@ main() {
|
|||
switch_(
|
||||
expr('int').checkContext('?'),
|
||||
[
|
||||
case_(x.pattern(), [
|
||||
case_(x.pattern(), body: [
|
||||
break_(),
|
||||
]),
|
||||
],
|
||||
isExhaustive: false)
|
||||
.checkIr('switch(expr(int), '
|
||||
'case(heads(varPattern(x, int)), '
|
||||
'case(heads(head(varPattern(x, int))), '
|
||||
'block(break())))'),
|
||||
]);
|
||||
});
|
||||
|
@ -163,13 +181,13 @@ main() {
|
|||
switch_(
|
||||
expr('int').checkContext('?'),
|
||||
[
|
||||
case_(x.pattern(type: 'num'), [
|
||||
case_(x.pattern(type: 'num'), body: [
|
||||
break_(),
|
||||
]),
|
||||
],
|
||||
isExhaustive: false)
|
||||
.checkIr('switch(expr(int), '
|
||||
'case(heads(varPattern(x, num)), '
|
||||
'case(heads(head(varPattern(x, num))), '
|
||||
'block(break())))'),
|
||||
]);
|
||||
});
|
||||
|
@ -180,7 +198,7 @@ main() {
|
|||
switch_(
|
||||
expr('int').checkContext('?'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [
|
||||
case_(intLiteral(0).pattern, body: [
|
||||
break_(),
|
||||
]),
|
||||
],
|
||||
|
@ -193,37 +211,43 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, []),
|
||||
case_(intLiteral(1).pattern, [
|
||||
case_(intLiteral(0).pattern, body: []),
|
||||
case_(intLiteral(1).pattern, body: [
|
||||
break_(),
|
||||
]),
|
||||
],
|
||||
isExhaustive: false)
|
||||
.checkIr('switch(expr(int), '
|
||||
'case(heads(const(0), const(1)), block(break())))'),
|
||||
'case(heads(head(const(0)), head(const(1))), '
|
||||
'block(break())))'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('merge labels', () {
|
||||
var x = Var('x');
|
||||
var l = Label('l');
|
||||
h.run([
|
||||
declare(x, type: 'int?', initializer: expr('int?')),
|
||||
x.expr.as_('int').stmt,
|
||||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [], hasLabel: true),
|
||||
case_(intLiteral(1).pattern, [
|
||||
x.expr.checkType('int?').stmt,
|
||||
break_(),
|
||||
]),
|
||||
case_(intLiteral(2).pattern, [
|
||||
x.expr.checkType('int').stmt,
|
||||
x.write(nullLiteral).stmt,
|
||||
continue_(),
|
||||
])
|
||||
],
|
||||
isExhaustive: false),
|
||||
expr('int'),
|
||||
[
|
||||
l.thenCase(case_(intLiteral(0).pattern, body: [])),
|
||||
case_(intLiteral(1).pattern, body: [
|
||||
x.expr.checkType('int?').stmt,
|
||||
break_(),
|
||||
]),
|
||||
case_(intLiteral(2).pattern, body: [
|
||||
x.expr.checkType('int').stmt,
|
||||
x.write(nullLiteral).stmt,
|
||||
continue_(),
|
||||
])
|
||||
],
|
||||
isExhaustive: false)
|
||||
.checkIr('switch(expr(int), '
|
||||
'case(heads(head(const(0)), head(const(1)), l), '
|
||||
'block(x, break())), '
|
||||
'case(heads(head(const(2))), block(x, null, continue())))'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -232,15 +256,37 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [
|
||||
case_(intLiteral(0).pattern, body: [
|
||||
break_(),
|
||||
]),
|
||||
case_(intLiteral(1).pattern, []),
|
||||
case_(intLiteral(1).pattern, body: []),
|
||||
],
|
||||
isExhaustive: false)
|
||||
.checkIr('switch(expr(int), '
|
||||
'case(heads(const(0)), block(break())), '
|
||||
'case(heads(const(1)), block()))'),
|
||||
'case(heads(head(const(0))), block(break())), '
|
||||
'case(heads(head(const(1))), block()))'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('when clause', () {
|
||||
var i = Var('i');
|
||||
h.run([
|
||||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(i.pattern(),
|
||||
when: i.expr
|
||||
.checkType('int')
|
||||
.eq(expr('num'))
|
||||
.checkContext('bool'),
|
||||
body: [
|
||||
break_(),
|
||||
]),
|
||||
],
|
||||
isExhaustive: true)
|
||||
.checkIr('switch(expr(int), '
|
||||
'case(heads(head(varPattern(i, int), ==(i, expr(num)))), '
|
||||
'block(break())))'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -251,8 +297,8 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(x.pattern(), []),
|
||||
default_([])..errorId = 'DEFAULT',
|
||||
case_(x.pattern(), body: []),
|
||||
default_(body: [])..errorId = 'DEFAULT',
|
||||
],
|
||||
isExhaustive: true)
|
||||
.expectErrors({'missingMatchVar(DEFAULT, x)'}),
|
||||
|
@ -265,13 +311,28 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(intLiteral(0).pattern, [])..errorId = 'CASE(0)',
|
||||
case_(x.pattern(), []),
|
||||
case_(intLiteral(0).pattern, body: [])
|
||||
..errorId = 'CASE(0)',
|
||||
case_(x.pattern(), body: []),
|
||||
],
|
||||
isExhaustive: true)
|
||||
.expectErrors({'missingMatchVar(CASE(0), x)'}),
|
||||
]);
|
||||
});
|
||||
|
||||
test('label', () {
|
||||
var x = Var('x');
|
||||
var l = Label('l')..errorId = 'LABEL';
|
||||
h.run([
|
||||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
l.thenCase(case_(x.pattern(), body: [])),
|
||||
],
|
||||
isExhaustive: true)
|
||||
.expectErrors({'missingMatchVar(LABEL, x)'}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('conflicting var:', () {
|
||||
|
@ -282,9 +343,9 @@ main() {
|
|||
expr('num'),
|
||||
[
|
||||
case_(x.pattern(type: 'int')..errorId = 'PATTERN(int x)',
|
||||
[]),
|
||||
body: []),
|
||||
case_(x.pattern(type: 'num')..errorId = 'PATTERN(num x)',
|
||||
[]),
|
||||
body: []),
|
||||
],
|
||||
isExhaustive: true)
|
||||
.expectErrors({
|
||||
|
@ -302,9 +363,9 @@ main() {
|
|||
switch_(
|
||||
expr('int'),
|
||||
[
|
||||
case_(x.pattern()..errorId = 'PATTERN(x)', []),
|
||||
case_(x.pattern()..errorId = 'PATTERN(x)', body: []),
|
||||
case_(x.pattern(type: 'int')..errorId = 'PATTERN(int x)',
|
||||
[]),
|
||||
body: []),
|
||||
],
|
||||
isExhaustive: true)
|
||||
.expectErrors({
|
||||
|
|
|
@ -1225,6 +1225,7 @@ sh
|
|||
sha
|
||||
shadowed
|
||||
shallow
|
||||
shares
|
||||
shas
|
||||
shelf
|
||||
shifts
|
||||
|
|
Loading…
Reference in a new issue