// 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 unsatisfiedEdges; List unsatisfiedSubstitutions; NullabilityNode get always => graph.always; NullabilityNode get never => graph.never; void assertUnsatisfied(List expectedUnsatisfiedEdges) { expect(unsatisfiedEdges.toSet(), expectedUnsatisfiedEdges.toSet()); } NullabilityEdge connect(NullabilityNode source, NullabilityNode destination, {bool hard = false, bool checkable = true, List 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); }