Flow analysis: clarify behavior of ifNullExpression_rightBegin.
Previously, it was unclear what information was stored in
_IfNullExpressionContext._previous. It turned out that it was the
"shortcut state", i.e. the flow state if the LHS of the `??` was
non-`null`. I've renamed variables and added comments to clarify
this. Also, based on the implementation of
`ifNullExpression_rightBegin`, it looked like it might update
`_current`, but in fact it did not (because `tryMarkNonNullable`
always returns an `ExpressionInfo` whose `ifFalse` is unchanged); I've
refactored the code so that this is clearer, and added a comment
explaining why it is the right thing to do.
Bug: https://github.com/dart-lang/language/issues/2020
Change-Id: Ib8a5235f9b9482f4b290ad90686385514660a95d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251142
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@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 57df318..6a287a8 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
@@ -3734,7 +3734,7 @@
void ifNullExpression_end() {
_IfNullExpressionContext<Type> context =
_stack.removeLast() as _IfNullExpressionContext<Type>;
- _current = _merge(_current, context._previous);
+ _current = _merge(_current, context._shortcutState);
}
@override
@@ -3742,17 +3742,30 @@
Expression leftHandSide, Type leftHandSideType) {
ReferenceWithType<Type>? lhsReference =
_getExpressionReference(leftHandSide);
- FlowModel<Type> promoted;
+ FlowModel<Type> shortcutState;
_current = _current.split();
if (lhsReference != null) {
- ExpressionInfo<Type> promotionInfo = _current.tryMarkNonNullable(
- operations, lhsReference, _promotionKeyStore);
- _current = promotionInfo.ifFalse;
- promoted = promotionInfo.ifTrue;
+ shortcutState = _current
+ .tryMarkNonNullable(operations, lhsReference, _promotionKeyStore)
+ .ifTrue;
} else {
- promoted = _current;
+ shortcutState = _current;
}
- _stack.add(new _IfNullExpressionContext<Type>(promoted));
+ _stack.add(new _IfNullExpressionContext<Type>(shortcutState));
+ // Note: we are now on the RHS of the `??`, and so at this point in the
+ // flow, it is known that the LHS evaluated to `null`. It's tempting to
+ // update `_current` to reflect this (either promoting the type of the LHS,
+ // if it's a variable reference, or marking the flow as unreachable, if the
+ // LHS had a non-nullable static type). However:
+ // - In the case where the LHS was a variable reference, we can't promote
+ // it, because we don't promote to `Null` (see
+ // https://github.com/dart-lang/language/issues/1505#issuecomment-975706918)
+ // - In the case where the LHS had a non-nullable static type, it still
+ // might have been `null` due to mixed-mode unsoundness, so we can't mark
+ // the flow as unreachable without allowing the unsoundness to escalate
+ // (see https://github.com/dart-lang/language/issues/1143)
+ //
+ // So we just leave `_current` as is.
}
@override
@@ -4372,12 +4385,15 @@
}
/// [_FlowContext] representing an "if-null" (`??`) expression.
-class _IfNullExpressionContext<Type extends Object>
- extends _SimpleContext<Type> {
- _IfNullExpressionContext(super.previous);
+class _IfNullExpressionContext<Type extends Object> extends _FlowContext {
+ /// The state if the operation short-cuts (i.e. if the expression before the
+ /// `??` was non-`null`).
+ final FlowModel<Type> _shortcutState;
+
+ _IfNullExpressionContext(this._shortcutState);
@override
- String toString() => '_IfNullExpressionContext(previous: $_previous)';
+ String toString() => '_IfNullExpressionContext($_shortcutState)';
}
/// Contextual information tracked by legacy type promotion about a binary "and"
@@ -5002,9 +5018,7 @@
abstract class _SimpleContext<Type extends Object> extends _FlowContext {
/// The stored state. For a `try` statement, this is the state from the
/// beginning of the `try` block. For a function expression, this is the
- /// state at the point the function expression was created. For an "if-null"
- /// expression, this is the state after execution of the expression before the
- /// `??`.
+ /// state at the point the function expression was created.
final FlowModel<Type> _previous;
_SimpleContext(this._previous);
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 48d0dcd..3ce420a 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -290,6 +290,7 @@
customizable
customized
cut
+cuts
cwd
cyclical
cyclically
@@ -437,6 +438,7 @@
erase
erased
es
+escalate
establish
established
estimate