Skip to content

Commit 659b32f

Browse files
johnniwinthercommit-bot@chromium.org
authored andcommitted
[cfe] Handle chained null shorting and null aware if-null set
Change-Id: I39d2448a957ca7f4f3d1f027e543f773b300ce9b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/126645 Commit-Queue: Johnni Winther <[email protected]> Reviewed-by: Jens Johansen <[email protected]>
1 parent 077795b commit 659b32f

28 files changed

+1023
-769
lines changed

pkg/_fe_analyzer_shared/test/flow_analysis/definite_assignment/data/assignment.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,5 @@ questionEq_rhs_not_guaranteed_to_execute() {
4242
late int v;
4343
int? i;
4444
/*cfe.unassigned*/ i ??= (v = 0);
45-
/*analyzer.unassigned*/ v;
45+
/*unassigned*/ v;
4646
}

pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data/null_aware_access.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,13 @@ void null_aware_cascades_do_not_promote_target(C? c) {
104104
// holds the result of evaluating the cascade target. So
105105
// effectively, no promotion happens (because there is no way to
106106
// observe a change to the type of that variable).
107-
c?..setter = c;
108-
c?..getterSetter += c;
109-
c?..getterSetter ??= c;
110-
c?..[c];
111-
c?..[c] = c;
112-
c?..[c] += c;
113-
c?..[c] ??= c;
107+
c?..setter = /*cfe.nonNullable*/ c;
108+
c?..getterSetter += /*cfe.nonNullable*/ c;
109+
c?..getterSetter ??= /*cfe.nonNullable*/ c;
110+
c?..[/*cfe.nonNullable*/ c];
111+
c?..[/*cfe.nonNullable*/ c] = /*cfe.nonNullable*/ c;
112+
c?..[/*cfe.nonNullable*/ c] += /*cfe.nonNullable*/ c;
113+
c?..[/*cfe.nonNullable*/ c] ??= /*cfe.nonNullable*/ c;
114114
}
115115

