Skip to content

Commit ca9671f

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Migration: add isUpstreamTriggered getter
It would have been very useful in helping to track down #38341 (comment) to have this information surfaced through the instrumentation output. Specifically, it would be nice if the user could hover over a type annotation that was *not* made nullable due to a propagationStep whose `reason` was `StateChangeReason.upstream`, and see information about all the edges for which the `sourceNode` corresponds to that type and `isUpstreamTriggered` returns `true`. These are, in effect, the edges that prevented the type from being marked nullable. (Note that there's another possible reason a type might not be marked nullable. It might be that there are no edges for which the `destinationNode` corresponds to that type and `isTriggered` returns `true`. In this case, the node is indeterminate, so the migration engine left it as non-nullable because there was no need to make it nullable). Change-Id: I9b969201b813496d41f3a373454c2659f683178d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/121941 Reviewed-by: Samuel Rawlins <srawlins@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
1 parent 7eeaead commit ca9671f

File tree

3 files changed

+94
-1
lines changed

3 files changed

+94
-1
lines changed

pkg/nnbd_migration/lib/instrumentation.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ abstract class EdgeInfo implements FixReasonInfo {
8787
/// The [isHard] property is always true for union edges.
8888
bool get isUnion;
8989

90+
/// Indicates whether the downstream node of this edge is non-nullable and the
91+
/// edge is hard (and thus upstream nullability propagation should try to make
92+
/// the source node non-nullable, if possible).
93+
bool get isUpstreamTriggered;
94+
9095
/// Information about the graph node that this edge "points away from".
9196
NullabilityNodeInfo get sourceNode;
9297
}

pkg/nnbd_migration/lib/src/nullability_node.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ class NullabilityEdge implements EdgeInfo {
4949
@override
5050
bool get isUnion => _kind == _NullabilityEdgeKind.union;
5151

52+
@override
53+
bool get isUpstreamTriggered {
54+
if (!isHard) return false;
55+
if (destinationNode._state != NullabilityState.nonNullable) return false;
56+
return true;
57+
}
58+
5259
@override
5360
NullabilityNode get sourceNode => upstreamNodes.first;
5461

@@ -277,6 +284,9 @@ class NullabilityGraph {
277284
_pendingEdges.addAll(never._upstreamEdges);
278285
while (_pendingEdges.isNotEmpty) {
279286
var edge = _pendingEdges.removeLast();
287+
// We only propagate for nodes that are "upstream triggered". At this
288+
// point of propagation, a node is upstream triggered if it is hard.
289+
assert(edge.isUpstreamTriggered == edge.isHard);
280290
if (!edge.isHard) continue;
281291
var node = edge.sourceNode;
282292
if (node is NullabilityNodeMutable &&

pkg/nnbd_migration/test/instrumentation_test.dart

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,70 @@ main() {
284284
expect(kToL.isSatisfied, true);
285285
}
286286

287+
test_graphEdge_isUpstreamTriggered() async {
288+
await analyze('''
289+
void f(int i, bool b) {
290+
assert(i != null);
291+
i.isEven; // unconditional
292+
g(i);
293+
h(i);
294+
if (b) {
295+
i.isEven; // conditional
296+
}
297+
}
298+
void g(int/*?*/ j) {}
299+
void h(int k) {}
300+
''');
301+
var iNode = explicitTypeNullability[findNode.typeAnnotation('int i')];
302+
var jNode = explicitTypeNullability[findNode.typeAnnotation('int/*?*/ j')];
303+
var kNode = explicitTypeNullability[findNode.typeAnnotation('int k')];
304+
var assertNode = findNode.statement('assert');
305+
var unconditionalUsageNode = findNode.simple('i.isEven; // unconditional');
306+
var conditionalUsageNode = findNode.simple('i.isEven; // conditional');
307+
var nonNullEdges = edgeOrigin.entries
308+
.where((entry) =>
309+
entry.key.sourceNode == iNode && entry.key.destinationNode == never)
310+
.toList();
311+
var assertEdge = nonNullEdges
312+
.where((entry) => entry.value.node == assertNode)
313+
.single
314+
.key;
315+
var unconditionalUsageEdge = edgeOrigin.entries
316+
.where((entry) => entry.value.node == unconditionalUsageNode)
317+
.single
318+
.key;
319+
var gCallEdge = edges
320+
.where((e) => e.sourceNode == iNode && e.destinationNode == jNode)
321+
.single;
322+
var hCallEdge = edges
323+
.where((e) => e.sourceNode == iNode && e.destinationNode == kNode)
324+
.single;
325+
var conditionalUsageEdge = edgeOrigin.entries
326+
.where((entry) => entry.value.node == conditionalUsageNode)
327+
.single
328+
.key;
329+
// Both assertEdge and unconditionalUsageEdge are upstream triggered because
330+
// either of them would have been sufficient to cause i to be marked as
331+
// non-nullable, even though only one of them was actually reported to have
332+
// done so via propagationStep.
333+
expect(propagationSteps.where((s) => s.node == iNode), hasLength(1));
334+
expect(assertEdge.isUpstreamTriggered, true);
335+
expect(unconditionalUsageEdge.isUpstreamTriggered, true);
336+
// conditionalUsageEdge is not upstream triggered because it is a soft edge,
337+
// so it would not have caused i to be marked as non-nullable.
338+
expect(conditionalUsageEdge.isUpstreamTriggered, false);
339+
// Even though gCallEdge is a hard edge, it is not upstream triggered
340+
// because its destination node is nullable.
341+
expect(gCallEdge.isHard, true);
342+
expect(gCallEdge.isUpstreamTriggered, false);
343+
// Even though hCallEdge is a hard edge and its destination node is
344+
// non-nullable, it is not upstream triggered because k could have been made
345+
// nullable without causing any problems, so the presence of this edge would
346+
// not have caused i to be marked as non-nullable.
347+
expect(hCallEdge.isHard, true);
348+
expect(hCallEdge.isUpstreamTriggered, false);
349+
}
350+
287351
test_graphEdge_origin() async {
288352
await analyze('''
289353
int f(int x) => x;
@@ -817,7 +881,7 @@ List<Object> f(List l) => l;
817881
hasLength(1));
818882
}
819883

820-
test_propagationStep() async {
884+
test_propagationStep_downstream() async {
821885
await analyze('''
822886
int x = null;
823887
''');
@@ -829,6 +893,20 @@ int x = null;
829893
expect(step.edge.destinationNode, xNode);
830894
}
831895

896+
test_propagationStep_upstream() async {
897+
await analyze('''
898+
void f(int x) {
899+
assert(x != null);
900+
}
901+
''');
902+
var xNode = explicitTypeNullability[findNode.typeAnnotation('int')];
903+
var step = propagationSteps.where((s) => s.node == xNode).single;
904+
expect(step.newState, NullabilityState.nonNullable);
905+
expect(step.reason, StateChangeReason.upstream);
906+
expect(step.edge.sourceNode, xNode);
907+
expect(step.edge.destinationNode, never);
908+
}
909+
832910
test_substitutionNode() async {
833911
await analyze('''
834912
class C<T> {

0 commit comments

Comments
 (0)