32e27f68dc
A nullability node of `null` will be discarded during a substitution. For instance, attempting to substitute `T=int?null` into `T?1` will produce `int?1`, rather than `int?2` where `2` is a substitution node. We will need this functionality in order to compare function types for equality, which is in turn needed to integrate with flow analysis. Change-Id: Ife5e3761b083d4584b2aff760dcb104529e7f9d4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/112745 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
632 lines
17 KiB
Dart
632 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 extends EdgeOrigin {}
|