116116
void null_aware_cascades_do_not_promote_others(C? c, int? i, int? j) {

pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart

Lines changed: 134 additions & 114 deletions
Large diffs are not rendered by default.

pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart

Lines changed: 90 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import 'dart:core' hide MapEntry;
66

77
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
88

9+
import 'package:_fe_analyzer_shared/src/util/link.dart';
10+
911
import 'package:front_end/src/fasta/kernel/internal_ast.dart';
1012
import 'package:front_end/src/fasta/type_inference/type_demotion.dart';
1113

@@ -502,7 +504,6 @@ class TypeInferrerImpl implements TypeInferrer {
502504
typeSchemaEnvironment = engine.typeSchemaEnvironment,
503505
isTopLevel = topLevel,
504506
typePromoter = new TypePromoter(engine.typeSchemaEnvironment),
505-
// TODO(dmitryas): Pass in the actual assigned variables.
506507
flowAnalysis = new FlowAnalysis(
507508
new TypeOperationsCfe(engine.typeSchemaEnvironment),
508509
assignedVariables);
@@ -1482,6 +1483,29 @@ class TypeInferrerImpl implements TypeInferrer {
14821483
variable.type = inferredType;
14831484
}
14841485

1486+
Link<NullAwareGuard> inferSyntheticVariableNullAware(
1487+
VariableDeclarationImpl variable) {
1488+
assert(variable.isImplicitlyTyped);
1489+
assert(variable.initializer != null);
1490+
ExpressionInferenceResult result = inferExpression(
1491+
variable.initializer, const UnknownType(), true,
1492+
isVoidAllowed: true);
1493+
variable.initializer = result.nullAwareAction..parent = variable;
1494+
DartType inferredType = inferDeclarationType(result.inferredType);
1495+
instrumentation?.record(uriForInstrumentation, variable.fileOffset, 'type',
1496+
new InstrumentationValueForType(inferredType));
1497+
variable.type = inferredType;
1498+
return result.nullAwareGuards;
1499+
}
1500+
1501+
NullAwareGuard createNullAwareGuard(VariableDeclaration variable) {
1502+
Member equalsMember =
1503+
findInterfaceMember(variable.type, equalsName, variable.fileOffset)
1504+
.member;
1505+
return new NullAwareGuard(
1506+
variable, variable.fileOffset, equalsMember, this);
1507+
}
1508+
14851509
/// Performs type inference on the given [expression].
14861510
///
14871511
/// [typeContext] is the expected type of the expression, based on surrounding
@@ -2111,7 +2135,7 @@ class TypeInferrerImpl implements TypeInferrer {
21112135

21122136
ExpressionInferenceResult _inferDynamicInvocation(
21132137
int fileOffset,
2114-
NullAwareGuard nullAwareGuard,
2138+
Link<NullAwareGuard> nullAwareGuards,
21152139
Expression receiver,
21162140
Name name,
21172141
Arguments arguments,
@@ -2124,12 +2148,12 @@ class TypeInferrerImpl implements TypeInferrer {
21242148
inferredType,
21252149
new MethodInvocationImpl(receiver, name, arguments)
21262150
..fileOffset = fileOffset,
2127-
nullAwareGuard);
2151+
nullAwareGuards);
21282152
}
21292153

21302154
ExpressionInferenceResult _inferMissingInvocation(
21312155
int fileOffset,
2132-
NullAwareGuard nullAwareGuard,
2156+
Link<NullAwareGuard> nullAwareGuards,
21332157
Expression receiver,
21342158
DartType receiverType,
21352159
ObjectAccessTarget target,
@@ -2145,12 +2169,13 @@ class TypeInferrerImpl implements TypeInferrer {
21452169
receiverType: receiverType);
21462170
assert(name != equalsName);
21472171
// TODO(johnniwinther): Use InvalidType instead.
2148-
return new ExpressionInferenceResult(const DynamicType(), error);
2172+
return new ExpressionInferenceResult.nullAware(
2173+
const DynamicType(), error, nullAwareGuards);
21492174
}
21502175

21512176
ExpressionInferenceResult _inferExtensionInvocation(
21522177
int fileOffset,
2153-
NullAwareGuard nullAwareGuard,
2178+
Link<NullAwareGuard> nullAwareGuards,
21542179
Expression receiver,
21552180
DartType receiverType,
21562181
ObjectAccessTarget target,
@@ -2183,12 +2208,12 @@ class TypeInferrerImpl implements TypeInferrer {
21832208
typeSchemaEnvironment, helper.uri, getTypeArgumentsInfo(arguments));
21842209
}
21852210
return new ExpressionInferenceResult.nullAware(
2186-
inferredType, staticInvocation, nullAwareGuard);
2211+
inferredType, staticInvocation, nullAwareGuards);
21872212
}
21882213

21892214
ExpressionInferenceResult _inferFunctionInvocation(
21902215
int fileOffset,
2191-
NullAwareGuard nullAwareGuard,
2216+
Link<NullAwareGuard> nullAwareGuards,
21922217
Expression receiver,
21932218
DartType receiverType,
21942219
ObjectAccessTarget target,
@@ -2204,12 +2229,12 @@ class TypeInferrerImpl implements TypeInferrer {
22042229
inferredType,
22052230
new MethodInvocation(receiver, callName, arguments)
22062231
..fileOffset = fileOffset,
2207-
nullAwareGuard);
2232+
nullAwareGuards);
22082233
}
22092234

22102235
ExpressionInferenceResult _inferInstanceMethodInvocation(
22112236
int fileOffset,
2212-
NullAwareGuard nullAwareGuard,
2237+
Link<NullAwareGuard> nullAwareGuards,
22132238
Expression receiver,
22142239
DartType receiverType,
22152240
ObjectAccessTarget target,
@@ -2297,12 +2322,12 @@ class TypeInferrerImpl implements TypeInferrer {
22972322
..fileOffset = fileOffset;
22982323

22992324
return new ExpressionInferenceResult.nullAware(
2300-
inferredType, replacement, nullAwareGuard);
2325+
inferredType, replacement, nullAwareGuards);
23012326
}
23022327

