Skip to content

Commit 4cdb2df

Browse files
stereotype441Commit Queue
authored andcommitted
[flow analysis] Re-work handling of CFE null-aware guards.
The CFE desugars null-aware expressions such as `a?.b` into equivalent `let` expressions involving a temporary guard variable (e.g., `let x = a in x == null ? null : x.b`). This desugaring happens in the body builder, prior to flow analysis. As a result, the expressions and variables passed to flow analysis are those of the desugared `let` expression rather than those of the code the user wrote. So a hack is needed to ensure that flow analysis produces the correct result; the hack involves promoting the temporary guard variable in the code path where it is not null. Prior to this change, the hack was done entirely in the CFE. Instead of making a single pair of calls to `FlowAnalysis.nullAwareAccess_rightBegin` and `FlowAnalysis.nullAwareAccess_end` for every null-aware expression, the CFE made two nested pairs of calls: one to promote the true target of the null-aware access, and the other to promote the temporary guard variable. This had the advantage of keeping the hack entirely in the CFE, but unfortunately it got in the way of fixing dart-lang/language#4344, because it prevented flow analysis from properly recognizing field accesses inside null-aware expressions. This change adds an optional `guardVariable` parameter to `FlowAnalysis_nullAwareAccess_rightBegin`, and shifts the responsibility for promoting the temporary guard variable to the flow analysis engine itself. With this change, the CFE no longer needs to make two nested pairs of calls to `FlowAnalysis.nullAwareAccess_rightBegin` and `FlowAnalysis.nullAwareAccess_end`, and flow analysis now has the necessary information to recognize field accesses inside null-aware expressions. I will perform the actual fix for dart-lang/language#4344 in a follow-up CL. Note that in the future, I would like to change the CFE so that it desugars null-aware accesses during type inference rather than during the body builder; this will allow the expressions and variables passed to flow analysis to be precisely those of the code the user wrote; that in turn will give us extra confidence that flow analysis produces the same results in the analyzer and the CFE. Change-Id: Ie63619a3ca0f011c1ae8e3c0c76b5bc7c1ab8419 Bug: dart-lang/language#4344 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427341 Reviewed-by: Johnni Winther <[email protected]> Reviewed-by: Chloe Stefantsova <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent 94c9dff commit 4cdb2df

File tree

8 files changed

+95
-34
lines changed

8 files changed

+95
-34
lines changed

pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ abstract class FlowAnalysis<
206206
Variable extends Object,
207207
Type extends Object
208208
>
209-
implements FlowAnalysisNullShortingInterface<Expression, Type> {
209+
implements FlowAnalysisNullShortingInterface<Expression, Variable, Type> {
210210
factory FlowAnalysis(
211211
FlowAnalysisOperations<Variable, Type> operations,
212212
AssignedVariables<Node, Variable> assignedVariables, {
@@ -1952,10 +1952,19 @@ class FlowAnalysisDebug<
19521952
}
19531953

19541954
@override
1955-
void nullAwareAccess_rightBegin(Expression? target, Type targetType) {
1955+
void nullAwareAccess_rightBegin(
1956+
Expression? target,
1957+
Type targetType, {
1958+
Variable? guardVariable,
1959+
}) {
19561960
_wrap(
1957-
'nullAwareAccess_rightBegin($target, $targetType)',
1958-
() => _wrapped.nullAwareAccess_rightBegin(target, targetType),
1961+
'nullAwareAccess_rightBegin($target, $targetType, '
1962+
'guardVariable: $guardVariable)',
1963+
() => _wrapped.nullAwareAccess_rightBegin(
1964+
target,
1965+
targetType,
1966+
guardVariable: guardVariable,
1967+
),
19591968
);
19601969
}
19611970

@@ -2493,6 +2502,7 @@ class FlowAnalysisDebug<
24932502
/// relevant to it.
24942503
abstract interface class FlowAnalysisNullShortingInterface<
24952504
Expression extends Object,
2505+
Variable extends Object,
24962506
Type extends Object
24972507
> {
24982508
/// Call this method after visiting an expression using `?.`.
@@ -2508,12 +2518,22 @@ abstract interface class FlowAnalysisNullShortingInterface<
25082518
/// null-aware operator, and should be non-null even if the null-aware access
25092519
/// starts a cascade section.
25102520
///
2521+
/// If the client desugars the null-aware access using a guard variable (e.g.,
2522+
/// if it desugars `a?.b` into `let x = a in x == null ? null : x.b`), it
2523+
/// should pass in the variable used for desugaring as [guardVariable]. Flow
2524+
/// analysis will ensure that this variable is promoted to the appropriate
2525+
/// type in the "not null" code path.
2526+
///
25112527
/// Note that [nullAwareAccess_end] should be called after the conclusion
25122528
/// of any null-shorting that is caused by the `?.`. So, for example, if the
25132529
/// code being analyzed is `x?.y?.z(x)`, [nullAwareAccess_rightBegin] should
25142530
/// be called once upon reaching each `?.`, but [nullAwareAccess_end] should
25152531
/// not be called until after processing the method call to `z(x)`.
2516-
void nullAwareAccess_rightBegin(Expression? target, Type targetType);
2532+
void nullAwareAccess_rightBegin(
2533+
Expression? target,
2534+
Type targetType, {
2535+
Variable? guardVariable,
2536+
});
25172537
}
25182538

25192539
/// An instance of the [FlowModel] class represents the information gathered by
@@ -4793,7 +4813,8 @@ class TrivialVariableReference<Type extends Object> extends _Reference<Type> {
47934813
@override
47944814
String toString() =>
47954815
'TrivialVariableReference(type: $_type, '
4796-
'promotionKey: $promotionKey, isThisOrSuper: $isThisOrSuper)';
4816+
'promotionKey: $promotionKey, isThisOrSuper: $isThisOrSuper, '
4817+
'ssaNode: $ssaNode)';
47974818
}
47984819

47994820
class WhyNotPromotedInfo {}
@@ -5908,7 +5929,11 @@ class _FlowAnalysisImpl<
59085929
}
59095930

59105931
@override
5911-
void nullAwareAccess_rightBegin(Expression? target, Type targetType) {
5932+
void nullAwareAccess_rightBegin(
5933+
Expression? target,
5934+
Type targetType, {
5935+
Variable? guardVariable,
5936+
}) {
59125937
_current = _current.split();
59135938
FlowModel<Type> shortcutControlPath = _current;
59145939
_Reference<Type>? targetReference = _getExpressionReference(target);
@@ -5930,6 +5955,22 @@ class _FlowAnalysisImpl<
59305955
break;
59315956
}
59325957
_stack.add(new _NullAwareAccessContext<Type>(shortcutControlPath));
5958+
if (guardVariable != null) {
5959+
// Promote the guard variable as well.
5960+
int promotionKey = promotionKeyStore.keyForVariable(guardVariable);
5961+
Type nonNullType = operations.promoteToNonNull(targetType);
5962+
_current = _current.updatePromotionInfo(
5963+
this,
5964+
promotionKey,
5965+
new PromotionModel(
5966+
promotedTypes: nonNullType == targetType ? null : [nonNullType],
5967+
tested: const [],
5968+
assigned: true,
5969+
unassigned: false,
5970+
ssaNode: new SsaNode(null),
5971+
),
5972+
);
5973+
}
59335974
}
59345975

59355976
@override

pkg/_fe_analyzer_shared/lib/src/type_inference/null_shorting.dart

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@ import '../flow_analysis/flow_analysis_operations.dart';
1616
/// The type parameter [Guard] should be instantiated with the data structure
1717
/// used by the client to desugar null-aware accesses. (The analyzer can
1818
/// instantiate this with `Null`, since it doesn't do desugaring.)
19-
mixin NullShortingMixin<Guard, Expression extends Object, Type extends Object>
20-
on TypeAnalysisNullShortingInterface<Expression, Type> {
19+
mixin NullShortingMixin<
20+
Guard,
21+
Expression extends Object,
22+
Variable extends Object,
23+
Type extends Object
24+
>
25+
on TypeAnalysisNullShortingInterface<Expression, Variable, Type> {
2126
/// Stack of [Guard] objects associated with null-shorting operations that
2227
/// haven't been terminated yet.
2328
final _guards = <Guard>[];
@@ -63,20 +68,36 @@ mixin NullShortingMixin<Guard, Expression extends Object, Type extends Object>
6368
/// desugar the null-aware access. It will be passed to
6469
/// [handleNullShortingStep] when the null shorting for this particular
6570
/// null-aware expression is terminated.
66-
void startNullShorting(Guard guard, Expression target, Type targetType) {
71+
///
72+
/// If the client desugars the null-aware access using a guard variable (e.g.,
73+
/// if it desugars `a?.b` into `let x = a in x == null ? null : x.b`), it
74+
/// should pass in the variable used for desugaring as [guardVariable]. Flow
75+
/// analysis will ensure that this variable is promoted to the appropriate
76+
/// type in the "not null" code path.
77+
void startNullShorting(
78+
Guard guard,
79+
Expression target,
80+
Type targetType, {
81+
Variable? guardVariable,
82+
}) {
6783
// Ensure the initializer of [_nullAwareVariable] is promoted to
6884
// non-nullable.
69-
flow.nullAwareAccess_rightBegin(target, targetType);
85+
flow.nullAwareAccess_rightBegin(
86+
target,
87+
targetType,
88+
guardVariable: guardVariable,
89+
);
7090
_guards.add(guard);
7191
}
7292
}
7393

7494
abstract interface class TypeAnalysisNullShortingInterface<
7595
Expression extends Object,
96+
Variable extends Object,
7697
Type extends Object
7798
> {
7899
/// Returns the client's [FlowAnalysis] object.
79-
FlowAnalysisNullShortingInterface<Expression, Type> get flow;
100+
FlowAnalysisNullShortingInterface<Expression, Variable, Type> get flow;
80101

81102
/// Returns the number of null-shorting operations that haven't been
82103
/// terminated yet.

pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,12 @@ mixin TypeAnalyzer<
279279
TypeDeclarationType extends Object,
280280
TypeDeclaration extends Object
281281
>
282-
implements TypeAnalysisNullShortingInterface<Expression, SharedTypeView> {
282+
implements
283+
TypeAnalysisNullShortingInterface<
284+
Expression,
285+
Variable,
286+
SharedTypeView
287+
> {
283288
/// Cached context types and their respective dot shorthand nodes.
284289
///
285290
/// The [SharedTypeSchemaView] is used to resolve dot shorthand heads. We

pkg/_fe_analyzer_shared/test/mini_ast.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6245,7 +6245,7 @@ class _MiniAstTypeAnalyzer
62456245
Type,
62466246
String
62476247
>,
6248-
NullShortingMixin<MiniIRTmp, Expression, SharedTypeView> {
6248+
NullShortingMixin<MiniIRTmp, Expression, Var, SharedTypeView> {
62496249
final Harness _harness;
62506250

62516251
@override

pkg/analyzer/lib/src/generated/resolver.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,12 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
134134
InterfaceElementImpl2
135135
>,
136136
// TODO(paulberry): not yet used.
137-
NullShortingMixin<Null, ExpressionImpl, SharedTypeView> {
137+
NullShortingMixin<
138+
Null,
139+
ExpressionImpl,
140+
PromotableElementImpl2,
141+
SharedTypeView
142+
> {
138143
/// Debug-only: if `true`, manipulations of [_rewriteStack] performed by
139144
/// [popRewrite], [pushRewrite], and [replaceExpression] will be printed.
140145
static const bool _debugRewriteStack = false;

pkg/front_end/lib/src/type_inference/inference_results.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,6 @@ class NullAwareGuard {
389389
///
390390
Expression createExpression(
391391
DartType inferredType, Expression nullAwareAction) {
392-
// End non-nullable promotion of the initializer of [_nullAwareVariable].
393-
_inferrer.flowAnalysis.nullAwareAccess_end();
394392
Expression equalsNull = _inferrer.createEqualsNull(
395393
_nullAwareFileOffset, createVariableGet(_nullAwareVariable));
396394

pkg/front_end/lib/src/type_inference/inference_visitor.dart

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase
106106
with
107107
TypeAnalyzer<TreeNode, Statement, Expression, VariableDeclaration,
108108
Pattern, InvalidExpression, TypeDeclarationType, TypeDeclaration>,
109-
NullShortingMixin<NullAwareGuard, Expression, SharedTypeView>,
109+
NullShortingMixin<NullAwareGuard, Expression, VariableDeclaration,
110+
SharedTypeView>,
110111
StackChecker
111112
implements
112113
ExpressionVisitor1<ExpressionInferenceResult, DartType>,
@@ -245,14 +246,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase
245246

246247
void createNullAwareGuard(VariableDeclaration variable) {
247248
startNullShorting(new NullAwareGuard(variable, variable.fileOffset, this),
248-
variable.initializer!, new SharedTypeView(variable.type));
249-
// Ensure [variable] is promoted to non-nullable.
250-
// TODO(johnniwinther): Avoid creating a [VariableGet] to promote the
251-
// variable.
252-
VariableGet read = new VariableGet(variable);
253-
flowAnalysis.variableRead(read, variable);
254-
flowAnalysis.nullAwareAccess_rightBegin(
255-
read, new SharedTypeView(variable.type));
249+
variable.initializer!, new SharedTypeView(variable.type),
250+
guardVariable: variable);
256251
}
257252

258253
@override
@@ -988,14 +983,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase
988983
// Ensure the initializer of [_nullAwareVariable] is promoted to
989984
// non-nullable.
990985
flow.nullAwareAccess_rightBegin(
991-
node.variable.initializer!, new SharedTypeView(node.variable.type));
992-
// Ensure [variable] is promoted to non-nullable.
993-
// TODO(johnniwinther): Avoid creating a [VariableGet] to promote the
994-
// variable.
995-
VariableGet read = new VariableGet(node.variable);
996-
flowAnalysis.variableRead(read, node.variable);
997-
flowAnalysis.nullAwareAccess_rightBegin(
998-
read, new SharedTypeView(node.variable.type));
986+
node.variable.initializer!, new SharedTypeView(node.variable.type),
987+
guardVariable: node.variable);
999988
}
1000989

1001990
Cascade? previousEnclosingCascade = _enclosingCascade;

pkg/front_end/lib/src/type_inference/type_inference_engine.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,8 @@ class OperationsCfe
673673
// use that if it exists.
674674
return new SharedTypeView(variable.lateType ?? variable.type);
675675
}
676+
// TODO(paulberry): see if this code path can be eliminated.
677+
// Coverage-ignore(suite): Not run.
676678
return new SharedTypeView(variable.type);
677679
}
678680

0 commit comments

Comments
 (0)