Files
sdk/pkg/nnbd_migration/test/nullability_node_test.dart
T
Paul Berry 233189e598 Migration: clean up EdgeOrigin class hierarchy.
This CL combines the EdgeOrigin and EdgeOriginWithLocation classes, so
that all EdgeOrigin objects are associated with source locations.  And
it separates the EdgeOrigin and PotentialModification roles of the
ExpressionChecks class into two separate classes.

This will enable a follow-up CL that switches the EdgeOrigin class
over to track source locations via AST nodes rather than offsets,
which will in turn allow rich information to be delivered to the
client about how the nullability graph relates to source code.

Change-Id: I736f60f93681fbbfbd5f9ee9a5589f99f819b9aa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/117282
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
2019-09-16 18:31:08 +00:00

634 lines
17 KiB
Dart

// 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/src/edge_origin.dart';
import 'package:nnbd_migration/src/nullability_node.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();
List<NullabilityEdge> unsatisfiedEdges;
List<NullabilityNodeForSubstitution> unsatisfiedSubstitutions;
NullabilityNode get always => graph.always;
NullabilityNode get never => graph.never;
void assertUnsatisfied(List<NullabilityEdge> expectedUnsatisfiedEdges) {
expect(unsatisfiedEdges, unorderedEquals(expectedUnsatisfiedEdges));
}
NullabilityEdge connect(NullabilityNode source, NullabilityNode destination,
{bool hard = false, List<NullabilityNode> guards = const []}) {
return graph.connect(source, destination, _TestEdgeOrigin(),
hard: hard, guards: guards);
}
NullabilityNode lub(NullabilityNode left, NullabilityNode right) {
return NullabilityNode.forLUB(left, right);
}
NullabilityNode newNode(int offset) =>
NullabilityNode.forTypeAnnotation(offset);
void propagate() {
graph.propagate();
unsatisfiedEdges = graph.unsatisfiedEdges.toList();
unsatisfiedSubstitutions = graph.unsatisfiedSubstitutions.toList();
}
NullabilityNode subst(NullabilityNode inner, NullabilityNode outer) {
return NullabilityNode.forSubstitution(inner, outer);
}
test_always_and_never_state() {
propagate();
expect(always.isNullable, isTrue);
expect(never.isNullable, isFalse);
assertUnsatisfied([]);
}
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]);
}
test_always_and_never_unaffected_by_soft_edges() {
var edge = connect(always, never);
propagate();
expect(always.isNullable, isTrue);
expect(never.isNullable, isFalse);
assertUnsatisfied([edge]);
}
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([]);
}
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([]);
}
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([]);
}
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]);
}
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]);
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
test_propagation_downstream_reverse_substitution_exact() {
// always -> subst(1, 2)
// 3 -> 1
// 4 -> 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);
connect(n4, n3);
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);
}
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);
}
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);
}
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(unsatisfiedSubstitutions, hasLength(1));
expect(unsatisfiedSubstitutions[0], same(substitutionNode));
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
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([]);
}
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]);
}
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]);
}
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]);
}
test_satisfied_edge_destination_nullable() {
var n1 = newNode(1);
var edge = connect(always, n1);
propagate();
assertUnsatisfied([]);
expect(edge.isSatisfied, true);
}
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);
}
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);
}
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);
}
test_substitution_simplify_null() {
var n1 = newNode(1);
expect(subst(null, n1), same(n1));
expect(subst(n1, null), same(n1));
}
test_unconstrainted_node_non_nullable() {
var n1 = newNode(1);
propagate();
expect(n1.isNullable, false);
assertUnsatisfied([]);
}
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);
}
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());
}
}
class _TestEdgeOrigin implements EdgeOrigin {
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}