Record information about guards in the nullability graph.
Change-Id: I169133b85e0405d1ff6b3384a6d99c7fcb33a0fd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102382
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
index c48537b..e36e92f 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
@@ -7,11 +7,12 @@
/// Data structure to keep track of the relationship between [NullabilityNode]
/// objects.
class NullabilityGraph {
- /// Map from a nullability node to those nodes that are "downstream" from it
- /// (meaning that if a key node is nullable, then all the nodes in the
+ /// Map from a nullability node to a list of [_NullabilityEdge] objects
+ /// describing the node's relationship to other nodes that are "downstream"
+ /// from it (meaning that if a key node is nullable, then all the nodes in the
/// corresponding value will either have to be nullable, or null checks will
/// have to be added).
- final _downstream = Map<NullabilityNode, List<NullabilityNode>>.identity();
+ final _downstream = Map<NullabilityNode, List<_NullabilityEdge>>.identity();
/// Map from a nullability node to those nodes that are "upstream" from it
/// (meaning that if a node in the value is nullable, then the corresponding
@@ -28,8 +29,12 @@
/// Records that [sourceNode] is immediately upstream from [destinationNode].
void connect(NullabilityNode sourceNode, NullabilityNode destinationNode,
- {bool unconditional: false}) {
- (_downstream[sourceNode] ??= []).add(destinationNode);
+ {bool unconditional: false, List<NullabilityNode> guards: const []}) {
+ var sources = [sourceNode]..addAll(guards);
+ var edge = _NullabilityEdge(destinationNode, sources);
+ for (var source in sources) {
+ (_downstream[source] ??= []).add(edge);
+ }
(_upstream[destinationNode] ??= []).add(sourceNode);
if (unconditional) {
(_unconditionalUpstream[destinationNode] ??= []).add(sourceNode);
@@ -38,12 +43,19 @@
void debugDump() {
for (var entry in _downstream.entries) {
- print('${entry.key} -> ${entry.value.map((value) {
- var suffix = getUnconditionalUpstreamNodes(value).contains(entry.key)
- ? ' (unconditional)'
- : '';
- return '$value$suffix';
- }).join(', ')}');
+ var destinations = entry.value
+ .where((edge) => edge.primarySource == entry.key)
+ .map((edge) {
+ var suffixes = <Object>[];
+ if (getUnconditionalUpstreamNodes(edge.destinationNode)
+ .contains(entry.key)) {
+ suffixes.add('unconditional');
+ }
+ suffixes.addAll(edge.guards);
+ var suffix = suffixes.isNotEmpty ? ' (${suffixes.join(', ')})' : '';
+ return '${edge.destinationNode}$suffix';
+ });
+ print('${entry.key} -> ${destinations.join(', ')}');
}
}
@@ -53,7 +65,9 @@
///
/// There is no guarantee of uniqueness of the iterated nodes.
Iterable<NullabilityNode> getDownstreamNodes(NullabilityNode node) =>
- _downstream[node] ?? const [];
+ (_downstream[node] ?? const [])
+ .where((edge) => edge.primarySource == node)
+ .map((edge) => edge.destinationNode);
/// Iterates through all nodes that are "upstream" of [node] due to
/// unconditional control flow.
@@ -71,3 +85,23 @@
Iterable<NullabilityNode> getUpstreamNodes(NullabilityNode node) =>
_upstream[node] ?? const [];
}
+
+/// Data structure to keep track of the relationship from one [NullabilityNode]
+/// object to another [NullabilityNode] that is "downstream" from it (meaning
+/// that if the former node is nullable, then the latter node will either have
+/// to be nullable, or null checks will have to be added).
+class _NullabilityEdge {
+ /// The node that is downstream.
+ final NullabilityNode destinationNode;
+
+ /// A set of source nodes. By convention, the first node is the primary
+ /// source and the other nodes are "guards". The destination node will only
+ /// need to be made nullable if all the source nodes are nullable.
+ final List<NullabilityNode> sources;
+
+ _NullabilityEdge(this.destinationNode, this.sources);
+
+ Iterable<NullabilityNode> get guards => sources.skip(1);
+
+ NullabilityNode get primarySource => sources.first;
+}
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_node.dart b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
index f332806..235ef5f 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_node.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
@@ -188,7 +188,7 @@
bool inConditionalControlFlow) {
var additionalConditions = <ConstraintVariable>[];
graph.connect(sourceNode, destinationNode,
- unconditional: !inConditionalControlFlow);
+ guards: guards, unconditional: !inConditionalControlFlow);
if (sourceNode._nullable != null) {
additionalConditions.add(sourceNode._nullable);
var destinationNonNullIntent = destinationNode.nonNullIntent;