Add the ability to do a debug dump of a nullability graph.
Change-Id: Ia3536423349ded8810c06ef2d4a631607273af88
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102300
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart b/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
index 51ae58c..ea6265c 100644
--- a/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
+++ b/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
@@ -48,13 +48,16 @@
/// Creates and stores a [DecoratedType] object corresponding to the given
/// [type] AST, and returns it.
- DecoratedType decorateType(TypeAnnotation type) {
+ DecoratedType decorateType(TypeAnnotation type, AstNode enclosingNode) {
return type == null
// TODO(danrubel): Return something other than this
// to indicate that we should insert a type for the declaration
// that is missing a type reference.
- ? new DecoratedType(DynamicTypeImpl.instance,
- NullabilityNode.forInferredDynamicType(_graph), _graph)
+ ? new DecoratedType(
+ DynamicTypeImpl.instance,
+ NullabilityNode.forInferredDynamicType(
+ _graph, enclosingNode.offset),
+ _graph)
: type.accept(this);
}
@@ -84,14 +87,14 @@
@override
DecoratedType visitFunctionDeclaration(FunctionDeclaration node) {
_handleExecutableDeclaration(node.declaredElement, node.returnType,
- node.functionExpression.parameters);
+ node.functionExpression.parameters, node);
return null;
}
@override
DecoratedType visitMethodDeclaration(MethodDeclaration node) {
_handleExecutableDeclaration(
- node.declaredElement, node.returnType, node.parameters);
+ node.declaredElement, node.returnType, node.parameters, node);
return null;
}
@@ -110,7 +113,7 @@
@override
DecoratedType visitSimpleFormalParameter(SimpleFormalParameter node) {
- var type = decorateType(node.type);
+ var type = decorateType(node.type, node);
var declaredElement = node.declaredElement;
type.node.trackNonNullIntent(node.offset);
_variables.recordDecoratedElementType(declaredElement, type);
@@ -158,9 +161,12 @@
DecoratedType visitTypeName(TypeName node) => visitTypeAnnotation(node);
/// Common handling of function and method declarations.
- void _handleExecutableDeclaration(ExecutableElement declaredElement,
- TypeAnnotation returnType, FormalParameterList parameters) {
- var decoratedReturnType = decorateType(returnType);
+ void _handleExecutableDeclaration(
+ ExecutableElement declaredElement,
+ TypeAnnotation returnType,
+ FormalParameterList parameters,
+ AstNode enclosingNode) {
+ var decoratedReturnType = decorateType(returnType, enclosingNode);
var previousFunctionType = _currentFunctionType;
// TODO(paulberry): test that it's correct to use `null` for the nullability
// of the function type
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
index bcc2300..311db8a 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
@@ -24,6 +24,12 @@
(_upstream[destinationNode] ??= []).add(sourceNode);
}
+ void debugDump() {
+ for (var entry in _downstream.entries) {
+ print('${entry.key} -> ${entry.value.join(', ')}');
+ }
+ }
+
/// Iterates through all nodes that are "downstream" of [node] (i.e. if
/// [node] is nullable, then all the iterated nodes will either have to be
/// nullable, or null checks will have to be added).
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_node.dart b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
index 453a8cc..cc5ee45 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_node.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
@@ -15,14 +15,17 @@
/// nullability inference graph is encoded into the wrapped constraint
/// variables. Over time this will be replaced by a first class representation
/// of the nullability inference graph.
-class NullabilityNode {
+abstract class NullabilityNode {
/// [NullabilityNode] used for types that are known a priori to be nullable
/// (e.g. the type of the `null` literal).
- static final always = NullabilityNode._(ConstraintVariable.always);
+ static final NullabilityNode always =
+ _NullabilityNodeSimple(ConstraintVariable.always, 'always');
/// [NullabilityNode] used for types that are known a priori to be
/// non-nullable (e.g. the type of an integer literal).
- static final never = NullabilityNode._(null);
+ static final NullabilityNode never = _NullabilityNodeSimple(null, 'never');
+
+ static final _debugNamesInUse = Set<String>();
/// [ConstraintVariable] whose value will be set to `true` if this type needs
/// to be nullable.
@@ -35,13 +38,17 @@
bool _isPossiblyOptional = false;
+ String _debugName;
+
/// Creates a [NullabilityNode] representing the nullability of a variable
/// whose type is `dynamic` due to type inference.
///
/// TODO(paulberry): this should go away; we should decorate the actual
/// inferred type rather than assuming `dynamic`.
- factory NullabilityNode.forInferredDynamicType(NullabilityGraph graph) {
- var node = NullabilityNode._(TypeIsNullable(null));
+ factory NullabilityNode.forInferredDynamicType(
+ NullabilityGraph graph, int offset) {
+ var node = _NullabilityNodeSimple(
+ TypeIsNullable(null), 'inferredDynamic($offset)');
graph.connect(NullabilityNode.always, node);
return node;
}
@@ -80,8 +87,11 @@
/// Creates a [NullabilityNode] representing the nullability of a type
/// annotation appearing explicitly in the user's program.
- NullabilityNode.forTypeAnnotation(int endOffset, {@required bool always})
- : this._(always ? ConstraintVariable.always : TypeIsNullable(endOffset));
+ factory NullabilityNode.forTypeAnnotation(int endOffset,
+ {@required bool always}) =>
+ _NullabilityNodeSimple(
+ always ? ConstraintVariable.always : TypeIsNullable(endOffset),
+ 'type($endOffset)');
NullabilityNode._(this._nullable);
@@ -103,6 +113,8 @@
/// an exception being thrown in the case of a `null` value at runtime).
ConstraintVariable get nonNullIntent => _nonNullIntent;
+ String get _debugPrefix;
+
/// Records the fact that an invocation was made to a function with named
/// parameters, and the named parameter associated with this node was not
/// supplied.
@@ -118,6 +130,24 @@
_recordConstraints(constraints, guards, const [], nonNullIntent);
}
+ String toString() {
+ if (_debugName == null) {
+ var prefix = _debugPrefix;
+ if (_debugNamesInUse.add(prefix)) {
+ _debugName = prefix;
+ } else {
+ for (int i = 0;; i++) {
+ var name = '${prefix}_$i';
+ if (_debugNamesInUse.add(name)) {
+ _debugName = name;
+ break;
+ }
+ }
+ }
+ }
+ return _debugName;
+ }
+
/// Tracks that the possibility that this nullability node might demonstrate
/// non-null intent, based on the fact that it corresponds to a formal
/// parameter declaration at location [offset].
@@ -223,6 +253,9 @@
graph.connect(left, this);
graph.connect(right, this);
}
+
+ @override
+ String get _debugPrefix => 'LUB($left, $right)';
}
/// Derived class for nullability nodes that arise from type variable
@@ -246,4 +279,15 @@
Constraints constraints, this.innerNode, this.outerNode)
: super._(ConstraintVariable.or(
constraints, innerNode?._nullable, outerNode._nullable));
+
+ @override
+ String get _debugPrefix => 'Substituted($innerNode, $outerNode)';
+}
+
+class _NullabilityNodeSimple extends NullabilityNode {
+ @override
+ final String _debugPrefix;
+
+ _NullabilityNodeSimple(ConstraintVariable nullable, this._debugPrefix)
+ : super._(nullable);
}