Switch nullability migration over to the new algorithm based on the new nullability graph.
Change-Id: I1cbfba8d3d46020d8e943109e582d61315781561
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102386
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 598dff1..0541ed0 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
@@ -27,8 +27,6 @@
final _unconditionalUpstream =
Map<NullabilityNode, List<NullabilityNode>>.identity();
- final _nullableNodes = Set<NullabilityNode>.identity();
-
final _nonNullIntentNodes = Set<NullabilityNode>.identity();
/// Verifies that the conclusions reached by [propagate] match the conclusions
@@ -39,7 +37,7 @@
var allNodes = _downstream.keys.toSet();
allNodes.addAll(_upstream.keys);
for (var node in allNodes) {
- node.check(_nullableNodes.contains(node));
+ node.check();
}
} catch (_) {
debugDump();
@@ -76,7 +74,7 @@
return '${edge.destinationNode}$suffix';
});
var suffixes = <String>[];
- if (_nullableNodes.contains(entry.key)) {
+ if (entry.key.isNullable) {
suffixes.add('nullable');
}
if (_nonNullIntentNodes.contains(entry.key)) {
@@ -122,7 +120,8 @@
/// Propagates nullability downstream.
void _propagateDownstream() {
- var pendingEdges = [_NullabilityEdge(NullabilityNode.always, const [])];
+ var pendingEdges = <_NullabilityEdge>[]
+ ..addAll(_downstream[NullabilityNode.always] ?? const []);
var pendingSubstitutions = <NullabilityNodeForSubstitution>[];
while (true) {
nextEdge:
@@ -135,12 +134,12 @@
continue;
}
for (var source in edge.sources) {
- if (!_nullableNodes.contains(source)) {
+ if (!source.isNullable) {
// Note all sources are nullable, so this edge doesn't apply yet.
continue nextEdge;
}
}
- if (_nullableNodes.add(node)) {
+ if (node.becomeNullable()) {
// Was not previously nullable, so we need to propagate.
pendingEdges.addAll(_downstream[node] ?? const []);
if (node is NullabilityNodeForSubstitution) {
@@ -150,8 +149,7 @@
}
if (pendingSubstitutions.isEmpty) break;
var node = pendingSubstitutions.removeLast();
- if (_nullableNodes.contains(node.innerNode) ||
- _nullableNodes.contains(node.outerNode)) {
+ if (node.innerNode.isNullable || node.outerNode.isNullable) {
// No further propagation is needed, since some other connection already
// propagated nullability to either the inner or outer node.
continue;
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_node.dart b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
index d9ed674..a489dd4 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_node.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
@@ -19,11 +19,12 @@
/// [NullabilityNode] used for types that are known a priori to be nullable
/// (e.g. the type of the `null` literal).
static final NullabilityNode always =
- _NullabilityNodeSimple(ConstraintVariable.always, 'always');
+ _NullabilityNodeImmutable(ConstraintVariable.always, 'always', true);
/// [NullabilityNode] used for types that are known a priori to be
/// non-nullable (e.g. the type of an integer literal).
- static final NullabilityNode never = _NullabilityNodeSimple(null, 'never');
+ static final NullabilityNode never =
+ _NullabilityNodeImmutable(null, 'never', false);
static final _debugNamesInUse = Set<String>();
@@ -40,6 +41,8 @@
String _debugName;
+ bool _isNullable;
+
/// Creates a [NullabilityNode] representing the nullability of a variable
/// whose type is `dynamic` due to type inference.
///
@@ -94,16 +97,17 @@
always ? ConstraintVariable.always : TypeIsNullable(endOffset),
'type($endOffset)');
- NullabilityNode._(this._nullable);
+ NullabilityNode._(this._nullable, {bool initiallyNullable: false})
+ : _isNullable = initiallyNullable;
/// Gets a string that can be appended to a type name during debugging to help
/// annotate the nullability of that type.
String get debugSuffix =>
this == always ? '?' : this == never ? '' : '?($this)';
- /// After constraint solving, this getter can be used to query whether the
- /// type associated with this node should be considered nullable.
- bool get isNullable => _nullable == null ? false : _nullable.value;
+ /// After nullability propagation, this getter can be used to query whether
+ /// the type associated with this node should be considered nullable.
+ bool get isNullable => _isNullable;
/// Indicates whether this node is associated with a named parameter for which
/// nullability migration needs to decide whether it is optional or required.
@@ -117,12 +121,26 @@
String get _debugPrefix;
- /// Verifies that the nullability of this node matches [isNullable].
- void check(bool isNullable) {
- if (isNullable != this.isNullable) {
+ /// After constraint solving, this getter can be used to query whether the
+ /// type associated with this node should be considered nullable.
+ bool get _oldIsNullable => _nullable == null ? false : _nullable.value;
+
+ /// During constraint solving, this method marks the type as nullable, or does
+ /// nothing if the type was already nullable.
+ ///
+ /// Return value indicates whether a change was made.
+ bool becomeNullable() {
+ if (_isNullable) return false;
+ _isNullable = true;
+ return true;
+ }
+
+ /// Verifies that the old and new nullability propagation algorithms match.
+ void check() {
+ if (_oldIsNullable != this.isNullable) {
throw new StateError(
'For $this, new algorithm gives nullability $isNullable; '
- 'old algorithm gives ${this.isNullable}');
+ 'old algorithm gives $_oldIsNullable');
}
}
@@ -298,10 +316,23 @@
String get _debugPrefix => 'Substituted($innerNode, $outerNode)';
}
+class _NullabilityNodeImmutable extends _NullabilityNodeSimple {
+ _NullabilityNodeImmutable(
+ ConstraintVariable nullable, String debugPrefix, bool isNullable)
+ : super(nullable, debugPrefix, initiallyNullable: isNullable);
+
+ @override
+ bool becomeNullable() {
+ if (_isNullable) return false;
+ throw new StateError('Tried to change the nullability of $this');
+ }
+}
+
class _NullabilityNodeSimple extends NullabilityNode {
@override
final String _debugPrefix;
- _NullabilityNodeSimple(ConstraintVariable nullable, this._debugPrefix)
- : super._(nullable);
+ _NullabilityNodeSimple(ConstraintVariable nullable, this._debugPrefix,
+ {bool initiallyNullable: false})
+ : super._(nullable, initiallyNullable: initiallyNullable);
}