mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 13:57:58 +00:00
Begin implementing flow analysis for patterns.
This CL adds support for flow analysis with variable patterns and guards, and integrates it with if-case elements, if-case statements, pattern variable declarations, switch expressions, and switch statements. It includes support for guards. No other types of patterns are handled yet. Bug: https://github.com/dart-lang/sdk/issues/50419 Change-Id: Iacad82b472cba0e2e670981847258e4046017576 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/274162 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
parent
f0c270e96d
commit
ea9b19c2c4
|
@ -178,6 +178,8 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
/// [conditionalExpression] should be the entire conditional expression.
|
||||
void conditional_thenBegin(Expression condition, Node conditionalExpression);
|
||||
|
||||
void constantPattern_end(Expression expression);
|
||||
|
||||
/// Register a declaration of the [variable] in the current state.
|
||||
/// Should also be called for function parameters.
|
||||
///
|
||||
|
@ -335,6 +337,41 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
/// Should also be called if a subexpression's type is Never.
|
||||
void handleExit();
|
||||
|
||||
/// Call this method after visiting the scrutinee expression of an if-case
|
||||
/// statement.
|
||||
void ifCaseStatement_afterExpression(Expression scrutinee);
|
||||
|
||||
/// Call this method before visiting an if-case statement.
|
||||
///
|
||||
/// The order of visiting an if-case statement with no "else" part should be:
|
||||
/// - Call [ifCaseStatement_begin]
|
||||
/// - Visit the expression
|
||||
/// - Call [ifCaseStatement_afterExpression]
|
||||
/// - Visit the pattern
|
||||
/// - Visit the guard (if any)
|
||||
/// - Call [ifCaseStatement_thenBegin]
|
||||
/// - Visit the "then" statement
|
||||
/// - Call [ifStatement_end], passing `false` for `hasElse`.
|
||||
///
|
||||
/// The order of visiting an if-case statement with an "else" part should be:
|
||||
/// - Call [ifCaseStatement_begin]
|
||||
/// - Visit the expression
|
||||
/// - Call [ifCaseStatement_afterExpression]
|
||||
/// - Visit the pattern
|
||||
/// - Visit the guard (if any)
|
||||
/// - Call [ifCaseStatement_thenBegin]
|
||||
/// - Visit the "then" statement
|
||||
/// - Call [ifStatement_elseBegin]
|
||||
/// - Visit the "else" statement
|
||||
/// - Call [ifStatement_end], passing `true` for `hasElse`.
|
||||
void ifCaseStatement_begin();
|
||||
|
||||
/// Call this method after visiting pattern and guard parts of an if-case
|
||||
/// statement.
|
||||
///
|
||||
/// [guard] should be the guard expression (if present); otherwise `null`.
|
||||
void ifCaseStatement_thenBegin(Expression? guard);
|
||||
|
||||
/// Call this method after visiting the RHS of an if-null expression ("??")
|
||||
/// or if-null assignment ("??=").
|
||||
///
|
||||
|
@ -481,6 +518,14 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
void parenthesizedExpression(
|
||||
Expression outerExpression, Expression innerExpression);
|
||||
|
||||
/// Call this method just after visiting the initializer of a pattern variable
|
||||
/// declaration, and before visiting the pattern.
|
||||
void patternVariableDeclaration_afterInitializer(Expression initializer);
|
||||
|
||||
/// Call this method after visiting the pattern of a pattern variable
|
||||
/// declaration.
|
||||
void patternVariableDeclaration_end();
|
||||
|
||||
/// Retrieves the type that a property named [propertyName] is promoted to, if
|
||||
/// the property is currently promoted. Otherwise returns `null`.
|
||||
///
|
||||
|
@ -538,21 +583,15 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
@visibleForTesting
|
||||
SsaNode<Type>? ssaNodeForTesting(Variable variable);
|
||||
|
||||
/// Call this method just after visiting a guard part of a case clause. See
|
||||
/// Call this method just before visiting a `case` or `default` clause. See
|
||||
/// [switchStatement_expressionEnd] for details.
|
||||
///
|
||||
/// [when] should be the expression following the `when` keyword.
|
||||
void switchStatement_afterGuard(Expression when);
|
||||
void switchStatement_beginAlternative();
|
||||
|
||||
/// Call this method just before visiting a sequence of two or more `case` or
|
||||
/// Call this method just before visiting a sequence of one or more `case` or
|
||||
/// `default` clauses that share a body. See [switchStatement_expressionEnd]
|
||||
/// for details.`
|
||||
/// 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.
|
||||
void switchStatement_beginCase();
|
||||
|
||||
/// Call this method just after visiting the body of a switch statement. See
|
||||
/// [switchStatement_expressionEnd] for details.
|
||||
///
|
||||
|
@ -561,12 +600,13 @@ 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
|
||||
/// Call this method just after visiting a `case` or `default` clause. See
|
||||
/// [switchStatement_expressionEnd] for details.`
|
||||
void switchStatement_endAlternative();
|
||||
///
|
||||
/// [guard] should be the expression following the `when` keyword, if present.
|
||||
void switchStatement_endAlternative(Expression? guard);
|
||||
|
||||
/// Call this method just after visiting a sequence of two or more `case` or
|
||||
/// Call this method just after visiting a sequence of one or more `case` or
|
||||
/// `default` clauses that share a body. See [switchStatement_expressionEnd]
|
||||
/// for details.`
|
||||
///
|
||||
|
@ -574,7 +614,7 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
/// [AssignedVariables.endNode] for the switch statement.
|
||||
///
|
||||
/// [hasLabels] indicates whether the case has any labels.
|
||||
void switchStatement_endAlternatives(Statement node,
|
||||
void switchStatement_endAlternatives(Statement? node,
|
||||
{required bool hasLabels});
|
||||
|
||||
/// Call this method just after visiting the expression part of a switch
|
||||
|
@ -585,20 +625,20 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
/// - Visit the switch expression.
|
||||
/// - Call [switchStatement_expressionEnd].
|
||||
/// - For each case body:
|
||||
/// - Call [switchStatement_beginCase].
|
||||
/// - If there is more than one `case` or `default` clause associated with
|
||||
/// this case body, call [switchStatement_beginAlternatives]. (Also safe
|
||||
/// to call if there is just one `case` or `default` clause).
|
||||
/// - 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_afterGuard].
|
||||
/// - If [switchStatement_beginAlternatives] was called, call
|
||||
/// [switchStatement_endAlternative].
|
||||
/// - If [switchStatement_beginAlternatives] was called, call
|
||||
/// [switchStatement_endAlternatives].
|
||||
/// - Call [switchStatement_beginAlternative].
|
||||
/// - If a pattern is present, visit it.
|
||||
/// - If a guard is present, visit it.
|
||||
/// - Call [switchStatement_endAlternative].
|
||||
/// - Call [switchStatement_endAlternatives].
|
||||
/// - Visit the case body.
|
||||
/// - Call [switchStatement_end].
|
||||
void switchStatement_expressionEnd(Statement? switchStatement);
|
||||
///
|
||||
/// [scrutinee] should be the expression appearing in parentheses after the
|
||||
/// `switch` keyword.
|
||||
void switchStatement_expressionEnd(
|
||||
Statement? switchStatement, Expression scrutinee);
|
||||
|
||||
/// Call this method just after visiting the expression `this` (or the
|
||||
/// pseudo-expression `super`, in the case of the analyzer, which represents
|
||||
|
@ -706,6 +746,12 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
/// statement.
|
||||
void tryFinallyStatement_finallyBegin(Node body);
|
||||
|
||||
/// Call this method after visiting a variable pattern.
|
||||
///
|
||||
/// [matchedType] should be the static type of the value being matched.
|
||||
/// [staticType] should be the static type of the variable pattern itself.
|
||||
void variablePattern({required Type matchedType, required Type staticType});
|
||||
|
||||
/// Call this method when encountering an expression that reads the value of
|
||||
/// a variable.
|
||||
///
|
||||
|
@ -892,6 +938,12 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
() => _wrapped.conditional_thenBegin(condition, conditionalExpression));
|
||||
}
|
||||
|
||||
@override
|
||||
void constantPattern_end(Expression expression) {
|
||||
_wrap('constantPattern_end($expression)',
|
||||
() => _wrapped.constantPattern_end(expression));
|
||||
}
|
||||
|
||||
@override
|
||||
void declare(Variable variable, bool initialized) {
|
||||
_wrap('declare($variable, $initialized)',
|
||||
|
@ -1014,6 +1066,23 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
_wrap('handleExit()', () => _wrapped.handleExit());
|
||||
}
|
||||
|
||||
@override
|
||||
void ifCaseStatement_afterExpression(Expression scrutinee) {
|
||||
_wrap('ifCaseStatement_afterExpression($scrutinee)',
|
||||
() => _wrapped.ifCaseStatement_afterExpression(scrutinee));
|
||||
}
|
||||
|
||||
@override
|
||||
void ifCaseStatement_begin() {
|
||||
_wrap('ifCaseStatement_begin()', () => _wrapped.ifCaseStatement_begin());
|
||||
}
|
||||
|
||||
@override
|
||||
void ifCaseStatement_thenBegin(Expression? guard) {
|
||||
_wrap('ifCaseStatement_thenBegin($guard)',
|
||||
() => _wrapped.ifCaseStatement_thenBegin(guard));
|
||||
}
|
||||
|
||||
@override
|
||||
void ifNullExpression_end() {
|
||||
return _wrap(
|
||||
|
@ -1174,6 +1243,20 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
_wrapped.parenthesizedExpression(outerExpression, innerExpression));
|
||||
}
|
||||
|
||||
@override
|
||||
void patternVariableDeclaration_afterInitializer(Expression initializer) {
|
||||
_wrap(
|
||||
'patternVariableDeclaration_afterInitializer($initializer)',
|
||||
() =>
|
||||
_wrapped.patternVariableDeclaration_afterInitializer(initializer));
|
||||
}
|
||||
|
||||
@override
|
||||
void patternVariableDeclaration_end() {
|
||||
_wrap('patternVariableDeclaration_end()',
|
||||
() => _wrapped.patternVariableDeclaration_end());
|
||||
}
|
||||
|
||||
@override
|
||||
Type? promotedPropertyType(Expression? target, String propertyName,
|
||||
Object? propertyMember, Type staticType) {
|
||||
|
@ -1212,9 +1295,9 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
}
|
||||
|
||||
@override
|
||||
void switchStatement_afterGuard(Expression when) {
|
||||
_wrap('switchStatement_afterGuard($when)',
|
||||
() => _wrapped.switchStatement_afterGuard(when));
|
||||
void switchStatement_beginAlternative() {
|
||||
_wrap('switchStatement_beginAlternative()',
|
||||
() => _wrapped.switchStatement_beginAlternative());
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1223,12 +1306,6 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
() => _wrapped.switchStatement_beginAlternatives());
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_beginCase() {
|
||||
_wrap('switchStatement_beginCase()',
|
||||
() => _wrapped.switchStatement_beginCase());
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_end(bool isExhaustive) {
|
||||
_wrap('switchStatement_end($isExhaustive)',
|
||||
|
@ -1236,13 +1313,13 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
}
|
||||
|
||||
@override
|
||||
void switchStatement_endAlternative() {
|
||||
_wrap('switchStatement_endAlternative()',
|
||||
() => _wrapped.switchStatement_endAlternative());
|
||||
void switchStatement_endAlternative(Expression? guard) {
|
||||
_wrap('switchStatement_endAlternative($guard)',
|
||||
() => _wrapped.switchStatement_endAlternative(guard));
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_endAlternatives(Statement node,
|
||||
void switchStatement_endAlternatives(Statement? node,
|
||||
{required bool hasLabels}) {
|
||||
_wrap(
|
||||
'switchStatement_endAlternatives($node, hasLabels: $hasLabels)',
|
||||
|
@ -1251,9 +1328,12 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
}
|
||||
|
||||
@override
|
||||
void switchStatement_expressionEnd(Statement? switchStatement) {
|
||||
_wrap('switchStatement_expressionEnd($switchStatement)',
|
||||
() => _wrapped.switchStatement_expressionEnd(switchStatement));
|
||||
void switchStatement_expressionEnd(
|
||||
Statement? switchStatement, Expression scrutinee) {
|
||||
_wrap(
|
||||
'switchStatement_expressionEnd($switchStatement, $scrutinee)',
|
||||
() =>
|
||||
_wrapped.switchStatement_expressionEnd(switchStatement, scrutinee));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1325,6 +1405,14 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
() => _wrapped.tryFinallyStatement_finallyBegin(body));
|
||||
}
|
||||
|
||||
@override
|
||||
void variablePattern({required Type matchedType, required Type staticType}) {
|
||||
_wrap(
|
||||
'variablePattern(matchedType: $matchedType, staticType: $staticType)',
|
||||
() => _wrapped.variablePattern(
|
||||
matchedType: matchedType, staticType: staticType));
|
||||
}
|
||||
|
||||
@override
|
||||
Type? variableRead(Expression expression, Variable variable) {
|
||||
return _wrap('variableRead($expression, $variable)',
|
||||
|
@ -3267,6 +3355,16 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
_current = conditionInfo.ifTrue;
|
||||
}
|
||||
|
||||
@override
|
||||
void constantPattern_end(Expression expression) {
|
||||
// As a temporary measure, just assume the pattern might or might not match.
|
||||
// This avoids some bogus "unreachable code" warnings in analyzer tests.
|
||||
// TODO(paulberry): replace this with an implementation that does similar
|
||||
// promotion to `==` operations.
|
||||
_PatternContext<Type> context = _stack.last as _PatternContext<Type>;
|
||||
context._unmatched = _join(context._unmatched, _current);
|
||||
}
|
||||
|
||||
@override
|
||||
void declare(Variable variable, bool initialized) {
|
||||
_current = _current.declare(
|
||||
|
@ -3468,6 +3566,31 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
_current = _current.setUnreachable();
|
||||
}
|
||||
|
||||
@override
|
||||
void ifCaseStatement_afterExpression(Expression scrutinee) {
|
||||
// If S0 is the statement `if (E0 case P when E1) S1 else S2`, then:
|
||||
// - before(P) = after(E0),
|
||||
// - before(E1) = matched(P).
|
||||
// Note that we don't need to take any action to handle
|
||||
// `before(E1) = matched(P)`, because we store both the "matched" state for
|
||||
// patterns and the "before" state for expressions in `_current`.
|
||||
_pushPattern(_getExpressionReference(scrutinee));
|
||||
}
|
||||
|
||||
@override
|
||||
void ifCaseStatement_begin() {
|
||||
// If S0 is the statement `if (E0 case P when E1) S1 else S2`, then:
|
||||
// - before(E0) = split(before(S0)).
|
||||
_current = _current.split();
|
||||
}
|
||||
|
||||
@override
|
||||
void ifCaseStatement_thenBegin(Expression? guard) {
|
||||
// If S0 is the statement `if (E0 case P when E1) S1 else S2`, then:
|
||||
// - before(S1) = true(E1).
|
||||
_stack.add(new _IfContext(_popPattern(guard)));
|
||||
}
|
||||
|
||||
@override
|
||||
void ifNullExpression_end() {
|
||||
_IfNullExpressionContext<Type> context =
|
||||
|
@ -3725,6 +3848,16 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
forwardExpression(outerExpression, innerExpression);
|
||||
}
|
||||
|
||||
@override
|
||||
void patternVariableDeclaration_afterInitializer(Expression initializer) {
|
||||
_pushPattern(_getExpressionReference(initializer));
|
||||
}
|
||||
|
||||
@override
|
||||
void patternVariableDeclaration_end() {
|
||||
_popPattern(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Type? promotedPropertyType(Expression? target, String propertyName,
|
||||
Object? propertyMember, Type staticType) {
|
||||
|
@ -3752,26 +3885,19 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
.variableInfo[promotionKeyStore.keyForVariable(variable)]?.ssaNode;
|
||||
|
||||
@override
|
||||
void switchStatement_afterGuard(Expression when) {
|
||||
ExpressionInfo<Type>? expressionInfo = _getExpressionInfo(when);
|
||||
if (expressionInfo != null) {
|
||||
_current = expressionInfo.ifTrue;
|
||||
}
|
||||
void switchStatement_beginAlternative() {
|
||||
_SwitchAlternativesContext<Type> context =
|
||||
_stack.last as _SwitchAlternativesContext<Type>;
|
||||
_pushPattern(context._scrutineeReference);
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_beginAlternatives() {
|
||||
_current = _current.split();
|
||||
_SwitchAlternativesContext<Type> context =
|
||||
new _SwitchAlternativesContext<Type>(_current);
|
||||
_stack.add(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_beginCase() {
|
||||
_SimpleStatementContext<Type> context =
|
||||
_stack.last as _SimpleStatementContext<Type>;
|
||||
_current = context._previous;
|
||||
_SwitchStatementContext<Type> context =
|
||||
_stack.last as _SwitchStatementContext<Type>;
|
||||
_current = context._previous.split();
|
||||
_stack.add(new _SwitchAlternativesContext<Type>(
|
||||
_current, context._scrutineeReference));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -3791,7 +3917,10 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
}
|
||||
|
||||
@override
|
||||
void switchStatement_endAlternative() {
|
||||
void switchStatement_endAlternative(Expression? guard) {
|
||||
// TODO(paulberry): make use of `unmatched`
|
||||
// ignore: unused_local_variable
|
||||
FlowModel<Type> unmatched = _popPattern(guard);
|
||||
_SwitchAlternativesContext<Type> context =
|
||||
_stack.last as _SwitchAlternativesContext<Type>;
|
||||
context._combinedModel = _join(context._combinedModel, _current);
|
||||
|
@ -3814,13 +3943,19 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
(alternativesContext._combinedModel ?? alternativesContext._previous)
|
||||
.unsplit();
|
||||
}
|
||||
// TODO(paulberry): consider doing a split here if unreachable, and a join
|
||||
// later, so that one case matching everything won't prevent promotion in
|
||||
// cases that follow
|
||||
}
|
||||
|
||||
@override
|
||||
void switchStatement_expressionEnd(Statement? switchStatement) {
|
||||
void switchStatement_expressionEnd(
|
||||
Statement? switchStatement, Expression scrutinee) {
|
||||
_current = _current.split();
|
||||
_SimpleStatementContext<Type> context =
|
||||
new _SimpleStatementContext<Type>(_current.reachable.parent!, _current);
|
||||
_SwitchStatementContext<Type> context = new _SwitchStatementContext<Type>(
|
||||
_current.reachable.parent!,
|
||||
_current,
|
||||
_getExpressionReference(scrutinee));
|
||||
_stack.add(context);
|
||||
if (switchStatement != null) {
|
||||
_statementToContext[switchStatement] = context;
|
||||
|
@ -3913,6 +4048,24 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
context._beforeFinally = _current;
|
||||
}
|
||||
|
||||
@override
|
||||
void variablePattern({required Type matchedType, required Type staticType}) {
|
||||
_PatternContext<Type> context = _stack.last as _PatternContext<Type>;
|
||||
ReferenceWithType<Type>? scrutineeReference = context._scrutineeReference;
|
||||
bool coversMatchedType =
|
||||
typeOperations.isSubtypeOf(matchedType, staticType);
|
||||
if (scrutineeReference != null) {
|
||||
ExpressionInfo<Type> promotionInfo =
|
||||
_current.tryPromoteForTypeCheck(this, scrutineeReference, staticType);
|
||||
_current = promotionInfo.ifTrue;
|
||||
if (!coversMatchedType) {
|
||||
context._unmatched = _join(context._unmatched, promotionInfo.ifFalse);
|
||||
}
|
||||
} else if (!coversMatchedType) {
|
||||
context._unmatched = _join(context._unmatched, _current);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Type? variableRead(Expression expression, Variable variable) {
|
||||
int variableKey = promotionKeyStore.keyForVariable(variable);
|
||||
|
@ -4146,6 +4299,23 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
FlowModel<Type> _merge(FlowModel<Type> first, FlowModel<Type>? second) =>
|
||||
FlowModel.merge(operations, first, second, _current._emptyVariableMap);
|
||||
|
||||
FlowModel<Type> _popPattern(Expression? guard) {
|
||||
_TopPatternContext<Type> context =
|
||||
_stack.removeLast() as _TopPatternContext<Type>;
|
||||
FlowModel<Type> unmatched = context._unmatched;
|
||||
if (guard != null) {
|
||||
ExpressionInfo<Type> guardInfo = _expressionEnd(guard);
|
||||
_current = guardInfo.ifTrue;
|
||||
unmatched = _join(unmatched, guardInfo.ifFalse);
|
||||
}
|
||||
return unmatched;
|
||||
}
|
||||
|
||||
void _pushPattern(ReferenceWithType<Type>? scrutineeReference) {
|
||||
_stack.add(new _TopPatternContext<Type>(
|
||||
scrutineeReference, _current.setUnreachable()));
|
||||
}
|
||||
|
||||
/// Associates [expression], which should be the most recently visited
|
||||
/// expression, with the given [expressionInfo] object, and updates the
|
||||
/// current flow model state to correspond to it.
|
||||
|
@ -4334,6 +4504,9 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
|
|||
_conditionalOrIf_thenBegin(condition, conditionalExpression);
|
||||
}
|
||||
|
||||
@override
|
||||
void constantPattern_end(Expression expression) {}
|
||||
|
||||
@override
|
||||
void declare(Variable variable, bool initialized) {}
|
||||
|
||||
|
@ -4406,6 +4579,15 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
|
|||
@override
|
||||
void handleExit() {}
|
||||
|
||||
@override
|
||||
void ifCaseStatement_afterExpression(Expression scrutinee) {}
|
||||
|
||||
@override
|
||||
void ifCaseStatement_begin() {}
|
||||
|
||||
@override
|
||||
void ifCaseStatement_thenBegin(Expression? guard) {}
|
||||
|
||||
@override
|
||||
void ifNullExpression_end() {}
|
||||
|
||||
|
@ -4596,6 +4778,12 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
|
|||
forwardExpression(outerExpression, innerExpression);
|
||||
}
|
||||
|
||||
@override
|
||||
void patternVariableDeclaration_afterInitializer(Expression initializer) {}
|
||||
|
||||
@override
|
||||
void patternVariableDeclaration_end() {}
|
||||
|
||||
@override
|
||||
Type? promotedPropertyType(Expression? target, String propertyName,
|
||||
Object? propertyMember, Type staticType) =>
|
||||
|
@ -4618,26 +4806,24 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
|
|||
}
|
||||
|
||||
@override
|
||||
void switchStatement_afterGuard(Expression when) {}
|
||||
void switchStatement_beginAlternative() {}
|
||||
|
||||
@override
|
||||
void switchStatement_beginAlternatives() {}
|
||||
|
||||
@override
|
||||
void switchStatement_beginCase() {}
|
||||
|
||||
@override
|
||||
void switchStatement_end(bool isExhaustive) {}
|
||||
|
||||
@override
|
||||
void switchStatement_endAlternative() {}
|
||||
void switchStatement_endAlternative(Expression? guard) {}
|
||||
|
||||
@override
|
||||
void switchStatement_endAlternatives(Statement node,
|
||||
void switchStatement_endAlternatives(Statement? node,
|
||||
{required bool hasLabels}) {}
|
||||
|
||||
@override
|
||||
void switchStatement_expressionEnd(Statement? switchStatement) {}
|
||||
void switchStatement_expressionEnd(
|
||||
Statement? switchStatement, Expression scrutinee) {}
|
||||
|
||||
@override
|
||||
void thisOrSuper(Expression expression, Type staticType) {}
|
||||
|
@ -4672,6 +4858,9 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
|
|||
@override
|
||||
void tryFinallyStatement_finallyBegin(Node body) {}
|
||||
|
||||
@override
|
||||
void variablePattern({required Type matchedType, required Type staticType}) {}
|
||||
|
||||
@override
|
||||
Type? variableRead(Expression expression, Variable variable) {
|
||||
int variableKey = _promotionKeyStore.keyForVariable(variable);
|
||||
|
@ -4830,6 +5019,16 @@ class _NullInfo<Type extends Object> implements ExpressionInfo<Type> {
|
|||
null;
|
||||
}
|
||||
|
||||
/// Base class for [_FlowContext]s representing patterns.
|
||||
abstract class _PatternContext<Type extends Object> extends _FlowContext {
|
||||
/// Flow model representing all code paths accumulated so far in which the
|
||||
/// pattern fails to match.
|
||||
abstract FlowModel<Type> _unmatched;
|
||||
|
||||
/// Reference for the value being matched, if any.
|
||||
ReferenceWithType<Type>? get _scrutineeReference;
|
||||
}
|
||||
|
||||
/// [ReferenceWithType] object representing a property get.
|
||||
class _PropertyReferenceWithType<Type extends Object>
|
||||
extends ReferenceWithType<Type> {
|
||||
|
@ -4888,7 +5087,46 @@ class _SwitchAlternativesContext<Type extends Object> extends _FlowContext {
|
|||
|
||||
FlowModel<Type>? _combinedModel;
|
||||
|
||||
_SwitchAlternativesContext(this._previous);
|
||||
/// Reference for the value being matched.
|
||||
final ReferenceWithType<Type>? _scrutineeReference;
|
||||
|
||||
_SwitchAlternativesContext(this._previous, this._scrutineeReference);
|
||||
|
||||
@override
|
||||
String toString() => '_SwitchAlternativesContext(previous: $_previous, '
|
||||
'combinedModel: $_combinedModel, scrutineeReference: '
|
||||
'$_scrutineeReference)';
|
||||
}
|
||||
|
||||
/// [_FlowContext] representing a switch statement.
|
||||
class _SwitchStatementContext<Type extends Object>
|
||||
extends _SimpleStatementContext<Type> {
|
||||
/// Reference for the value being matched.
|
||||
final ReferenceWithType<Type>? _scrutineeReference;
|
||||
|
||||
_SwitchStatementContext(
|
||||
super.checkpoint, super._previous, this._scrutineeReference);
|
||||
|
||||
@override
|
||||
String toString() => '_SwitchStatementContext(breakModel: $_breakModel, '
|
||||
'continueModel: $_continueModel, previous: $_previous, '
|
||||
'checkpoint: $_checkpoint, scrutineeReference: $_scrutineeReference)';
|
||||
}
|
||||
|
||||
/// [_FlowContext] representing the top level of a pattern syntax tree.
|
||||
class _TopPatternContext<Type extends Object> extends _PatternContext<Type> {
|
||||
@override
|
||||
final ReferenceWithType<Type>? _scrutineeReference;
|
||||
|
||||
@override
|
||||
FlowModel<Type> _unmatched;
|
||||
|
||||
_TopPatternContext(this._scrutineeReference, this._unmatched);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'_TopPatternContext(scrutineeReference: $_scrutineeReference, '
|
||||
'unmatched: $_unmatched)';
|
||||
}
|
||||
|
||||
/// Specialization of [ExpressionInfo] for the case where the information we
|
||||
|
|
|
@ -292,6 +292,7 @@ mixin TypeAnalyzer<
|
|||
errors?.refutablePatternInIrrefutableContext(node, irrefutableContext);
|
||||
}
|
||||
Type staticType = analyzeExpression(expression, matchedType);
|
||||
flow?.constantPattern_end(expression);
|
||||
// Stack: (Expression)
|
||||
if (errors != null && !options.patternsEnabled) {
|
||||
Expression? switchScrutinee = context.getSwitchScrutinee(node);
|
||||
|
@ -364,8 +365,9 @@ mixin TypeAnalyzer<
|
|||
required Object? context,
|
||||
}) {
|
||||
// Stack: ()
|
||||
flow?.ifStatement_conditionBegin();
|
||||
flow?.ifCaseStatement_begin();
|
||||
Type initializerType = analyzeExpression(expression, unknownType);
|
||||
flow?.ifCaseStatement_afterExpression(expression);
|
||||
// Stack: (Expression)
|
||||
// TODO(paulberry): rework handling of isFinal
|
||||
dispatchPattern(
|
||||
|
@ -380,7 +382,7 @@ mixin TypeAnalyzer<
|
|||
handleNoGuard(node, 0);
|
||||
}
|
||||
// Stack: (Expression, Pattern, Guard)
|
||||
flow?.ifStatement_thenBegin(null, node);
|
||||
flow?.ifCaseStatement_thenBegin(guard);
|
||||
_analyzeIfElementCommon(node, ifTrue, ifFalse, context);
|
||||
}
|
||||
|
||||
|
@ -408,8 +410,9 @@ mixin TypeAnalyzer<
|
|||
Map<String, Variable> variables,
|
||||
) {
|
||||
// Stack: ()
|
||||
flow?.ifStatement_conditionBegin();
|
||||
flow?.ifCaseStatement_begin();
|
||||
Type initializerType = analyzeExpression(expression, unknownType);
|
||||
flow?.ifCaseStatement_afterExpression(expression);
|
||||
// Stack: (Expression)
|
||||
// TODO(paulberry): rework handling of isFinal
|
||||
dispatchPattern(
|
||||
|
@ -441,7 +444,7 @@ mixin TypeAnalyzer<
|
|||
handleNoGuard(node, 0);
|
||||
}
|
||||
// Stack: (Expression, Pattern, Guard)
|
||||
flow?.ifStatement_thenBegin(null, node);
|
||||
flow?.ifCaseStatement_thenBegin(guard);
|
||||
_analyzeIfCommon(node, ifTrue, ifFalse);
|
||||
return initializerType;
|
||||
}
|
||||
|
@ -892,6 +895,7 @@ mixin TypeAnalyzer<
|
|||
if (isLate) {
|
||||
flow?.lateInitializer_end();
|
||||
}
|
||||
flow?.patternVariableDeclaration_afterInitializer(initializer);
|
||||
dispatchPattern(
|
||||
initializerType,
|
||||
new MatchContext<Node, Expression, Pattern, Type, Variable>(
|
||||
|
@ -903,6 +907,7 @@ mixin TypeAnalyzer<
|
|||
),
|
||||
pattern,
|
||||
);
|
||||
flow?.patternVariableDeclaration_end();
|
||||
// Stack: (Expression, Pattern)
|
||||
}
|
||||
|
||||
|
@ -1071,14 +1076,16 @@ mixin TypeAnalyzer<
|
|||
Type expressionType = analyzeExpression(scrutinee, unknownType);
|
||||
// Stack: (Expression)
|
||||
handleSwitchScrutinee(expressionType);
|
||||
flow?.switchStatement_expressionEnd(null);
|
||||
flow?.switchStatement_expressionEnd(null, scrutinee);
|
||||
Type? lubType;
|
||||
for (int i = 0; i < numCases; i++) {
|
||||
// Stack: (Expression, i * ExpressionCase)
|
||||
SwitchExpressionMemberInfo<Node, Expression, Variable> memberInfo =
|
||||
getSwitchExpressionMemberInfo(node, i);
|
||||
flow?.switchStatement_beginCase();
|
||||
flow?.switchStatement_beginAlternatives();
|
||||
flow?.switchStatement_beginAlternative();
|
||||
Node? pattern = memberInfo.head.pattern;
|
||||
Expression? guard;
|
||||
if (pattern != null) {
|
||||
dispatchPattern(
|
||||
expressionType,
|
||||
|
@ -1090,12 +1097,11 @@ mixin TypeAnalyzer<
|
|||
pattern,
|
||||
);
|
||||
// Stack: (Expression, i * ExpressionCase, Pattern)
|
||||
Expression? guard = memberInfo.head.guard;
|
||||
guard = memberInfo.head.guard;
|
||||
bool hasGuard = guard != null;
|
||||
if (hasGuard) {
|
||||
_checkGuardType(guard, analyzeExpression(guard, boolType));
|
||||
// Stack: (Expression, i * ExpressionCase, Pattern, Expression)
|
||||
flow?.switchStatement_afterGuard(guard);
|
||||
} else {
|
||||
handleNoGuard(node, i);
|
||||
// Stack: (Expression, i * ExpressionCase, Pattern, Expression)
|
||||
|
@ -1104,6 +1110,8 @@ mixin TypeAnalyzer<
|
|||
} else {
|
||||
handleDefault(node, i);
|
||||
}
|
||||
flow?.switchStatement_endAlternative(guard);
|
||||
flow?.switchStatement_endAlternatives(null, hasLabels: false);
|
||||
// Stack: (Expression, i * ExpressionCase, CaseHead)
|
||||
Type type = analyzeExpression(memberInfo.expression, context);
|
||||
// Stack: (Expression, i * ExpressionCase, CaseHead, Expression)
|
||||
|
@ -1130,12 +1138,11 @@ mixin TypeAnalyzer<
|
|||
Type scrutineeType = analyzeExpression(scrutinee, unknownType);
|
||||
// Stack: (Expression)
|
||||
handleSwitchScrutinee(scrutineeType);
|
||||
flow?.switchStatement_expressionEnd(node);
|
||||
flow?.switchStatement_expressionEnd(node, scrutinee);
|
||||
bool hasDefault = false;
|
||||
bool lastCaseTerminates = true;
|
||||
for (int caseIndex = 0; caseIndex < numCases; caseIndex++) {
|
||||
// Stack: (Expression, numExecutionPaths * StatementCase)
|
||||
flow?.switchStatement_beginCase();
|
||||
flow?.switchStatement_beginAlternatives();
|
||||
// Stack: (Expression, numExecutionPaths * StatementCase,
|
||||
// numHeads * CaseHead)
|
||||
|
@ -1147,6 +1154,8 @@ mixin TypeAnalyzer<
|
|||
CaseHeadOrDefaultInfo<Node, Expression, Variable> head =
|
||||
heads[headIndex];
|
||||
Node? pattern = head.pattern;
|
||||
flow?.switchStatement_beginAlternative();
|
||||
Expression? guard;
|
||||
if (pattern != null) {
|
||||
dispatchPattern(
|
||||
scrutineeType,
|
||||
|
@ -1163,12 +1172,11 @@ mixin TypeAnalyzer<
|
|||
);
|
||||
// Stack: (Expression, numExecutionPaths * StatementCase,
|
||||
// numHeads * CaseHead, Pattern),
|
||||
Expression? guard = head.guard;
|
||||
guard = head.guard;
|
||||
if (guard != null) {
|
||||
_checkGuardType(guard, analyzeExpression(guard, boolType));
|
||||
// Stack: (Expression, numExecutionPaths * StatementCase,
|
||||
// numHeads * CaseHead, Pattern, Expression),
|
||||
flow?.switchStatement_afterGuard(guard);
|
||||
} else {
|
||||
handleNoGuard(node, caseIndex);
|
||||
}
|
||||
|
@ -1179,7 +1187,7 @@ mixin TypeAnalyzer<
|
|||
}
|
||||
// Stack: (Expression, numExecutionPaths * StatementCase,
|
||||
// numHeads * CaseHead),
|
||||
flow?.switchStatement_endAlternative();
|
||||
flow?.switchStatement_endAlternative(guard);
|
||||
}
|
||||
// Stack: (Expression, numExecutionPaths * StatementCase,
|
||||
// numHeads * CaseHead)
|
||||
|
@ -1273,6 +1281,7 @@ mixin TypeAnalyzer<
|
|||
matchedType: matchedType,
|
||||
requiredType: staticType);
|
||||
}
|
||||
flow?.variablePattern(matchedType: matchedType, staticType: staticType);
|
||||
bool isImplicitlyTyped = declaredType == null;
|
||||
if (variable != null) {
|
||||
if (name == null) {
|
||||
|
|
|
@ -6375,6 +6375,269 @@ main() {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('Patterns:', () {
|
||||
group('If-case element:', () {
|
||||
test('guarded', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'int?'),
|
||||
ifCaseElement(
|
||||
expr('Object'),
|
||||
wildcard().when(x.expr.notEq(nullLiteral)),
|
||||
block([
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'int'),
|
||||
]).thenExpr(expr('String')).asCollectionElement,
|
||||
block([
|
||||
checkReachable(true),
|
||||
checkNotPromoted(x),
|
||||
]).thenExpr(expr('String')).asCollectionElement)
|
||||
.inContextElementType('String'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('promotes', () {
|
||||
var x = Var('x');
|
||||
var y = Var('y');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
ifCaseElement(
|
||||
x.expr,
|
||||
y.pattern(type: 'int'),
|
||||
block([
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'int'),
|
||||
]).thenExpr(expr('String')).asCollectionElement,
|
||||
block([
|
||||
checkReachable(true),
|
||||
checkNotPromoted(x),
|
||||
]).thenExpr(expr('String')).asCollectionElement)
|
||||
.inContextElementType('String'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('If-case statement:', () {
|
||||
test('guarded', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'int?'),
|
||||
ifCase(expr('Object'), wildcard().when(x.expr.notEq(nullLiteral)), [
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'int'),
|
||||
], [
|
||||
checkReachable(true),
|
||||
checkNotPromoted(x),
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
test('promotes', () {
|
||||
var x = Var('x');
|
||||
var y = Var('y');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
ifCase(x.expr, y.pattern(type: 'int'), [
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'int'),
|
||||
], [
|
||||
checkReachable(true),
|
||||
checkNotPromoted(x),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
|
||||
test('promotion in both pattern and guard', () {
|
||||
var x = Var('x');
|
||||
var y = Var('y');
|
||||
h.run([
|
||||
declare(x, type: 'int?'),
|
||||
declare(y, type: 'String?'),
|
||||
ifCase(
|
||||
x.expr, wildcard(type: 'int').when(y.expr.notEq(nullLiteral)), [
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'int'),
|
||||
checkPromoted(y, 'String'),
|
||||
], [
|
||||
checkReachable(true),
|
||||
checkNotPromoted(x),
|
||||
checkNotPromoted(y),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('Switch expression:', () {
|
||||
test('guarded', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'int?'),
|
||||
switchExpr(expr('Object'), [
|
||||
wildcard().when(x.expr.notEq(nullLiteral)).thenExpr(block([
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'int'),
|
||||
]).thenExpr(expr('String'))),
|
||||
wildcard().thenExpr(block([
|
||||
checkReachable(true),
|
||||
checkNotPromoted(x),
|
||||
]).thenExpr(expr('String'))),
|
||||
]).stmt,
|
||||
]);
|
||||
});
|
||||
|
||||
test('promotes', () {
|
||||
var x = Var('x');
|
||||
var y = Var('y');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
switchExpr(x.expr, [
|
||||
y.pattern(type: 'int').thenExpr(block([
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'int'),
|
||||
]).thenExpr(expr('String'))),
|
||||
wildcard().thenExpr(block([
|
||||
checkReachable(true),
|
||||
checkNotPromoted(x),
|
||||
]).thenExpr(expr('String'))),
|
||||
]).stmt,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('Switch statement:', () {
|
||||
test('guarded', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'int?'),
|
||||
switch_(
|
||||
expr('Object'),
|
||||
[
|
||||
switchStatementMember([
|
||||
wildcard().when(x.expr.notEq(nullLiteral)).switchCase
|
||||
], [
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'int'),
|
||||
]),
|
||||
switchStatementMember([
|
||||
default_
|
||||
], [
|
||||
checkReachable(true),
|
||||
checkNotPromoted(x),
|
||||
]),
|
||||
],
|
||||
isExhaustive: true),
|
||||
]);
|
||||
});
|
||||
|
||||
test('promotes', () {
|
||||
var x = Var('x');
|
||||
var y = Var('y');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
switch_(
|
||||
x.expr,
|
||||
[
|
||||
switchStatementMember([
|
||||
y.pattern(type: 'int').switchCase
|
||||
], [
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'int'),
|
||||
]),
|
||||
switchStatementMember([
|
||||
default_
|
||||
], [
|
||||
checkReachable(true),
|
||||
checkNotPromoted(x),
|
||||
]),
|
||||
],
|
||||
isExhaustive: true),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('Variable pattern:', () {
|
||||
group('covers matched type:', () {
|
||||
test('without promotion candidate', () {
|
||||
// In `if(<some int> case num x) ...`, the `else` branch should be
|
||||
// unreachable because the type `num` fully covers the type `int`.
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
ifCase(expr('int'), x.pattern(type: 'num'), [
|
||||
checkReachable(true),
|
||||
], [
|
||||
checkReachable(false),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
|
||||
test('with promotion candidate', () {
|
||||
// In `if(x case num y) ...`, the `else` branch should be unreachable
|
||||
// because the type `num` fully covers the type `int`.
|
||||
var x = Var('x');
|
||||
var y = Var('y');
|
||||
h.run([
|
||||
declare(x, type: 'int'),
|
||||
ifCase(x.expr, y.pattern(type: 'num'), [
|
||||
checkReachable(true),
|
||||
checkNotPromoted(x),
|
||||
], [
|
||||
checkReachable(false),
|
||||
checkNotPromoted(x),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group("doesn't cover matched type:", () {
|
||||
test('without promotion candidate', () {
|
||||
// In `if(<some num> case int x) ...`, the `else` branch should be
|
||||
// reachable because the type `int` doesn't fully cover the type
|
||||
// `num`.
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
ifCase(expr('num'), x.pattern(type: 'int'), [
|
||||
checkReachable(true),
|
||||
], [
|
||||
checkReachable(true),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
|
||||
group('with promotion candidate:', () {
|
||||
test('without factor', () {
|
||||
var x = Var('x');
|
||||
var y = Var('y');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
ifCase(x.expr, y.pattern(type: 'int'), [
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'int'),
|
||||
], [
|
||||
checkReachable(true),
|
||||
checkNotPromoted(x),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
|
||||
test('with factor', () {
|
||||
var x = Var('x');
|
||||
var y = Var('y');
|
||||
h.run([
|
||||
declare(x, type: 'int?'),
|
||||
ifCase(x.expr, y.pattern(type: 'Null'), [
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'Null'),
|
||||
], [
|
||||
checkReachable(true),
|
||||
checkPromoted(x, 'int'),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the appropriate matcher for expecting an assertion error to be
|
||||
|
|
|
@ -763,7 +763,9 @@ class MiniAstOperations
|
|||
'double <: int?': false,
|
||||
'double <: String': false,
|
||||
'dynamic <: int': false,
|
||||
'dynamic <: int?': false,
|
||||
'dynamic <: Null': false,
|
||||
'dynamic <: num': false,
|
||||
'dynamic <: Object': false,
|
||||
'int <: bool': false,
|
||||
'int <: double': false,
|
||||
|
@ -783,6 +785,7 @@ class MiniAstOperations
|
|||
'int <: String': false,
|
||||
'int <: ?': true,
|
||||
'int? <: int': false,
|
||||
'int? <: Never': false,
|
||||
'int? <: Null': false,
|
||||
'int? <: num': false,
|
||||
'int? <: num?': true,
|
||||
|
@ -836,6 +839,7 @@ class MiniAstOperations
|
|||
'Never? <: num?': true,
|
||||
'Never? <: Object?': true,
|
||||
'Null <: int?': true,
|
||||
'Object <: FutureOr<Object>': true,
|
||||
'Object <: int': false,
|
||||
'Object <: int?': false,
|
||||
'Object <: List': false,
|
||||
|
@ -857,6 +861,9 @@ class MiniAstOperations
|
|||
'String <: num?': false,
|
||||
'String <: Object': true,
|
||||
'String <: Object?': true,
|
||||
'String <: String?': true,
|
||||
'String? <: Null': false,
|
||||
'String? <: Object': false,
|
||||
'String? <: Object?': true,
|
||||
};
|
||||
|
||||
|
@ -873,15 +880,18 @@ class MiniAstOperations
|
|||
'Object - bool': Type('Object'),
|
||||
'Object - int': Type('Object'),
|
||||
'Object - String': Type('Object'),
|
||||
'int - num': Type('int'),
|
||||
'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? - Null': Type('int'),
|
||||
'int? - String': Type('int?'),
|
||||
'Null - int': Type('Null'),
|
||||
'num - int': Type('num'),
|
||||
'num - num': Type('Never'),
|
||||
'num? - num': Type('Never?'),
|
||||
'num? - int': Type('num?'),
|
||||
'num? - int?': Type('num'),
|
||||
|
|
|
@ -1940,14 +1940,14 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
|
|||
@override
|
||||
DecoratedType? visitSwitchStatement(SwitchStatement node) {
|
||||
_dispatch(node.expression);
|
||||
_flowAnalysis!.switchStatement_expressionEnd(node);
|
||||
_flowAnalysis!.switchStatement_expressionEnd(node, node.expression);
|
||||
var hasDefault = false;
|
||||
for (var member in node.members) {
|
||||
_postDominatedLocals.doScoped(action: () {
|
||||
var hasLabel = member.labels.isNotEmpty;
|
||||
_flowAnalysis!.switchStatement_beginCase();
|
||||
_flowAnalysis!.switchStatement_beginAlternatives();
|
||||
_flowAnalysis!.switchStatement_endAlternative();
|
||||
_flowAnalysis!.switchStatement_beginAlternative();
|
||||
_flowAnalysis!.switchStatement_endAlternative(null);
|
||||
_flowAnalysis!
|
||||
.switchStatement_endAlternatives(node, hasLabels: hasLabel);
|
||||
if (member is SwitchCase) {
|
||||
|
|
Loading…
Reference in a new issue