23032328
ExpressionInferenceResult _inferInstanceGetterInvocation(
23042329
int fileOffset,
2305-
NullAwareGuard nullAwareGuard,
2330+
Link<NullAwareGuard> nullAwareGuards,
23062331
Expression receiver,
23072332
DartType receiverType,
23082333
ObjectAccessTarget target,
@@ -2395,12 +2420,12 @@ class TypeInferrerImpl implements TypeInferrer {
23952420
..fileOffset = fileOffset;
23962421

23972422
return new ExpressionInferenceResult.nullAware(
2398-
inferredType, replacement, nullAwareGuard);
2423+
inferredType, replacement, nullAwareGuards);
23992424
}
24002425

24012426
ExpressionInferenceResult _inferInstanceFieldInvocation(
24022427
int fileOffset,
2403-
NullAwareGuard nullAwareGuard,
2428+
Link<NullAwareGuard> nullAwareGuards,
24042429
Expression receiver,
24052430
DartType receiverType,
24062431
ObjectAccessTarget target,
@@ -2470,7 +2495,7 @@ class TypeInferrerImpl implements TypeInferrer {
24702495
..fileOffset = fileOffset;
24712496

24722497
return new ExpressionInferenceResult.nullAware(
2473-
inferredType, replacement, nullAwareGuard);
2498+
inferredType, replacement, nullAwareGuards);
24742499
}
24752500

