blob: c50614f9c96a2bf89077b3aa00d7a03afb64fe9a [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:nnbd_migration/instrumentation.dart';
import 'package:nnbd_migration/src/edge_origin.dart';
import 'package:nnbd_migration/src/nullability_node.dart';
import 'package:nnbd_migration/src/nullability_node_target.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(NullabilityNodeTest);
});
}
@reflectiveTest
class NullabilityNodeTest {
final graph = NullabilityGraphForTesting();
/// A list of all edges that couldn't be satisfied. May contain duplicates.
List<NullabilityEdge> unsatisfiedEdges;
List<NullabilityNodeForSubstitution> unsatisfiedSubstitutions;
NullabilityNode get always => graph.always;
NullabilityNode get never => graph.never;
void assertUnsatisfied(List<NullabilityEdge> expectedUnsatisfiedEdges) {
expect(unsatisfiedEdges.toSet(), expectedUnsatisfiedEdges.toSet());
}
NullabilityEdge connect(NullabilityNode source, NullabilityNode destination,
{bool hard = false,
bool checkable = true,
List<NullabilityNode> guards = const []}) {
return graph.connect(source, destination, _TestEdgeOrigin(),
hard: hard, checkable: checkable, guards: guards);
}
NullabilityNode lub(NullabilityNode left, NullabilityNode right) {
return NullabilityNode.forLUB(left, right);
}
NullabilityNode newNode(int id) =>
NullabilityNode.forTypeAnnotation(NullabilityNodeTarget.text('node $id'));
void propagate() {
var propagationResult = graph.propagate(null);
unsatisfiedEdges = propagationResult.unsatisfiedEdges;
unsatisfiedSubstitutions = propagationResult.unsatisfiedSubstitutions;
}
NullabilityNode subst(NullabilityNode inner, NullabilityNode outer) {
return NullabilityNode.forSubstitution(inner, outer);
}
void test_always_and_never_state() {
propagate();
expect(always.isNullable, isTrue);
expect(never.isNullable, isFalse);
assertUnsatisfied([]);
}
void test_always_and_never_unaffected_by_hard_edges() {
var edge = connect(always, never, hard: true);
propagate();
expect(always.isNullable, isTrue);
expect(never.isNullable, isFalse);
assertUnsatisfied([edge]);
}
void test_always_and_never_unaffected_by_soft_edges() {
var edge = connect(always, never);
propagate();
expect(always.isNullable, isTrue);
expect(never.isNullable, isFalse);
assertUnsatisfied([edge]);
}
void test_always_destination() {
// always -> 1 -(hard)-> always
var n1 = newNode(1);
connect(always, n1);
connect(n1, always, hard: true);
propagate();
// Upstream propagation of non-nullability ignores edges terminating at
// `always`, so n1 should be nullable.
expect(n1.isNullable, true);
assertUnsatisfied([]);
}
void test_edge_satisfied_due_to_guard() {
// always -> 1
// 1 -(2) -> 3
// 3 -(hard)-> never
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
connect(n1, n3, guards: [n2]);
connect(n3, never, hard: true);
propagate();
expect(n1.isNullable, true);
expect(n2.isNullable, false);
expect(n3.isNullable, false);
// Although n1 is nullable and n3 is non-nullable, the edge from 1 to 3 is
// considered satisfied because the guard (n2) is non-nullable.
assertUnsatisfied([]);
}
void test_lubNode_relatesInBothDirections() {
final nodeA = newNode(1);
final nodeB = newNode(2);
final lubNode = lub(nodeA, nodeB);
expect(nodeA.outerCompoundNodes, [lubNode]);
expect(nodeB.outerCompoundNodes, [lubNode]);
}
void test_never_source() {
// never -> 1
var n1 = newNode(1);
connect(never, n1);
propagate();
// Downstream propagation of nullability ignores edges originating at
// `never`, so n1 should be non-nullable.
expect(n1.isNullable, false);
assertUnsatisfied([]);
}
void test_propagation_always_union() {
// always == 1
// 1 -(hard)-> never
// 1 -> 2
// 1 -> 3
// 3 -(hard)-> never
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
union(always, n1);
var edge_1_never = connect(n1, never, hard: true);
connect(n1, n2);
var edge_1_3 = connect(n1, n3);
connect(n3, never, hard: true);
propagate();
// Union edges take precedence over hard ones, so n1 should be nullable.
expect(n1.isNullable, true);
// And nullability should be propagated to n2.
expect(n2.isNullable, true);
// But it should not be propagated to n3 because non-nullability propagation
// takes precedence over ordinary nullability propagation.
expect(n3.isNullable, false);
assertUnsatisfied([edge_1_never, edge_1_3]);
}
void test_propagation_always_union_reversed() {
// always == 1
// 1 -(hard)-> never
// 1 -> 2
// 1 -> 3
// 3 -(hard)-> never
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
union(n1, always);
var edge_1_never = connect(n1, never, hard: true);
connect(n1, n2);
var edge_1_3 = connect(n1, n3);
connect(n3, never, hard: true);
propagate();
// Union edges take precedence over hard ones, so n1 should be nullable.
expect(n1.isNullable, true);
// And nullability should be propagated to n2.
expect(n2.isNullable, true);
// But it should not be propagated to n3 because non-nullability propagation
// takes precedence over ordinary nullability propagation.
expect(n3.isNullable, false);
assertUnsatisfied([edge_1_never, edge_1_3]);
}
void test_propagation_downstream_breadth_first() {
// always -> 1 -> 2
// 1 -> 3 -> 4
// always -> 5 -> 4
// 5 -> 6 -> 2
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
var n4 = newNode(4);
var n5 = newNode(5);
var n6 = newNode(6);
connect(always, n1);
connect(n1, n2);
connect(n1, n3);
connect(n3, n4);
connect(always, n5);
connect(n5, n4);
connect(n5, n6);
connect(n6, n2);
propagate();
// Node 2 should be caused by node 1, since that's the shortest path back to
// "always". Similarly, node 4 should be caused by node 5.
expect(_downstreamCauseNode(n2), same(n1));
expect(_downstreamCauseNode(n4), same(n5));
}
void test_propagation_downstream_guarded_multiple_guards_all_satisfied() {
// always -> 1
// always -> 2
// always -> 3
// 1 -(2,3)-> 4
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
var n4 = newNode(3);
connect(always, n1);
connect(always, n2);
connect(always, n3);
connect(n1, n4, guards: [n2, n3]);
propagate();
expect(n4.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_guarded_multiple_guards_not_all_satisfied() {
// always -> 1
// always -> 2
// 1 -(2,3)-> 4
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
var n4 = newNode(3);
connect(always, n1);
connect(always, n2);
connect(n1, n4, guards: [n2, n3]);
propagate();
expect(n4.isNullable, false);
assertUnsatisfied([]);
}
void test_propagation_downstream_guarded_satisfy_guard_first() {
// always -> 1
// always -> 2
// 2 -(1)-> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
connect(always, n2);
connect(n2, n3, guards: [n1]);
propagate();
expect(n3.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_guarded_satisfy_source_first() {
// always -> 1
// always -> 2
// 1 -(2)-> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
connect(always, n2);
connect(n1, n3, guards: [n2]);
propagate();
expect(n3.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_guarded_unsatisfied_guard() {
// always -> 1
// 1 -(2)-> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
connect(n1, n3, guards: [n2]);
propagate();
expect(n3.isNullable, false);
assertUnsatisfied([]);
}
void test_propagation_downstream_guarded_unsatisfied_source() {
// always -> 1
// 2 -(1)-> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
connect(n2, n3, guards: [n1]);
propagate();
expect(n3.isNullable, false);
assertUnsatisfied([]);
}
void test_propagation_downstream_reverse_substitution_exact() {
// always -> subst(1, 2)
// 3 -(uncheckable)-> 1
// 4 -(uncheckable)-> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
var n4 = newNode(4);
connect(always, subst(n1, n2));
connect(n3, n1, checkable: false);
connect(n4, n3, checkable: false);
propagate();
expect(n1.isNullable, true);
expect(n1.isExactNullable, true);
expect(n2.isNullable, false);
expect(n3.isNullable, true);
expect(n3.isExactNullable, true);
expect(n4.isNullable, true);
expect(n4.isExactNullable, true);
}
void test_propagation_downstream_reverse_substitution_exact_checkable() {
// always -> subst(1, 2)
// 3 -(uncheckable)-> 1
// 4 -(checkable)-> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
var n4 = newNode(4);
connect(always, subst(n1, n2));
connect(n3, n1, checkable: false);
connect(n4, n3, checkable: true);
propagate();
expect(n1.isNullable, true);
expect(n1.isExactNullable, true);
expect(n2.isNullable, false);
expect(n3.isNullable, true);
expect(n3.isExactNullable, true);
expect(n4.isNullable, false);
expect(n4.isExactNullable, false);
}
void test_propagation_downstream_reverse_substitution_inner_non_nullable() {
// 1 -> never (hard)
// always -> subst(1, 2)
// 3 -> 2
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(n1, never, hard: true);
connect(always, subst(n1, n2));
connect(n3, n2);
propagate();
expect(n1.isNullable, false);
expect(n2.isNullable, true);
expect(n2.isExactNullable, false);
expect(n3.isNullable, false);
}
void test_propagation_downstream_reverse_substitution_non_null_intent() {
// always -> subst(1, 2)
// 3 -(uncheckable)-> 1
// 4 -(checkable)-> 3
// 4 -(hard) -> never
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
var n4 = newNode(4);
connect(always, subst(n1, n2));
connect(n3, n1, checkable: false);
connect(n4, n3, checkable: false);
connect(n4, never, hard: true);
propagate();
expect(n1.isNullable, true);
expect(n1.isExactNullable, true);
expect(n2.isNullable, false);
expect(n3.isNullable, true);
expect(n3.isExactNullable, true);
expect(n4.isNullable, false);
expect(n4.isExactNullable, false);
}
void
test_propagation_downstream_reverse_substitution_outer_already_nullable() {
// always -> 2
// always -> subst(1, 2)
// 3 -> 2
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n2);
connect(always, subst(n1, n2));
connect(n3, n2);
propagate();
expect(n1.isNullable, false);
expect(n2.isNullable, true);
expect(n2.isExactNullable, false);
expect(n3.isNullable, false);
}
void test_propagation_downstream_reverse_substitution_unsatisfiable() {
// 1 -> never (hard)
// 2 -> never (hard)
// always -> subst(1, 2)
var n1 = newNode(1);
var n2 = newNode(2);
connect(n1, never, hard: true);
connect(n2, never, hard: true);
var substitutionNode = subst(n1, n2);
connect(always, substitutionNode);
propagate();
expect(n1.isNullable, false);
expect(n2.isNullable, false);
expect(substitutionNode.isNullable, false);
expect(unsatisfiedSubstitutions, isEmpty);
}
void test_propagation_downstream_through_lub_both() {
// always -> 1
// always -> 2
// LUB(1, 2) -> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
connect(always, n2);
connect(lub(n1, n2), n3);
propagate();
expect(n3.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_through_lub_cascaded() {
// always -> 1
// LUB(LUB(1, 2), 3) -> 4
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
var n4 = newNode(3);
connect(always, n1);
connect(lub(lub(n1, n2), n3), n4);
propagate();
expect(n4.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_through_lub_left() {
// always -> 1
// LUB(1, 2) -> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
connect(lub(n1, n2), n3);
propagate();
expect(n3.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_through_lub_neither() {
// LUB(1, 2) -> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(lub(n1, n2), n3);
propagate();
expect(n3.isNullable, false);
assertUnsatisfied([]);
}
void test_propagation_downstream_through_lub_right() {
// always -> 2
// LUB(1, 2) -> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n2);
connect(lub(n1, n2), n3);
propagate();
expect(n3.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_through_substitution_both() {
// always -> 1
// always -> 2
// subst(1, 2) -> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
connect(always, n2);
connect(subst(n1, n2), n3);
propagate();
expect(n3.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_through_substitution_cascaded() {
// always -> 1
// subst(subst(1, 2), 3) -> 4
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
var n4 = newNode(3);
connect(always, n1);
connect(subst(subst(n1, n2), n3), n4);
propagate();
expect(n4.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_through_substitution_inner() {
// always -> 1
// subst(1, 2) -> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
connect(subst(n1, n2), n3);
propagate();
expect(n3.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_through_substitution_neither() {
// subst(1, 2) -> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(subst(n1, n2), n3);
propagate();
expect(n3.isNullable, false);
assertUnsatisfied([]);
}
void test_propagation_downstream_through_substitution_outer() {
// always -> 2
// subst(1, 2) -> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n2);
connect(subst(n1, n2), n3);
propagate();
expect(n3.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_through_union() {
// always -> 1
// 1 == 2
// 2 -> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
union(n1, n2);
connect(n2, n3);
propagate();
expect(n1.isNullable, true);
expect(n2.isNullable, true);
expect(n3.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_downstream_through_union_reversed() {
// always -> 1
// 2 == 1
// 2 -> 3
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
union(n2, n1);
connect(n2, n3);
propagate();
expect(n1.isNullable, true);
expect(n2.isNullable, true);
expect(n3.isNullable, true);
assertUnsatisfied([]);
}
void test_propagation_simple() {
// always -(soft)-> 1 -(soft)-> 2 -(hard) -> 3 -(hard)-> never
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
connect(always, n1);
var edge_1_2 = connect(n1, n2);
connect(n2, n3, hard: true);
connect(n3, never, hard: true);
propagate();
expect(n1.isNullable, true);
expect(n2.isNullable, false);
expect(n3.isNullable, false);
assertUnsatisfied([edge_1_2]);
}
void test_propagation_upstream_breadth_first() {
// never <- 1 <- 2
// 1 <- 3 <- 4
// never <- 5 <- 4
// 5 <- 6 <- 2
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
var n4 = newNode(4);
var n5 = newNode(5);
var n6 = newNode(6);
connect(n1, never, hard: true);
connect(n2, n1, hard: true);
connect(n3, n1, hard: true);
connect(n4, n3, hard: true);
connect(n5, never, hard: true);
connect(n4, n5, hard: true);
connect(n6, n5, hard: true);
connect(n2, n6, hard: true);
propagate();
// Node 2 should be caused by node 1, since that's the shortest path back to
// "always". Similarly, node 4 should be caused by node 5.
expect(_upstreamCauseNode(n2), same(n1));
expect(_upstreamCauseNode(n4), same(n5));
}
void test_propagation_upstream_through_union() {
// always -> 1
// always -> 2
// always -> 3
// 1 -(hard)-> 2
// 2 == 3
// 3 -(hard)-> never
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
var edge_always_1 = connect(always, n1);
var edge_always_2 = connect(always, n2);
var edge_always_3 = connect(always, n3);
connect(n1, n2, hard: true);
union(n2, n3);
connect(n3, never, hard: true);
propagate();
expect(n1.isNullable, false);
expect(n2.isNullable, false);
expect(n3.isNullable, false);
assertUnsatisfied([edge_always_1, edge_always_2, edge_always_3]);
}
void test_propagation_upstream_through_union_reversed() {
// always -> 1
// always -> 2
// always -> 3
// 1 -(hard)-> 2
// 3 == 2
// 3 -(hard)-> never
var n1 = newNode(1);
var n2 = newNode(2);
var n3 = newNode(3);
var edge_always_1 = connect(always, n1);
var edge_always_2 = connect(always, n2);
var edge_always_3 = connect(always, n3);
connect(n1, n2, hard: true);
union(n3, n2);
connect(n3, never, hard: true);
propagate();
expect(n1.isNullable, false);
expect(n2.isNullable, false);
expect(n3.isNullable, false);
assertUnsatisfied([edge_always_1, edge_always_2, edge_always_3]);
}
void test_satisfied_edge_destination_nullable() {
var n1 = newNode(1);
var edge = connect(always, n1);
propagate();
assertUnsatisfied([]);
expect(edge.isSatisfied, true);
}
void test_satisfied_edge_source_non_nullable() {
var n1 = newNode(1);
var n2 = newNode(1);
var edge = connect(n1, n2);
propagate();
assertUnsatisfied([]);
expect(edge.isSatisfied, true);
}
void test_satisfied_edge_two_sources_first_non_nullable() {
var n1 = newNode(1);
var n2 = newNode(1);
connect(always, n2);
var edge = connect(n1, never, guards: [n2]);
propagate();
assertUnsatisfied([]);
expect(edge.isSatisfied, true);
}
void test_satisfied_edge_two_sources_second_non_nullable() {
var n1 = newNode(1);
var n2 = newNode(1);
connect(always, n1);
var edge = connect(n1, never, guards: [n2]);
propagate();
assertUnsatisfied([]);
expect(edge.isSatisfied, true);
}
void test_substitution_simplify_null() {
var n1 = newNode(1);
expect(subst(null, n1), same(n1));
expect(subst(n1, null), same(n1));
}
void test_substitutionNode_relatesInBothDirections() {
final nodeA = newNode(1);
final nodeB = newNode(2);
final substNode = subst(nodeA, nodeB);
expect(nodeA.outerCompoundNodes, [substNode]);
expect(nodeB.outerCompoundNodes, [substNode]);
}
void test_unconstrainted_node_non_nullable() {
var n1 = newNode(1);
propagate();
expect(n1.isNullable, false);
assertUnsatisfied([]);
}
void test_unsatisfied_edge_multiple_sources() {
var n1 = newNode(1);
connect(always, n1);
var edge = connect(always, never, guards: [n1]);
propagate();
assertUnsatisfied([edge]);
expect(edge.isSatisfied, false);
}
void test_unsatisfied_edge_single_source() {
var edge = connect(always, never);
propagate();
assertUnsatisfied([edge]);
expect(edge.isSatisfied, false);
}
void union(NullabilityNode x, NullabilityNode y) {
graph.union(x, y, _TestEdgeOrigin());
}
NullabilityNode _downstreamCauseNode(NullabilityNode node) =>
(node.whyNullable as SimpleDownstreamPropagationStep).edge.sourceNode;
NullabilityNode _upstreamCauseNode(NullabilityNode node) =>
node.whyNotNullable.principalCause.node;
}
class _TestEdgeOrigin implements EdgeOrigin {
@override
CodeReference get codeReference => null;
@override
String get description => 'Test edge';
@override
EdgeOriginKind get kind => null;
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}