[flow analysis] Rework _EqualityCheckResult using patterns.
The class `_EqualityCheckResult`, and its subtypes, were introduced
prior to support for patterns. This change makes
`_EqualityCheckResult` into a sealed class and changes the logic that
consumes it to use `switch` statements rather than `is` tests. The
resulting logic is equivalent, but I believe it's easier to read.
This paves the way for adding a new kind of `_EqualityCheckResult`,
which I'll need to do to support the new `sound-flow-analysis`
feature. (The new kind of `_EqualityCheckResult` will be used in the
circumstance where the two operands of an equality comparison are
guaranteed to be unequal because of their types).
Bug: https://github.com/dart-lang/sdk/issues/60438
Change-Id: I217da37847beff5a43bddb5ed734cc3e4c74970a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/420184
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index 83b2ce8..d6d5f44 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -4290,7 +4290,7 @@
/// Result of performing equality check. This class is used as the return value
/// for [_FlowAnalysisImpl._equalityCheck].
-abstract class _EqualityCheckResult {
+sealed class _EqualityCheckResult {
const _EqualityCheckResult._();
}
@@ -4686,30 +4686,33 @@
// information about legacy operands. But since we are currently in full
// (post null safety) flow analysis logic, we can safely assume that they
// are not null.
- _EqualityCheckResult equalityCheckResult = _equalityCheck(
- leftOperandInfo, leftOperandType, rightOperandInfo, rightOperandType);
- if (equalityCheckResult is _GuaranteedEqual) {
- // Both operands are known by flow analysis to compare equal, so the whole
- // expression behaves equivalently to a boolean (either `true` or `false`
- // depending whether the check uses the `!=` operator).
- booleanLiteral(wholeExpression, !notEqual);
- } else if (equalityCheckResult is _EqualityCheckIsNullCheck<Type>) {
- _Reference<Type>? reference = equalityCheckResult.reference;
- if (reference == null) {
- // One side of the equality check is `null`, but the other side is not a
- // promotable reference. So there's no promotion to do.
- return;
- }
- // The equality check is a null check of something potentially promotable
- // (e.g. a local variable). Record the necessary information so that if
- // this null check winds up being used for a conditional branch, the
- // variable's will be promoted on the appropriate code path.
- ExpressionInfo<Type> equalityInfo =
- _current.tryMarkNonNullable(this, reference);
- _storeExpressionInfo(
- wholeExpression, notEqual ? equalityInfo : equalityInfo._invert());
- } else {
- assert(equalityCheckResult is _NoEqualityInformation);
+ switch (_equalityCheck(
+ leftOperandInfo, leftOperandType, rightOperandInfo, rightOperandType)) {
+ case _GuaranteedEqual():
+ // Both operands are known by flow analysis to compare equal, so the
+ // whole expression behaves equivalently to a boolean (either `true` or
+ // `false` depending whether the check uses the `!=` operator).
+ booleanLiteral(wholeExpression, !notEqual);
+
+ // SAFETY: we can assume `reference` is a `_Reference<Type>` because we
+ // require clients not to mix data obtained from different
+ // instantiations of `FlowAnalysis`.
+ case _EqualityCheckIsNullCheck(:var reference as _Reference<Type>?):
+ if (reference == null) {
+ // One side of the equality check is `null`, but the other side is not
+ // a promotable reference. So there's no promotion to do.
+ return;
+ }
+ // The equality check is a null check of something potentially
+ // promotable (e.g. a local variable). Record the necessary information
+ // so that if this null check winds up being used for a conditional
+ // branch, the variable's will be promoted on the appropriate code path.
+ ExpressionInfo<Type> equalityInfo =
+ _current.tryMarkNonNullable(this, reference);
+ _storeExpressionInfo(
+ wholeExpression, notEqual ? equalityInfo : equalityInfo._invert());
+
+ case _NoEqualityInformation():
// Since flow analysis can't garner any information from this equality
// check, nothing needs to be done; by not calling `_storeExpressionInfo`,
// we ensure that if `_getExpressionInfo` is later called on this
@@ -5958,69 +5961,68 @@
_Reference<Type> newReference = context
.createReference(matchedValueType, _current)
.addPreviousInfo(context._matchedValueInfo, this, _current);
- _EqualityCheckResult equalityCheckResult = _equalityCheck(newReference,
- matchedValueType, _getExpressionInfo(operand), operandType);
- if (equalityCheckResult is _NoEqualityInformation) {
- // We have no information so we have to assume the pattern might or
- // might not match.
- _unmatched = _join(_unmatched!, _current);
- } else if (equalityCheckResult is _EqualityCheckIsNullCheck<Type>) {
- FlowModel<Type>? ifNotNull;
- if (!equalityCheckResult.isReferenceOnRight) {
- // The `null` literal is on the right hand side of the implicit
- // equality check, meaning it is the constant value. So the user is
- // doing something like this:
- //
- // if (v case == null) { ... }
- //
- // So we want to promote the type of `v` in the case where the
- // constant pattern *didn't* match.
- ifNotNull = _nullCheckPattern(matchedValueType: matchedValueType);
- if (ifNotNull == null) {
- // `_nullCheckPattern` returns `null` in the case where the matched
- // value type is non-nullable. In fully sound programs, this would
- // mean that the pattern cannot possibly match. However, in mixed
- // mode programs it might match due to unsoundness. Since we don't
- // want type inference results to change when a program becomes
- // fully sound, we have to assume that we're in mixed mode, and thus
- // the pattern might match.
+ switch (_equalityCheck(newReference, matchedValueType,
+ _getExpressionInfo(operand), operandType)) {
+ case _NoEqualityInformation():
+ // We have no information so we have to assume the pattern might or
+ // might not match.
+ _unmatched = _join(_unmatched!, _current);
+ case _EqualityCheckIsNullCheck<Object>(:var isReferenceOnRight):
+ FlowModel<Type>? ifNotNull;
+ if (!isReferenceOnRight) {
+ // The `null` literal is on the right hand side of the implicit
+ // equality check, meaning it is the constant value. So the user is
+ // doing something like this:
+ //
+ // if (v case == null) { ... }
+ //
+ // So we want to promote the type of `v` in the case where the
+ // constant pattern *didn't* match.
+ ifNotNull = _nullCheckPattern(matchedValueType: matchedValueType);
+ if (ifNotNull == null) {
+ // `_nullCheckPattern` returns `null` in the case where the matched
+ // value type is non-nullable. In fully sound programs, this would
+ // mean that the pattern cannot possibly match. However, in mixed
+ // mode programs it might match due to unsoundness. Since we don't
+ // want type inference results to change when a program becomes
+ // fully sound, we have to assume that we're in mixed mode, and thus
+ // the pattern might match.
+ ifNotNull = _current;
+ }
+ } else {
+ // The `null` literal is on the left hand side of the implicit
+ // equality check, meaning it is the scrutinee. So the user is doing
+ // something silly like this:
+ //
+ // if (null case == c) { ... }
+ //
+ // (where `c` is some constant). There's no variable to promote.
+ //
+ // Since flow analysis can't make use of the results of constant
+ // evaluation, we can't really assume anything; as far as we know, the
+ // pattern might or might not match.
ifNotNull = _current;
}
- } else {
- // The `null` literal is on the left hand side of the implicit
- // equality check, meaning it is the scrutinee. So the user is doing
- // something silly like this:
- //
- // if (null case == c) { ... }
- //
- // (where `c` is some constant). There's no variable to promote.
- //
- // Since flow analysis can't make use of the results of constant
- // evaluation, we can't really assume anything; as far as we know, the
- // pattern might or might not match.
- ifNotNull = _current;
- }
- if (notEqual) {
- _unmatched = _join(_unmatched!, _current);
- _current = ifNotNull;
- } else {
- _unmatched = _join(_unmatched!, ifNotNull);
- }
- } else {
- assert(equalityCheckResult is _GuaranteedEqual);
- if (notEqual) {
- // Both operands are known by flow analysis to compare equal, so the
- // constant pattern is guaranteed *not* to match.
- _unmatched = _join(_unmatched!, _current);
- _current = _current.setUnreachable();
- } else {
- // Both operands are known by flow analysis to compare equal, so the
- // constant pattern is guaranteed to match. Since our approach to
- // handling patterns in flow analysis uses "implicit and" semantics
- // (initially assuming that the pattern always matches, and then
- // updating the `_current` and `_unmatched` states to reflect what
- // values the pattern rejects), we don't have to do any updates.
- }
+ if (notEqual) {
+ _unmatched = _join(_unmatched!, _current);
+ _current = ifNotNull;
+ } else {
+ _unmatched = _join(_unmatched!, ifNotNull);
+ }
+ case _GuaranteedEqual():
+ if (notEqual) {
+ // Both operands are known by flow analysis to compare equal, so the
+ // constant pattern is guaranteed *not* to match.
+ _unmatched = _join(_unmatched!, _current);
+ _current = _current.setUnreachable();
+ } else {
+ // Both operands are known by flow analysis to compare equal, so the
+ // constant pattern is guaranteed to match. Since our approach to
+ // handling patterns in flow analysis uses "implicit and" semantics
+ // (initially assuming that the pattern always matches, and then
+ // updating the `_current` and `_unmatched` states to reflect what
+ // values the pattern rejects), we don't have to do any updates.
+ }
}
}