24762501
/// Performs the core type inference algorithm for method invocations (this
@@ -2481,9 +2506,9 @@ class TypeInferrerImpl implements TypeInferrer {
24812506
ExpressionInferenceResult result =
24822507
inferExpression(node.receiver, const UnknownType(), true);
24832508
Expression receiver;
2484-
NullAwareGuard nullAwareGuard;
2509+
Link<NullAwareGuard> nullAwareGuards;
24852510
if (isNonNullableByDefault) {
2486-
nullAwareGuard = result.nullAwareGuard;
2511+
nullAwareGuards = result.nullAwareGuards;
24872512
receiver = result.nullAwareAction;
24882513
} else {
24892514
receiver = result.expression;
@@ -2499,7 +2524,7 @@ class TypeInferrerImpl implements TypeInferrer {
24992524
if (member.kind == ProcedureKind.Getter) {
25002525
return _inferInstanceGetterInvocation(
25012526
node.fileOffset,
2502-
nullAwareGuard,
2527+
nullAwareGuards,
25032528
receiver,
25042529
receiverType,
25052530
target,
@@ -2509,7 +2534,7 @@ class TypeInferrerImpl implements TypeInferrer {
25092534
} else {
25102535
return _inferInstanceMethodInvocation(
25112536
node.fileOffset,
2512-
nullAwareGuard,
2537+
nullAwareGuards,
25132538
receiver,
25142539
receiverType,
25152540
target,
@@ -2518,18 +2543,18 @@ class TypeInferrerImpl implements TypeInferrer {
25182543
isImplicitCall: node.isImplicitCall);
25192544
}
25202545
} else {
2521-
return _inferInstanceFieldInvocation(node.fileOffset, nullAwareGuard,
2546+
return _inferInstanceFieldInvocation(node.fileOffset, nullAwareGuards,
25222547
receiver, receiverType, target, node.arguments, typeContext,
25232548
isImplicitCall: node.isImplicitCall);
25242549
}
25252550
break;
25262551
case ObjectAccessTargetKind.callFunction:
2527-
return _inferFunctionInvocation(node.fileOffset, nullAwareGuard,
2552+
return _inferFunctionInvocation(node.fileOffset, nullAwareGuards,
25282553
receiver, receiverType, target, node.arguments, typeContext);
25292554
case ObjectAccessTargetKind.extensionMember:
25302555
return _inferExtensionInvocation(
25312556
node.fileOffset,
2532-
nullAwareGuard,
2557+
nullAwareGuards,
25332558
receiver,
25342559
receiverType,
25352560
target,
@@ -2539,7 +2564,7 @@ class TypeInferrerImpl implements TypeInferrer {
25392564
case ObjectAccessTargetKind.missing:
25402565
return _inferMissingInvocation(
25412566
node.fileOffset,
2542-
nullAwareGuard,
2567+
nullAwareGuards,
25432568
receiver,
25442569
receiverType,
25452570
target,
@@ -2550,7 +2575,7 @@ class TypeInferrerImpl implements TypeInferrer {
25502575
case ObjectAccessTargetKind.dynamic:
25512576
case ObjectAccessTargetKind.invalid:
25522577
case ObjectAccessTargetKind.unresolved:
2553-
return _inferDynamicInvocation(node.fileOffset, nullAwareGuard,
2578+
return _inferDynamicInvocation(node.fileOffset, nullAwareGuards,
25542579
receiver, node.name, node.arguments, typeContext);
25552580
}
25562581
return unhandled('$target', 'inferMethodInvocation', node.fileOffset,
@@ -3256,10 +3281,10 @@ class ExpressionInferenceResult {
32563281

32573282
factory ExpressionInferenceResult.nullAware(
32583283
DartType inferredType, Expression expression,
3259-
[NullAwareGuard nullAwareGuard]) {
3260-
if (nullAwareGuard != null) {
3284+
[Link<NullAwareGuard> nullAwareGuards = const Link<NullAwareGuard>()]) {
3285+
if (nullAwareGuards != null && nullAwareGuards.isNotEmpty) {
32613286
return new NullAwareExpressionInferenceResult(
3262-
inferredType, nullAwareGuard, expression);
3287+
inferredType, nullAwareGuards, expression);
32633288
} else {
32643289
return new ExpressionInferenceResult(inferredType, expression);
32653290
}
@@ -3268,13 +3293,13 @@ class ExpressionInferenceResult {
32683293
ExpressionInferenceResult(this.inferredType, this.expression)
32693294
: assert(expression != null);
32703295

3271-
/// The guard used for null-aware access if the expression is part of a
3272-
/// null-shorting, and `null` otherwise.
3273-
NullAwareGuard get nullAwareGuard => null;
3296+
/// The guards used for null-aware access if the expression is part of a
3297+
/// null-shorting.
3298+
Link<NullAwareGuard> get nullAwareGuards => const Link<NullAwareGuard>();
32743299

3275-
/// If the expression is part of a null-shorting, the action performed on
3276-
/// the variable in [nullAwareGuard]. Otherwise, this is the same as
3277-
/// [expression].
3300+
/// If the expression is part of a null-shorting, this is the action performed
3301+
/// on the guarded variable, found as the first guard in [nullAwareGuards].
3302+
/// Otherwise, this is the same as [expression].
32783303
Expression get nullAwareAction => expression;
32793304

32803305
String toString() => 'ExpressionInferenceResult($inferredType,$expression)';
@@ -3291,11 +3316,25 @@ class NullAwareGuard {
32913316
/// The [Member] used for the == call.
32923317
final Member _nullAwareEquals;
32933318

3294-
NullAwareGuard(
3295-
this._nullAwareVariable, this._nullAwareFileOffset, this._nullAwareEquals)
3319+
final TypeInferrerImpl _inferrer;
3320+
3321+
NullAwareGuard(this._nullAwareVariable, this._nullAwareFileOffset,
3322+
this._nullAwareEquals, this._inferrer)
32963323
: assert(_nullAwareVariable != null),
32973324
assert(_nullAwareFileOffset != null),
3298-
assert(_nullAwareEquals != null);
3325+
assert(_nullAwareEquals != null),
3326+
assert(_inferrer != null) {
3327+
// Ensure the initializer of [_nullAwareVariable] is promoted to
3328+
// non-nullable.
3329+
_inferrer.flowAnalysis
3330+
.nullAwareAccess_rightBegin(_nullAwareVariable.initializer);
3331+
// Ensure [_nullAwareVariable] is promoted to non-nullable.
3332+
// TODO(johnniwinther): Avoid creating a [VariableGet] to promote the
3333+
// variable.
3334+
VariableGet read = new VariableGet(_nullAwareVariable);
3335+
_inferrer.flowAnalysis.variableRead(read, _nullAwareVariable);
3336+
_inferrer.flowAnalysis.nullAwareAccess_rightBegin(read);
3337+
}
32993338

33003339
/// Creates the null-guarded application of [nullAwareAction] with the
33013340
/// [inferredType].
@@ -3307,6 +3346,10 @@ class NullAwareGuard {
33073346
///
33083347
Expression createExpression(
33093348
DartType inferredType, Expression nullAwareAction) {
3349+
// End non-nullable promotion of [_nullAwareVariable].
3350+
_inferrer.flowAnalysis.nullAwareAccess_end();
3351+
// End non-nullable promotion of the initializer of [_nullAwareVariable].
3352+
_inferrer.flowAnalysis.nullAwareAccess_end();
33103353
MethodInvocation equalsNull = createEqualsNull(_nullAwareFileOffset,
33113354
createVariableGet(_nullAwareVariable), _nullAwareEquals);
33123355
ConditionalExpression condition = new ConditionalExpression(
@@ -3330,28 +3373,33 @@ class NullAwareExpressionInferenceResult implements ExpressionInferenceResult {
33303373
final DartType inferredType;
33313374

33323375
@override
3333-
final NullAwareGuard nullAwareGuard;
3376+
final Link<NullAwareGuard> nullAwareGuards;
33343377

33353378
@override
33363379
final Expression nullAwareAction;
33373380

33383381
Expression _expression;
33393382

33403383
NullAwareExpressionInferenceResult(
3341-
this.inferredType, this.nullAwareGuard, this.nullAwareAction)
3342-
: assert(nullAwareGuard != null),
3384+
this.inferredType, this.nullAwareGuards, this.nullAwareAction)
3385+
: assert(nullAwareGuards.isNotEmpty),
33433386
assert(nullAwareAction != null);
33443387

33453388
Expression get expression {
33463389
if (_expression == null) {
3347-
_expression ??=
3348-
nullAwareGuard.createExpression(inferredType, nullAwareAction);
3390+
_expression = nullAwareAction;
3391+
Link<NullAwareGuard> nullAwareGuard = nullAwareGuards;
3392+
while (nullAwareGuard.isNotEmpty) {
3393+
_expression =
3394+
nullAwareGuard.head.createExpression(inferredType, _expression);
3395+
nullAwareGuard = nullAwareGuard.tail;
3396+
}
33493397
}
33503398
return _expression;
33513399
}
33523400

33533401
String toString() =>
3354-
'NullAwareExpressionInferenceResult($inferredType,$nullAwareGuard,'
3402+
'NullAwareExpressionInferenceResult($inferredType,$nullAwareGuards,'
33553403
'$nullAwareAction)';
33563404
}
33573405

pkg/front_end/test/id_tests/nullability_test.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ main(List<String> args) async {
2222
const NullabilityDataComputer(), [cfeNonNullableOnlyConfig]),
2323
skipList: [
2424
// TODO(johnniwinther): Run all nullability tests.
25-
'null_aware_access.dart',
2625
'try_finally.dart',
2726
'while.dart',
2827
]);

pkg/front_end/test/id_tests/type_promotion_test.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@ main(List<String> args) async {
2424
const TypePromotionDataComputer(), [cfeNonNullableOnlyConfig]),
2525
skipList: [
2626
// TODO(johnniwinther): Run all type promotion tests.
27-
'assignment_promoted.dart',
2827
'bug39178.dart',
2928
'constructor_initializer.dart',
3029
'for.dart',
3130
'not_promoted.dart',
32-
'null_aware_assignment.dart',
3331
'switch.dart',
3432
'try_finally.dart',
3533
'type_parameter.dart',

0 commit comments

Comments
 (0)