Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit c3a0bb0

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Migration: Add "why not nullable" traces where "!"s are inserted.
Change-Id: I0367f869d59da0397589df7df43170f1ecbcb69c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/139401 Reviewed-by: Mike Fairhurst <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent 2830955 commit c3a0bb0

File tree

5 files changed

+119
-16
lines changed

5 files changed

+119
-16
lines changed

pkg/analysis_server/lib/src/edit/nnbd_migration/info_builder.dart

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,27 @@ class InfoBuilder {
514514
}).toList();
515515
}
516516

517-
void _computeTraceInfo(NullabilityNodeInfo node, List<TraceInfo> traces) {
517+
void _computeTraceNonNullableInfo(
518+
NullabilityNodeInfo node, List<TraceInfo> traces) {
519+
List<TraceEntryInfo> entries = [];
520+
var step = node.whyNotNullable;
521+
if (step == null) {
522+
return;
523+
}
524+
assert(identical(step.node, node));
525+
while (step != null) {
526+
entries.add(_nodeToTraceEntry(step.node));
527+
if (step.codeReference != null) {
528+
entries.add(_stepToTraceEntry(step));
529+
}
530+
step = step.principalCause;
531+
}
532+
var description = 'Non-nullability reason';
533+
traces.add(TraceInfo(description, entries));
534+
}
535+
536+
void _computeTraceNullableInfo(
537+
NullabilityNodeInfo node, List<TraceInfo> traces) {
518538
List<TraceEntryInfo> entries = [];
519539
var step = node.whyNullable;
520540
if (step == null) {
@@ -537,12 +557,13 @@ class InfoBuilder {
537557
for (var reason in fixReasons) {
538558
if (reason is NullabilityNodeInfo) {
539559
if (reason.isNullable) {
540-
_computeTraceInfo(reason, traces);
560+
_computeTraceNullableInfo(reason, traces);
541561
}
542562
} else if (reason is EdgeInfo) {
543563
assert(reason.sourceNode.isNullable);
544564
assert(!reason.destinationNode.isNullable);
545-
_computeTraceInfo(reason.sourceNode, traces);
565+
_computeTraceNullableInfo(reason.sourceNode, traces);
566+
_computeTraceNonNullableInfo(reason.destinationNode, traces);
546567
} else {
547568
assert(false, 'Unrecognized reason type: ${reason.runtimeType}');
548569
}

pkg/analysis_server/lib/src/edit/nnbd_migration/migration_info.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class NavigationTarget extends NavigationRegion {
123123
}
124124

125125
@override
126-
String toString() => 'NavigationTarget["$filePath", $offset, $length]';
126+
String toString() => 'NavigationTarget["$filePath", $line, $offset, $length]';
127127
}
128128

129129
/// An additional detail related to a region.

pkg/analysis_server/test/src/edit/nnbd_migration/info_builder_test.dart

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,6 +1856,58 @@ void h() {
18561856
assertTraceEntry(unit, entries[1], 'f', unit.content.indexOf('int?'));
18571857
}
18581858

1859+
Future<void> test_trace_nullCheck_notNullableReason() async {
1860+
UnitInfo unit = await buildInfoForSingleTestFile('''
1861+
void f(int i) { // f
1862+
assert(i != null);
1863+
}
1864+
void g(int i) { // g
1865+
f(i); // call f
1866+
}
1867+
void h(int/*?*/ i) {
1868+
g(i);
1869+
}
1870+
''', migratedContent: '''
1871+
void f(int i) { // f
1872+
assert(i != null);
1873+
}
1874+
void g(int i) { // g
1875+
f(i); // call f
1876+
}
1877+
void h(int?/*?*/ i) {
1878+
g(i!);
1879+
}
1880+
''');
1881+
var region = unit.regions
1882+
.where((regionInfo) => regionInfo.offset == unit.content.indexOf('!)'))
1883+
.single;
1884+
expect(region.traces, hasLength(2));
1885+
// Trace 0 is the nullability reason; we don't care about that right now.
1886+
// Trace 1 is the non-nullability reason.
1887+
var trace = region.traces[1];
1888+
expect(trace.description, 'Non-nullability reason');
1889+
var entries = trace.entries;
1890+
expect(entries, hasLength(5));
1891+
// Entry 0 is the nullability of g's argument
1892+
assertTraceEntry(
1893+
unit, entries[0], 'g', unit.content.indexOf('int i) { // g'));
1894+
// Entry 1 is the edge from g's argument to f's argument, due to g's call to
1895+
// f.
1896+
assertTraceEntry(
1897+
unit, entries[1], 'g', unit.content.indexOf('i); // call f'));
1898+
// Entry 2 is the nullability of f's argument
1899+
assertTraceEntry(
1900+
unit, entries[2], 'f', unit.content.indexOf('int i) { // f'));
1901+
// Entry 3 is the edge f's argument to never, due to the assert.
1902+
assertTraceEntry(unit, entries[3], 'f', unit.content.indexOf('assert'));
1903+
// Entry 4 is the "never" node.
1904+
// TODO(paulberry): this node provides no additional useful information and
1905+
// shouldn't be included in the trace.
1906+
expect(entries[4].description, 'never');
1907+
expect(entries[4].function, null);
1908+
expect(entries[4].target, null);
1909+
}
1910+
18591911
Future<void> test_trace_substitutionNode() async {
18601912
UnitInfo unit = await buildInfoForSingleTestFile('''
18611913
class C<T extends Object/*!*/> {}
@@ -1875,10 +1927,12 @@ String/*!*/ y = x[0]!;
18751927
var region = unit.regions
18761928
.where((regionInfo) => regionInfo.offset == unit.content.indexOf('!;'))
18771929
.single;
1878-
// The nullability node associated with adding the `!` is a substitution
1930+
// The "why nullable" node associated with adding the `!` is a substitution
18791931
// node, and we don't currently generate a trace for a substitution node.
18801932
// TODO(paulberry): fix this.
1881-
expect(region.traces, isEmpty);
1933+
// We do, however, generate a trace for "why not nullable".
1934+
expect(region.traces, hasLength(1));
1935+
expect(region.traces[0].description, 'Non-nullability reason');
18821936
}
18831937

18841938
Future<void> test_uninitializedField() async {

pkg/nnbd_migration/lib/instrumentation.dart

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ abstract class DecoratedTypeInfo {
112112
DecoratedTypeInfo typeArgument(int i);
113113
}
114114

115-
/// Information about a propagation stup that occurred during downstream
115+
/// Information about a propagation step that occurred during downstream
116116
/// propagation.
117117
abstract class DownstreamPropagationStepInfo implements PropagationStepInfo {
118118
DownstreamPropagationStepInfo get principalCause;
@@ -362,6 +362,10 @@ abstract class NullabilityNodeInfo implements FixReasonInfo {
362362
/// The edges that caused this node to have the nullability that it has.
363363
Iterable<EdgeInfo> get upstreamEdges;
364364

365+
/// If [isNullable] is false, the propagation step that caused this node to
366+
/// become non-nullable (if any).
367+
UpstreamPropagationStepInfo get whyNotNullable;
368+
365369
/// If [isNullable] is true, the propagation step that caused this node to
366370
/// become nullable.
367371
DownstreamPropagationStepInfo get whyNullable;
@@ -388,3 +392,17 @@ abstract class SubstitutionNodeInfo extends NullabilityNodeInfo {
388392
/// `*` in `T*`.
389393
NullabilityNodeInfo get outerNode;
390394
}
395+
396+
/// Information about a propagation step that occurred during upstream
397+
/// propagation.
398+
abstract class UpstreamPropagationStepInfo implements PropagationStepInfo {
399+
/// The node whose nullability was changed.
400+
///
401+
/// Any propagation step that took effect should have a non-null value here.
402+
/// Propagation steps that are pending but have not taken effect yet, or that
403+
/// never had an effect (e.g. because an edge was not triggered) will have a
404+
/// `null` value for this field.
405+
NullabilityNodeInfo get node;
406+
407+
UpstreamPropagationStepInfo get principalCause;
408+
}

pkg/nnbd_migration/lib/src/nullability_node.dart

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,7 @@ abstract class NullabilityNode implements NullabilityNodeInfo {
766766
@override
767767
Iterable<EdgeInfo> get upstreamEdges => _upstreamEdges;
768768

769-
/// If this node has non-null intent, the propagation step that caused it to
770-
/// have non-null intent, otherwise `null`.
769+
@override
771770
UpstreamPropagationStep get whyNotNullable;
772771

773772
String get _jsonKind;
@@ -1167,7 +1166,8 @@ class SimpleExactNullablePropagationStep extends ExactNullablePropagationStep {
11671166

11681167
/// Propagation step where we mark a node as having non-null intent due to it
11691168
/// being upstream from another node with non-null intent.
1170-
class UpstreamPropagationStep extends PropagationStep {
1169+
class UpstreamPropagationStep extends PropagationStep
1170+
implements UpstreamPropagationStepInfo {
11711171
@override
11721172
final UpstreamPropagationStep principalCause;
11731173

@@ -1177,23 +1177,32 @@ class UpstreamPropagationStep extends PropagationStep {
11771177
/// The new state of the node's non-null intent.
11781178
final NonNullIntent newNonNullIntent;
11791179

1180+
/// The nullability edge connecting [node] to the node it is upstream from, if
1181+
/// any.
1182+
final NullabilityEdge edge;
1183+
11801184
UpstreamPropagationStep(
1181-
this.principalCause, this.node, this.newNonNullIntent);
1185+
this.principalCause, this.node, this.newNonNullIntent, this.edge);
11821186

11831187
UpstreamPropagationStep.fromJson(
11841188
dynamic json, NullabilityGraphDeserializer deserializer)
11851189
: principalCause = deserializer.stepForId(json['cause'] as int)
11861190
as UpstreamPropagationStep,
11871191
node = deserializer.nodeForId(json['node'] as int),
1188-
newNonNullIntent = NonNullIntent.fromJson(json['newState']);
1192+
newNonNullIntent = NonNullIntent.fromJson(json['newState']),
1193+
edge = deserializer.edgeForId(json['edge'] as int);
1194+
1195+
@override
1196+
CodeReference get codeReference => edge?.codeReference;
11891197

11901198
@override
11911199
Map<String, Object> toJson(NullabilityGraphSerializer serializer) {
11921200
return {
11931201
'kind': 'upstream',
11941202
'cause': serializer.idForStep(principalCause),
11951203
'node': serializer.idForNode(node),
1196-
'newState': newNonNullIntent.toJson()
1204+
'newState': newNonNullIntent.toJson(),
1205+
'edge': serializer.idForEdge(edge)
11971206
};
11981207
}
11991208

@@ -1388,7 +1397,7 @@ class _PropagationState {
13881397
void _propagateUpstream() {
13891398
Queue<UpstreamPropagationStep> pendingSteps = Queue();
13901399
pendingSteps
1391-
.add(UpstreamPropagationStep(null, _never, NonNullIntent.direct));
1400+
.add(UpstreamPropagationStep(null, _never, NonNullIntent.direct, null));
13921401
while (pendingSteps.isNotEmpty) {
13931402
var cause = pendingSteps.removeFirst();
13941403
var pendingNode = cause.node;
@@ -1408,7 +1417,8 @@ class _PropagationState {
14081417
} else {
14091418
newNonNullIntent = oldNonNullIntent.addIndirect();
14101419
}
1411-
var step = UpstreamPropagationStep(cause, node, newNonNullIntent);
1420+
var step =
1421+
UpstreamPropagationStep(cause, node, newNonNullIntent, edge);
14121422
_setNonNullIntent(step);
14131423
if (!oldNonNullIntent.isPresent) {
14141424
// We did not previously have non-null intent, so we need to
@@ -1426,7 +1436,7 @@ class _PropagationState {
14261436
}
14271437
var oldNonNullIntent = node._nonNullIntent;
14281438
var newNonNullIntent = oldNonNullIntent.addIndirect();
1429-
var step = UpstreamPropagationStep(cause, node, newNonNullIntent);
1439+
var step = UpstreamPropagationStep(cause, node, newNonNullIntent, null);
14301440
_setNonNullIntent(step);
14311441
if (!oldNonNullIntent.isPresent) {
14321442
// We did not previously have non-null intent, so we need to

0 commit comments

Comments
 (0)