blob: 1885167e9337e7858d02df6def78c9215fe3bf0d [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:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/dart/element/type_system.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart' show TypeSystemImpl;
import 'package:analyzer/src/dart/error/hint_codes.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/testing/test_type_provider.dart';
import 'package:nnbd_migration/fix_reason_target.dart';
import 'package:nnbd_migration/instrumentation.dart';
import 'package:nnbd_migration/src/decorated_class_hierarchy.dart';
import 'package:nnbd_migration/src/decorated_type.dart';
import 'package:nnbd_migration/src/edge_builder.dart';
import 'package:nnbd_migration/src/edge_origin.dart';
import 'package:nnbd_migration/src/expression_checks.dart';
import 'package:nnbd_migration/src/nullability_node.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'migration_visitor_test_base.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(AssignmentCheckerTest);
defineReflectiveTests(EdgeBuilderTest);
});
}
@reflectiveTest
class AssignmentCheckerTest extends Object
with EdgeTester, DecoratedTypeTester {
static const EdgeOrigin origin = _TestEdgeOrigin();
LibraryElementImpl _myLibrary;
ClassElement _myListOfListClass;
DecoratedType _myListOfListSupertype;
@override
final TypeProvider typeProvider;
@override
final NullabilityGraphForTesting graph;
@override
final decoratedTypeParameterBounds = DecoratedTypeParameterBounds();
final AssignmentCheckerForTesting checker;
factory AssignmentCheckerTest() {
var typeProvider = TestTypeProvider().asLegacy;
_setCoreLibrariesTypeSystem(typeProvider);
var graph = NullabilityGraphForTesting();
var decoratedClassHierarchy = _DecoratedClassHierarchyForTesting();
var checker = AssignmentCheckerForTesting(
TypeSystemImpl(
implicitCasts: true,
isNonNullableByDefault: false,
strictInference: false,
typeProvider: typeProvider,
),
typeProvider,
graph,
decoratedClassHierarchy);
var assignmentCheckerTest =
AssignmentCheckerTest._(typeProvider, graph, checker);
decoratedClassHierarchy.assignmentCheckerTest = assignmentCheckerTest;
return assignmentCheckerTest;
}
AssignmentCheckerTest._(this.typeProvider, this.graph, this.checker);
void assign(DecoratedType source, DecoratedType destination,
{bool hard = false}) {
checker.checkAssignment(origin,
source: source, destination: destination, hard: hard);
}
DecoratedType myListOfList(DecoratedType elementType) {
_initMyLibrary();
if (_myListOfListClass == null) {
var t = typeParameter('T', object());
_myListOfListSupertype = list(list(typeParameterType(t)));
_myListOfListClass = ClassElementImpl('MyListOfList', 0)
..enclosingElement = _myLibrary.definingCompilationUnit
..typeParameters = [t]
..supertype = _myListOfListSupertype.type as InterfaceType;
}
return DecoratedType(
InterfaceTypeImpl(
element: _myListOfListClass,
typeArguments: [elementType.type],
nullabilitySuffix: NullabilitySuffix.star,
),
newNode(),
typeArguments: [elementType],
);
}
void test_bottom_to_generic() {
var t = list(object());
assign(bottom, t);
assertEdge(never, t.node, hard: false);
assertNoEdge(anyNode, t.typeArguments[0].node);
}
void test_bottom_to_simple() {
var t = object();
assign(bottom, t);
assertEdge(never, t.node, hard: false);
}
void test_complex_to_typeParam() {
var bound = list(object());
var t1 = list(object());
var t2 = typeParameterType(typeParameter('T', bound));
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
assertEdge(t1.node, bound.node, hard: false);
// TODO(40622): Should this be a checkable edge?
assertEdge(t1.typeArguments[0].node, bound.typeArguments[0].node,
hard: false, checkable: false);
}
void test_dynamic_to_dynamic() {
assign(dynamic_, dynamic_);
// Note: no assertions to do; just need to make sure there wasn't a crash.
}
void test_dynamic_to_void() {
assign(dynamic_, void_);
// Note: no assertions to do; just need to make sure there wasn't a crash.
}
void test_function_type_named_parameter() {
var t1 = function(dynamic_, named: {'x': object()});
var t2 = function(dynamic_, named: {'x': object()});
assign(t1, t2, hard: true);
// Note: t1 and t2 are swapped due to contravariance.
assertEdge(t2.namedParameters['x'].node, t1.namedParameters['x'].node,
hard: false, checkable: false);
}
void test_function_type_named_to_no_parameter() {
var t1 = function(dynamic_, named: {'x': object()});
var t2 = function(dynamic_);
assign(t1, t2);
// Note: no assertions to do; just need to make sure there wasn't a crash.
}
void test_function_type_positional_parameter() {
var t1 = function(dynamic_, positional: [object()]);
var t2 = function(dynamic_, positional: [object()]);
assign(t1, t2, hard: true);
// Note: t1 and t2 are swapped due to contravariance.
assertEdge(t2.positionalParameters[0].node, t1.positionalParameters[0].node,
hard: false, checkable: false);
}
void test_function_type_positional_to_no_parameter() {
var t1 = function(dynamic_, positional: [object()]);
var t2 = function(dynamic_);
assign(t1, t2);
// Note: no assertions to do; just need to make sure there wasn't a crash.
}
void test_function_type_positional_to_required_parameter() {
var t1 = function(dynamic_, positional: [object()]);
var t2 = function(dynamic_, required: [object()]);
assign(t1, t2, hard: true);
// Note: t1 and t2 are swapped due to contravariance.
assertEdge(t2.positionalParameters[0].node, t1.positionalParameters[0].node,
hard: false, checkable: false);
}
void test_function_type_required_parameter() {
var t1 = function(dynamic_, required: [object()]);
var t2 = function(dynamic_, required: [object()]);
assign(t1, t2);
// Note: t1 and t2 are swapped due to contravariance.
assertEdge(t2.positionalParameters[0].node, t1.positionalParameters[0].node,
hard: false, checkable: false);
}
void test_function_type_return_type() {
var t1 = function(object());
var t2 = function(object());
assign(t1, t2, hard: true);
assertEdge(t1.returnType.node, t2.returnType.node,
hard: false, checkable: false);
}
void test_function_void_to_function_object() {
// This is not an ideal pattern, but void is assignable to Object in certain
// cases such as those with compound types here. We must support it.
var t1 = function(void_);
var t2 = function(object());
assign(t1, t2, hard: true);
assertEdge(t1.returnType.node, t2.returnType.node,
hard: false, checkable: false);
}
void test_future_int_to_future_or_int() {
var t1 = future(int_());
var t2 = futureOr(int_());
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
assertEdge(t1.typeArguments[0].node, t2.typeArguments[0].node,
hard: false, checkable: false);
}
void test_future_or_int_to_future_int() {
var t1 = futureOr(int_());
var t2 = future(int_());
assign(t1, t2, hard: true);
// FutureOr<int>? is nullable, so Future<int>? should be.
assertEdge(t1.node, t2.node, hard: true);
// FutureOr<int?> is nullable, so Future<int>? should be.
assertEdge(t1.typeArguments[0].node, t2.node, hard: true);
// FutureOr<int?> may hold a Future<int?>, so carry that forward.
assertEdge(t1.typeArguments[0].node, t2.typeArguments[0].node, hard: false);
// FutureOr<int>? does not accept a Future<int?>, so don't draw this.
assertNoEdge(t1.node, t2.typeArguments[0].node);
}
void test_future_or_int_to_int() {
var t1 = futureOr(int_());
var t2 = int_();
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
assertEdge(t1.typeArguments[0].node, t2.node, hard: false);
}
void test_future_or_list_object_to_list_int() {
var t1 = futureOr(list(object()));
var t2 = list(int_());
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
assertEdge(t1.typeArguments[0].node, t2.node, hard: false);
assertEdge(
t1.typeArguments[0].typeArguments[0].node, t2.typeArguments[0].node,
hard: false);
}
void test_future_or_object_to_future_or_int() {
var t1 = futureOr(object());
var t2 = futureOr(int_());
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
assertEdge(t1.typeArguments[0].node, t2.typeArguments[0].node, hard: false);
}
void test_future_or_to_future_or() {
var t1 = futureOr(int_());
var t2 = futureOr(int_());
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
assertEdge(t1.typeArguments[0].node, t2.typeArguments[0].node, hard: false);
}
void test_generic_to_dynamic() {
var t = list(object());
assign(t, dynamic_);
assertEdge(t.node, always, hard: false);
assertNoEdge(t.typeArguments[0].node, anyNode);
}
void test_generic_to_generic_downcast() {
var t1 = list(list(object()));
var t2 = myListOfList(object());
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
// Let A, B, and C be nullability nodes such that:
// - t2 is MyListOfList<Object?A>
var a = t2.typeArguments[0].node;
// - t1 is List<List<Object?B>>
var b = t1.typeArguments[0].typeArguments[0].node;
// - the supertype of MyListOfList<T> is List<List<T?C>>
var c = _myListOfListSupertype.typeArguments[0].typeArguments[0].node;
// Then there should be an edge from b to substitute(a, c)
assertEdge(b, substitutionNode(a, c), hard: false);
}
void test_generic_to_generic_downcast_of_type_parameter() {
var t = typeParameterType(typeParameter('T', object()));
var t1 = iterable(t);
var t2 = list(t);
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
var a = t1.typeArguments[0].node;
var b = t2.typeArguments[0].node;
assertEdge(a, b, hard: false);
}
void test_generic_to_generic_downcast_same_element() {
var t1 = list(object());
var t2 = list(int_());
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
assertEdge(t1.typeArguments[0].node, t2.typeArguments[0].node, hard: false);
}
void test_generic_to_generic_same_element() {
var t1 = list(object());
var t2 = list(object());
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
assertEdge(t1.typeArguments[0].node, t2.typeArguments[0].node,
hard: false, checkable: false);
}
void test_generic_to_generic_upcast() {
var t1 = myListOfList(object());
var t2 = list(list(object()));
assign(t1, t2);
assertEdge(t1.node, t2.node, hard: false);
// Let A, B, and C be nullability nodes such that:
// - t1 is MyListOfList<Object?A>
var a = t1.typeArguments[0].node;
// - t2 is List<List<Object?B>>
var b = t2.typeArguments[0].typeArguments[0].node;
// - the supertype of MyListOfList<T> is List<List<T?C>>
var c = _myListOfListSupertype.typeArguments[0].typeArguments[0].node;
// Then there should be an edge from substitute(a, c) to b.
assertEdge(substitutionNode(a, c), b, hard: false, checkable: false);
}
void test_generic_to_object() {
var t1 = list(object());
var t2 = object();
assign(t1, t2);
assertEdge(t1.node, t2.node, hard: false);
assertNoEdge(t1.typeArguments[0].node, anyNode);
}
void test_generic_to_void() {
var t = list(object());
assign(t, void_);
assertEdge(t.node, always, hard: false);
assertNoEdge(t.typeArguments[0].node, anyNode);
}
void test_int_to_future_or_int() {
var t1 = int_();
var t2 = futureOr(int_());
assign(t1, t2, hard: true);
// Note: given code like:
// int x = null;
// FutureOr<int> y = x;
// There are two possible migrations for `FutureOr<int>`: we could change it
// to either `FutureOr<int?>` or `FutureOr<int>?`. We choose to do
// `FutureOr<int>?` because it is a narrower type, so it is less likely to
// cause a proliferation of nullable types in the user's program.
assertEdge(t1.node, t2.node, hard: true);
assertNoEdge(t1.node, t2.typeArguments[0].node);
}
void test_iterable_object_to_list_void() {
assign(iterable(object()), list(void_));
// Note: no assertions to do; just need to make sure there wasn't a crash.
}
void test_null_to_generic() {
var t = list(object());
assign(null_, t);
assertEdge(always, t.node, hard: false);
assertNoEdge(anyNode, t.typeArguments[0].node);
}
void test_null_to_simple() {
var t = object();
assign(null_, t);
assertEdge(always, t.node, hard: false);
}
void test_object_to_void() {
assign(object(), void_);
// Note: no assertions to do; just need to make sure there wasn't a crash.
}
void test_simple_to_dynamic() {
var t = object();
assign(t, dynamic_);
assertEdge(t.node, always, hard: false);
}
void test_simple_to_simple() {
var t1 = object();
var t2 = object();
assign(t1, t2);
assertEdge(t1.node, t2.node, hard: false);
}
void test_simple_to_simple_hard() {
var t1 = object();
var t2 = object();
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
}
void test_simple_to_void() {
var t = object();
assign(t, void_);
assertEdge(t.node, always, hard: false);
}
void test_typeParam_to_complex() {
var bound = list(object());
var t1 = typeParameterType(typeParameter('T', bound));
var t2 = list(object());
assign(t1, t2, hard: true);
assertEdge(t1.node, t2.node, hard: true);
assertEdge(bound.node, t2.node, hard: false);
// TODO(40622): Should this be a checkable edge?
assertEdge(bound.typeArguments[0].node, t2.typeArguments[0].node,
hard: false, checkable: false);
}
void test_typeParam_to_object() {
var t1 = typeParameterType(typeParameter('T', object()));
var t2 = object();
assign(t1, t2);
assertEdge(t1.node, t2.node, hard: false);
}
void test_typeParam_to_typeParam() {
var t = typeParameter('T', object());
var t1 = typeParameterType(t);
var t2 = typeParameterType(t);
assign(t1, t2);
assertEdge(t1.node, t2.node, hard: false);
}
@override
TypeParameterElement typeParameter(String name, DecoratedType bound) {
var t = super.typeParameter(name, bound);
checker.bounds[t] = bound;
return t;
}
void _initMyLibrary() {
if (_myLibrary != null) {
return;
}
var coreLibrary = typeProvider.boolElement.library as LibraryElementImpl;
var analysisContext = coreLibrary.context;
var analysisSession = coreLibrary.session;
var typeSystem = coreLibrary.typeSystem;
var uriStr = 'package:test/test.dart';
_myLibrary = LibraryElementImpl(
analysisContext,
analysisSession,
uriStr,
-1,
0,
FeatureSet.fromEnableFlags2(
sdkLanguageVersion: Version.parse('2.10.0'),
flags: [EnableString.non_nullable],
),
);
_myLibrary.typeSystem = typeSystem;
_myLibrary.typeProvider = coreLibrary.typeProvider;
var uri = Uri.parse(uriStr);
var source = _MockSource(uri);
var definingUnit = CompilationUnitElementImpl();
definingUnit.source = source;
definingUnit.librarySource = source;
definingUnit.enclosingElement = _myLibrary;
_myLibrary.definingCompilationUnit = definingUnit;
}
static void _setCoreLibrariesTypeSystem(TypeProviderImpl typeProvider) {
var typeSystem = TypeSystemImpl(
isNonNullableByDefault: false,
implicitCasts: true,
strictInference: false,
typeProvider: typeProvider,
);
_setLibraryTypeSystem(
typeProvider.objectElement.library,
typeProvider,
typeSystem,
);
_setLibraryTypeSystem(
typeProvider.futureElement.library,
typeProvider,
typeSystem,
);
}
static void _setLibraryTypeSystem(
LibraryElement libraryElement,
TypeProvider typeProvider,
TypeSystem typeSystem,
) {
var libraryElementImpl = libraryElement as LibraryElementImpl;
libraryElementImpl.typeProvider = typeProvider as TypeProviderImpl;
libraryElementImpl.typeSystem = typeSystem as TypeSystemImpl;
}
}
@reflectiveTest
class EdgeBuilderTest extends EdgeBuilderTestBase {
void assertGLB(
NullabilityNode node, NullabilityNode left, NullabilityNode right) {
expect(node, isNot(TypeMatcher<NullabilityNodeForLUB>()));
assertEdge(left, node, hard: false, guards: [right]);
assertEdge(node, left, hard: false);
assertEdge(node, right, hard: false);
}
void assertLUB(NullabilityNode node, Object left, Object right) {
var conditionalNode = node as NullabilityNodeForLUB;
var leftMatcher = NodeMatcher(left);
var rightMatcher = NodeMatcher(right);
expect(leftMatcher.matches(conditionalNode.left), true);
expect(rightMatcher.matches(conditionalNode.right), true);
}
/// Checks that there are no nullability nodes upstream from [node] that could
/// cause it to become nullable.
void assertNoUpstreamNullability(NullabilityNode node) {
// Store `neverClosure` in a local variable so that we avoid the
// computational expense of recomputing it each time through the loop below.
var neverClosure = this.neverClosure;
// Any node with a hard edge to never (or never itself) won't become
// nullable, even if it has nodes upstream from it.
if (neverClosure.contains(node)) return;
// Otherwise, make sure that every node directly upstream from this node
// has a hard edge to never.
for (var edge in getEdges(anyNode, node)) {
expect(neverClosure, contains(edge.sourceNode));
}
}
/// Verifies that a null check will occur when the given edge is unsatisfied.
///
/// [expressionChecks] is the object tracking whether or not a null check is
/// needed.
void assertNullCheck(
ExpressionChecksOrigin expressionChecks, NullabilityEdge expectedEdge) {
expect(expressionChecks.checks.edges.values, contains(expectedEdge));
}
/// Gets the [ExpressionChecks] associated with the expression whose text
/// representation is [text], or `null` if the expression has no
/// [ExpressionChecks] associated with it.
ExpressionChecksOrigin checkExpression(String text) {
return variables.checkExpression(findNode.expression(text));
}
/// Gets the [DecoratedType] associated with the expression whose text
/// representation is [text], or `null` if the expression has no
/// [DecoratedType] associated with it.
DecoratedType decoratedExpressionType(String text) {
return variables.decoratedExpressionType(findNode.expression(text));
}
bool hasNullCheckHint(Expression expression) =>
variables.getNullCheckHint(testSource, expression) != null;
Future<void> test_already_migrated_field() async {
await analyze('''
double f() => double.NAN;
''');
var nanElement = typeProvider.doubleType.element.getField('NAN');
assertEdge(variables.decoratedElementType(nanElement).node,
decoratedTypeAnnotation('double f').node,
hard: false);
}
Future<void> test_ArgumentError_checkNotNull_not_postDominating() async {
await analyze('''
void f(bool b, int i, int j) {
ArgumentError.checkNotNull(j);
if (b) return;
ArgumentError.checkNotNull(i);
}
''');
// Asserts after ifs don't demonstrate non-null intent.
assertNoEdge(decoratedTypeAnnotation('int i').node, never);
// But asserts before ifs do
assertEdge(decoratedTypeAnnotation('int j').node, never, hard: true);
}
Future<void> test_ArgumentError_checkNotNull_postDominating() async {
await analyze('''
void f(int i) {
ArgumentError.checkNotNull(i);
}
''');
assertEdge(decoratedTypeAnnotation('int i').node, never, hard: true);
}
Future<void> test_ArgumentError_checkNotNull_prefixed() async {
await analyze('''
import 'dart:core' as core;
void f(core.int i) {
core.ArgumentError.checkNotNull(i);
}
''');
assertEdge(decoratedTypeAnnotation('int i').node, never, hard: true);
}
Future<void> test_as_dynamic() async {
await analyze('''
void f(Object o) {
(o as dynamic).gcd(1);
}
''');
assertEdge(decoratedTypeAnnotation('Object o').node,
decoratedTypeAnnotation('dynamic').node,
hard: true);
assertEdge(decoratedTypeAnnotation('dynamic').node, never, hard: true);
}
Future<void> test_as_int() async {
await analyze('''
void f(Object o) {
(o as int).gcd(1);
}
''');
assertEdge(decoratedTypeAnnotation('Object o').node,
decoratedTypeAnnotation('int').node,
hard: true);
assertEdge(decoratedTypeAnnotation('int').node, never, hard: true);
expect(
variables.wasUnnecessaryCast(testSource, findNode.as_('o as')), false);
}
Future<void> test_as_int_null_ok() async {
await analyze('''
void f(Object o) {
(o as int)?.gcd(1);
}
''');
assertEdge(decoratedTypeAnnotation('Object o').node,
decoratedTypeAnnotation('int').node,
hard: true);
assertNoEdge(decoratedTypeAnnotation('int').node, never);
}
Future<void> test_as_int_unnecessary() async {
verifyNoTestUnitErrors = false;
await analyze('''
void f(int i) {
(i as int).gcd(1);
}
''');
expect(
testAnalysisResult.errors.single.errorCode, HintCode.UNNECESSARY_CAST);
assertEdge(decoratedTypeAnnotation('int i').node,
decoratedTypeAnnotation('int)').node,
hard: true);
assertEdge(decoratedTypeAnnotation('int)').node, never, hard: true);
expect(
variables.wasUnnecessaryCast(testSource, findNode.as_('i as')), true);
}
Future<void> test_as_side_cast() async {
await analyze('''
class A {}
class B {}
class C implements A, B {}
B f(A a) {
// possible via f(C());
return a as B;
}
''');
assertEdge(
decoratedTypeAnnotation('A a').node, decoratedTypeAnnotation('B;').node,
hard: true);
}
Future<void> test_as_side_cast_generics() async {
await analyze('''
class A<T> {}
class B<T> {}
class C implements A<int>, B<bool> {}
B<bool> f(A<int> a) {
// possible via f(C());
return a as B<bool>;
}
''');
assertEdge(decoratedTypeAnnotation('A<int> a').node,
decoratedTypeAnnotation('B<bool>;').node,
hard: true);
assertEdge(decoratedTypeAnnotation('bool>;').node,
decoratedTypeAnnotation('bool> f').node,
hard: false, checkable: false);
assertNoEdge(anyNode, decoratedTypeAnnotation('bool>;').node);
assertNoEdge(anyNode, decoratedTypeAnnotation('int> a').node);
// int> a should be connected to the bound of T in A<T>, but nothing else.
expect(
decoratedTypeAnnotation('int> a').node.downstreamEdges, hasLength(1));
}
Future<void> test_assert_demonstrates_non_null_intent() async {
await analyze('''
void f(int i) {
assert(i != null);
}
''');
assertEdge(decoratedTypeAnnotation('int i').node, never, hard: true);
}
Future<void> test_assert_initializer_demonstrates_non_null_intent() async {
await analyze('''
class C {
C(int i)
: assert(i != null);
}
''');
assertEdge(decoratedTypeAnnotation('int i').node, never, hard: true);
}
Future<void> test_assert_is_demonstrates_non_null_intent() async {
// Note, this could also be handled via improved flow analysis rather than a
// hard edge.
await analyze('''
void f(dynamic i) {
assert(i is int);
}
''');
assertEdge(decoratedTypeAnnotation('dynamic i').node, never, hard: true);
}
Future<void> test_assign_bound_to_type_parameter() async {
await analyze('''
class C<T extends List<int>> {
T f(List<int> x) => x;
}
''');
var boundType = decoratedTypeAnnotation('List<int>>');
var parameterType = decoratedTypeAnnotation('List<int> x');
var tType = decoratedTypeAnnotation('T f');
assertEdge(parameterType.node, tType.node, hard: true);
assertEdge(parameterType.node, boundType.node, hard: false);
// TODO(mfairhurst): Confirm we want this edge.
// TODO(40622): Should this be a checkable edge?
assertEdge(
parameterType.typeArguments[0].node, boundType.typeArguments[0].node,
hard: false, checkable: false);
}
Future<void> test_assign_dynamic_to_other_type() async {
await analyze('''
int f(dynamic d) => d;
''');
// There is no explicit null check necessary, since `dynamic` is
// downcastable to any type, nullable or not.
expect(checkExpression('d;'), isNull);
// But we still create an edge, to make sure that the possibility of `null`
// propagates to callees.
assertEdge(decoratedTypeAnnotation('dynamic').node,
decoratedTypeAnnotation('int').node,
hard: true);
}
Future<void> test_assign_function_type_to_function_interface_type() async {
await analyze('''
Function f(void Function() x) => x;
''');
assertEdge(decoratedGenericFunctionTypeAnnotation('void Function()').node,
decoratedTypeAnnotation('Function f').node,
hard: true);
}
Future<void> test_assign_future_to_futureOr_complex() async {
await analyze('''
import 'dart:async';
FutureOr<List<int>> f(Future<List<int>> x) => x;
''');
// If `x` is `Future<List<int?>>`, then the only way to migrate is to make
// the return type `FutureOr<List<int?>>`.
assertEdge(decoratedTypeAnnotation('int>> x').node,
decoratedTypeAnnotation('int>> f').node,
hard: false, checkable: false);
assertNoEdge(decoratedTypeAnnotation('int>> x').node,
decoratedTypeAnnotation('List<int>> f').node);
assertNoEdge(decoratedTypeAnnotation('int>> x').node,
decoratedTypeAnnotation('FutureOr<List<int>> f').node);
}
Future<void> test_assign_future_to_futureOr_simple() async {
await analyze('''
import 'dart:async';
FutureOr<int> f(Future<int> x) => x;
''');
// If `x` is nullable, then there are two migrations possible: we could make
// the return type `FutureOr<int?>` or we could make it `FutureOr<int>?`.
// We choose `FutureOr<int>?` because it's strictly more conservative (it's
// a subtype of `FutureOr<int?>`).
assertEdge(decoratedTypeAnnotation('Future<int> x').node,
decoratedTypeAnnotation('FutureOr<int>').node,
hard: true);
assertNoEdge(decoratedTypeAnnotation('Future<int> x').node,
decoratedTypeAnnotation('int> f').node);
// If `x` is `Future<int?>`, then the only way to migrate is to make the
// return type `FutureOr<int?>`.
assertEdge(
substitutionNode(
decoratedTypeAnnotation('int> x').node, inSet(pointsToNever)),
decoratedTypeAnnotation('int> f').node,
hard: false,
checkable: false);
assertNoEdge(decoratedTypeAnnotation('int> x').node,
decoratedTypeAnnotation('FutureOr<int>').node);
}
Future<void> test_assign_non_future_to_futureOr_complex() async {
await analyze('''
import 'dart:async';
FutureOr<List<int>> f(List<int> x) => x;
''');
// If `x` is `List<int?>`, then the only way to migrate is to make the
// return type `FutureOr<List<int?>>`.
assertEdge(decoratedTypeAnnotation('int> x').node,
decoratedTypeAnnotation('int>> f').node,
hard: false, checkable: false);
assertNoEdge(decoratedTypeAnnotation('int> x').node,
decoratedTypeAnnotation('List<int>> f').node);
assertNoEdge(decoratedTypeAnnotation('int> x').node,
decoratedTypeAnnotation('FutureOr<List<int>> f').node);
}
Future<void> test_assign_non_future_to_futureOr_simple() async {
await analyze('''
import 'dart:async';
FutureOr<int> f(int x) => x;
''');
// If `x` is nullable, then there are two migrations possible: we could make
// the return type `FutureOr<int?>` or we could make it `FutureOr<int>?`.
// We choose `FutureOr<int>?` because it's strictly more conservative (it's
// a subtype of `FutureOr<int?>`).
assertEdge(decoratedTypeAnnotation('int x').node,
decoratedTypeAnnotation('FutureOr<int>').node,
hard: true);
assertNoEdge(decoratedTypeAnnotation('int x').node,
decoratedTypeAnnotation('int>').node);
}
Future<void> test_assign_null_to_generic_type() async {
await analyze('''
main() {
List<int> x = null;
}
''');
// TODO(paulberry): edge should be hard.
assertEdge(inSet(alwaysPlus), decoratedTypeAnnotation('List').node,
hard: false);
}
Future<void> test_assign_to_bound_as() async {
// TODO(mfairhurst): support downcast to type params with bounds
await analyze('''
class C<T> {}
void f(Object o) {
o as C<int>;
}
''');
// For now, edge to `anyNode`, because the true bound is inferred.
assertEdge(decoratedTypeAnnotation('int').node, anyNode, hard: true);
}
Future<void> test_assign_to_bound_class_alias() async {
await analyze('''
class C<T extends Object/*1*/> {}
class D<T extends Object/*2*/> {}
mixin M<T extends Object/*3*/> {}
class F = C<int> with M<String> implements D<num>;
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object/*1*/').node,
hard: true);
assertEdge(decoratedTypeAnnotation('num').node,
decoratedTypeAnnotation('Object/*2*/').node,
hard: true);
assertEdge(decoratedTypeAnnotation('String').node,
decoratedTypeAnnotation('Object/*3*/').node,
hard: true);
}
Future<void> test_assign_to_bound_class_extends() async {
await analyze('''
class A<T extends Object> {}
class C extends A<int> {}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_class_implements() async {
await analyze('''
class A<T extends Object> {}
class C implements A<int> {}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_class_with() async {
await analyze('''
class A<T extends Object> {}
class C extends Object with A<int> {}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object>').node,
hard: true);
}
Future<void> test_assign_to_bound_extension_extended_type() async {
await analyze('''
class C<T extends Object> {}
extension E on C<int> {}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object>').node,
hard: true);
}
Future<void> test_assign_to_bound_field_formal_typed() async {
await analyze('''
class C<T extends Object> {}
class D {
dynamic i;
D(C<int> this.i);
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_field_formal_typed_function() async {
await analyze('''
class C<T extends Object> {}
class D {
dynamic i;
D(this.i(C<int> name));
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_for() async {
await analyze('''
class C<T extends Object> {}
void main() {
for (C<int> c = null ;;) {}
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object>').node,
hard: true);
}
Future<void> test_assign_to_bound_for_element() async {
await analyze('''
class C<T extends Object> {}
void main() {
[for (C<int> c = null ;;) c];
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object>').node,
hard: true);
}
Future<void> test_assign_to_bound_for_in() async {
await analyze('''
class C<T extends Object> {}
void main() {
for (C<int> c in []) {}
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object>').node,
hard: true);
}
Future<void> test_assign_to_bound_for_in_element() async {
await analyze('''
class C<T extends Object> {}
void main() {
[for (C<int> c in []) c];
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object>').node,
hard: true);
}
Future<void> test_assign_to_bound_function_invocation_type_argument() async {
await analyze('''
void f<T extends Object>() {}
void main() {
(f)<int>();
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_in_return_type() async {
await analyze('''
class C<T extends Object> {}
C<int> f() => null;
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_in_type_argument() async {
await analyze('''
class C<T extends Object> {}
C<C<int>> f() => null;
''');
assertEdge(decoratedTypeAnnotation('C<int>').node,
decoratedTypeAnnotation('Object').node,
hard: true);
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_instance_creation() async {
await analyze('''
class C<T extends Object> {}
void main() {
C<int>();
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_list_literal() async {
await analyze('''
class C<T extends Object> {}
void main() {
<C<int>>[];
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_local_variable() async {
await analyze('''
class C<T extends Object> {}
main() {
C<int> c = null;
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_map_literal() async {
await analyze('''
class C<T extends Object> {}
void main() {
<C<int>, C<String>>{};
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
assertEdge(decoratedTypeAnnotation('String').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_method_bound() async {
await analyze('''
class C<T extends Object> {}
class D {
f<U extends C<int>>() {}
}
''');
}
Future<void> test_assign_to_bound_method_call_type_argument() async {
await analyze('''
void f<T extends Object>() {}
void main() {
f<int>();
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_mixin_implements() async {
await analyze('''
class A<T extends Object> {}
mixin C implements A<int> {}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_mixin_on() async {
await analyze('''
class A<T extends Object> {}
mixin C on A<int> {}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_mixin_type_parameter_bound() async {
await analyze('''
class C<T extends Object> {}
mixin M<T extends C<int>> {}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_redirecting_constructor_argument() async {
await analyze('''
class A<T extends Object> {}
class C {
factory C() = D<A<int>>;
}
class D<U> implements C {}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_set_literal() async {
await analyze('''
class C<T extends Object> {}
void main() {
<C<int>>{};
}
''');
assertEdge(decoratedTypeAnnotation('int').node,
decoratedTypeAnnotation('Object').node,
hard: true);
}
Future<void> test_assign_to_bound_within_bound() async {
await analyze('''
class A<T extends Object> {}
class B<T extends A<int>> {}
''');
var aBound = decoratedTypeAnnotation('Object').node;
var aBoundInt = decoratedTypeAnnotation('int').node;
assertEdge(aBoundInt, aBound, hard: true);
}
Future<void> test_assign_to_bound_within_bound_method() async {
await analyze('''
class C<T extends Object> {}
void f<T extends C<int>>() {}
''');
var cBound = decoratedTypeAnnotation('Object').node;
var fcInt = decoratedTypeAnnotation('int').node;
assertEdge(fcInt, cBound, hard: true);
}
Future<void> test_assign_type_parameter_to_bound() async {
await analyze('''
class C<T extends List<int>> {
List<int> f(T x) => x;
}
''');
var boundType = decoratedTypeAnnotation('List<int>>');
var returnType = decoratedTypeAnnotation('List<int> f');
var tType = decoratedTypeAnnotation('T x');
assertEdge(tType.node, returnType.node, hard: true);
assertEdge(boundType.node, returnType.node, hard: false);
// TODO(40622): Should this be a checkable edge?
assertEdge(
boundType.typeArguments[0].node, returnType.typeArguments[0].node,
hard: false, checkable: false);
}
Future<void> test_assign_upcast_generic() async {
await analyze('''
void f(Iterable<int> x) {}
void g(List<int> x) {
f(x);
}
''');
var iterableInt = decoratedTypeAnnotation('Iterable<int>');
var listInt = decoratedTypeAnnotation('List<int>');
assertEdge(listInt.node, iterableInt.node, hard: true);
assertEdge(
substitutionNode(listInt.typeArguments[0].node, inSet(pointsToNever)),
iterableInt.typeArguments[0].node,
hard: false,
checkable: false);
}
Future<void> test_assignment_code_reference() async {
await analyze('''
void f(int i) {
int j = i;
}
''');
var edge = assertEdge(decoratedTypeAnnotation('int i').node,
decoratedTypeAnnotation('int j').node,
hard: true);
var codeReference = edge.codeReference;
expect(codeReference, isNotNull);
expect(codeReference.path, contains('test.dart'));
expect(codeReference.line, 2);
expect(codeReference.column, 11);
}
Future<void> test_assignmentExpression_compound_dynamic() async {
await analyze('''
void f(dynamic x, int y) {
x += y;
}
''');
// No assertions; just making sure this doesn't crash.
}
Future<void> test_assignmentExpression_compound_simple() async {
var code = '''
abstract class C {
C operator+(C x);
}
C f(C y, C z) => (y += z);
''';
await analyze(code);
var targetEdge = assertEdge(
decoratedTypeAnnotation('C y').node, inSet(pointsToNever),
hard: true);
expect(
(graph.getEdgeOrigin(targetEdge) as CompoundAssignmentOrigin)
.node
.operator
.offset,
code.indexOf('+='));
assertNullCheck(
checkExpression('z);'),
assertEdge(decoratedTypeAnnotation('C z').node,
decoratedTypeAnnotation('C x').node,
hard: true));
var operatorReturnEdge = assertEdge(
decoratedTypeAnnotation('C operator').node,
decoratedTypeAnnotation('C y').node,
hard: false);
expect(
(graph.getEdgeOrigin(operatorReturnEdge) as CompoundAssignmentOrigin)
.node
.operator
.offset,
code.indexOf('+='));
var fReturnEdge = assertEdge(decoratedTypeAnnotation('C operator').node,
decoratedTypeAnnotation('C f').node,
hard: false);
assertNullCheck(checkExpression('(y += z)'), fReturnEdge);
}
Future<void> test_assignmentExpression_compound_withSubstitution() async {
// Failing due to a side-cast from incorrectly instantiating the operator.
var code = '''
abstract class C<T> {
C<T> operator+(C<T> x);
}
C<int> f(C<int> y, C<int> z) => (y += z);
''';
await analyze(code);
var targetEdge = assertEdge(
decoratedTypeAnnotation('C<int> y').node, inSet(pointsToNever),
hard: true);
expect(
(graph.getEdgeOrigin(targetEdge) as CompoundAssignmentOrigin)
.node
.operator
.offset,
code.indexOf('+='));
assertNullCheck(
checkExpression('z);'),
assertEdge(decoratedTypeAnnotation('C<int> z').node,
decoratedTypeAnnotation('C<T> x').node,
hard: true));
var operatorReturnEdge = assertEdge(
decoratedTypeAnnotation('C<T> operator').node,
decoratedTypeAnnotation('C<int> y').node,
hard: false);
expect(
(graph.getEdgeOrigin(operatorReturnEdge) as CompoundAssignmentOrigin)
.node
.operator
.offset,
code.indexOf('+='));
var fReturnEdge = assertEdge(decoratedTypeAnnotation('C<T> operator').node,
decoratedTypeAnnotation('C<int> f').node,
hard: false);
assertNullCheck(checkExpression('(y += z)'), fReturnEdge);
}
Future<void> test_assignmentExpression_field() async {
await analyze('''
class C {
int x = 0;
}
void f(C c, int i) {
c.x = i;
}
''');
assertEdge(decoratedTypeAnnotation('int i').node,
decoratedTypeAnnotation('int x').node,
hard: true);
}
Future<void> test_assignmentExpression_field_cascaded() async {
await analyze('''
class C {
int x = 0;
}
void f(C c, int i) {
c..x = i;
}
''');
assertEdge(decoratedTypeAnnotation('int i').node,
decoratedTypeAnnotation('int x').node,
hard: true);
}
Future<void> test_assignmentExpression_field_target_check() async {
await analyze('''
class C {
int x = 0;
}
void f(C c, int i) {
c.x = i;
}
''');
assertNullCheck(checkExpression('c.x'),
assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true));
}
Future<void> test_assignmentExpression_field_target_check_cascaded() async {
await analyze('''
class C {
int x = 0;
}
void f(C c, int i) {
c..x = i;
}
''');
assertNullCheck(checkExpression('c..x'),
assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true));
}
Future<void> test_assignmentExpression_indexExpression_index() async {
await analyze('''
class C {
void operator[]=(int a, int b) {}
}
void f(C c, int i, int j) {
c[i] = j;
}
''');
assertEdge(decoratedTypeAnnotation('int i').node,
decoratedTypeAnnotation('int a').node,
hard: true);
}
Future<void> test_assignmentExpression_indexExpression_return_value() async {
await analyze('''
class C {
void operator[]=(int a, int b) {}
}
int f(C c, int i, int j) => c[i] = j;
''');
assertEdge(decoratedTypeAnnotation('int j').node,
decoratedTypeAnnotation('int f').node,
hard: false);
}
Future<void> test_assignmentExpression_indexExpression_target_check() async {
await analyze('''
class C {
void operator[]=(int a, int b) {}
}
void f(C c, int i, int j) {
c[i] = j;
}
''');
assertNullCheck(checkExpression('c['),
assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true));
}
Future<void> test_assignmentExpression_indexExpression_value() async {
await analyze('''
class C {
void operator[]=(int a, int b) {}
}
void f(C c, int i, int j) {
c[i] = j;
}
''');
assertEdge(decoratedTypeAnnotation('int j').node,
decoratedTypeAnnotation('int b').node,
hard: true);
}
Future<void>
test_assignmentExpression_nullAware_complex_contravariant() async {
await analyze('''
void Function(int) f(void Function(int) x, void Function(int) y) => x ??= y;
''');
var xNullable =
decoratedGenericFunctionTypeAnnotation('void Function(int) x').node;
var xParamNullable = decoratedTypeAnnotation('int) x').node;
var yParamNullable = decoratedTypeAnnotation('int) y').node;
var returnParamNullable = decoratedTypeAnnotation('int) f').node;
assertEdge(xParamNullable, yParamNullable,
hard: false, checkable: false, guards: [xNullable]);
assertEdge(returnParamNullable, xParamNullable,
hard: false, checkable: false);
}
Future<void> test_assignmentExpression_nullAware_complex_covariant() async {
await analyze('''
List<int> f(List<int> x, List<int> y) => x ??= y;
''');
var xNullable = decoratedTypeAnnotation('List<int> x').node;
var yNullable = decoratedTypeAnnotation('List<int> y').node;
var xElementNullable = decoratedTypeAnnotation('int> x').node;
var yElementNullable = decoratedTypeAnnotation('int> y').node;
var returnElementNullable = decoratedTypeAnnotation('int> f').node;
assertEdge(yNullable, xNullable, hard: false, guards: [xNullable]);
assertEdge(yElementNullable, xElementNullable,
hard: false, checkable: false, guards: [xNullable]);
assertEdge(xElementNullable, returnElementNullable,
hard: false, checkable: false);
}
Future<void> test_assignmentExpression_nullAware_simple() async {
await analyze('''
int f(int x, int y) => (x ??= y);
''');
var yNullable = decoratedTypeAnnotation('int y').node;
var xNullable = decoratedTypeAnnotation('int x').node;
var returnNullable = decoratedTypeAnnotation('int f').node;
var glbNode = decoratedExpressionType('(x ??= y)').node;
assertEdge(yNullable, xNullable, hard: false, guards: [xNullable]);
assertEdge(yNullable, glbNode, hard: false, guards: [xNullable]);
assertEdge(glbNode, xNullable, hard: false);
assertEdge(glbNode, yNullable, hard: false);
assertEdge(glbNode, returnNullable, hard: false);
}
Future<void> test_assignmentExpression_operands() async {
await analyze('''
void f(int i, int j) {
i = j;
}
''');
assertEdge(decoratedTypeAnnotation('int j').node,
decoratedTypeAnnotation('int i').node,
hard: true);
}
Future<void> test_assignmentExpression_return_value() async {
await analyze('''
void f(int i, int j) {
g(i = j);
}
void g(int k) {}
''');
assertEdge(decoratedTypeAnnotation('int j').node,
decoratedTypeAnnotation('int k').node,
hard: false);
}
Future<void> test_assignmentExpression_setter() async {
await analyze('''
class C {
void set s(int value) {}
}
void f(C c, int i) {
c.s = i;
}
''');
assertEdge(decoratedTypeAnnotation('int i').node,
decoratedTypeAnnotation('int value').node,
hard: true);
}
Future<void> test_assignmentExpression_setter_null_aware() async {
await analyze('''
class C {
void set s(int value) {}
}
int f(C c, int i) => (c?.s = i);
''');
var lubNode =
decoratedExpressionType('(c?.s = i)').node as NullabilityNodeForLUB;
expect(lubNode.left, same(decoratedTypeAnnotation('C c').node));
expect(lubNode.right, same(decoratedTypeAnnotation('int i').node));
assertEdge(lubNode, decoratedTypeAnnotation('int f').node, hard: false);
}
Future<void> test_assignmentExpression_setter_target_check() async {
await analyze('''
class C {
void set s(int value) {}
}
void f(C c, int i) {
c.s = i;
}
''');
assertNullCheck(checkExpression('c.s'),
assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true));
}
@failingTest
Future<void> test_awaitExpression_future_nonNullable() async {
await analyze('''
Future<void> f() async {
int x = await g();
}
Future<int> g() async => 3;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int').node);
}
@failingTest
Future<void> test_awaitExpression_future_nullable() async {
await analyze('''
Future<void> f() async {
int x = await g();
}
Future<int> g() async => null;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int').node);
}
Future<void> test_awaitExpression_nonFuture() async {
await analyze('''
Future<void> f() async {
int x = await 3;
}
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int').node);
}
Future<void> test_binaryExpression_ampersand_result_not_null() async {
await analyze('''
int f(int i, int j) => i & j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int f').node);
}
Future<void> test_binaryExpression_ampersandAmpersand() async {
await analyze('''
bool f(bool i, bool j) => i && j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('bool i').node);
}
Future<void> test_binaryExpression_bar_result_not_null() async {
await analyze('''
int f(int i, int j) => i | j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int f').node);
}
Future<void> test_binaryExpression_barBar() async {
await analyze('''
bool f(bool i, bool j) => i || j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('bool i').node);
}
Future<void> test_binaryExpression_caret_result_not_null() async {
await analyze('''
int f(int i, int j) => i ^ j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int f').node);
}
Future<void> test_binaryExpression_equal() async {
await analyze('''
bool f(int i, int j) => i == j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('bool f').node);
}
Future<void> test_binaryExpression_equal_null() async {
await analyze('''
void f(int i) {
if (i == null) {
g(i);
} else {
h(i);
}
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is known to be non-nullable at the site of
// the call to h()
assertNoEdge(iNode, kNode);
// But there is an edge from i to j
assertEdge(iNode, jNode, hard: false, guards: [iNode]);
}
Future<void> test_binaryExpression_equal_null_null() async {
await analyze('''
void f(int i) {
if (null == null) {
g(i);
}
}
void g(int j) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
assertEdge(iNode, jNode,
hard: false,
guards: TypeMatcher<Iterable<NullabilityNode>>()
.having((g) => g.single, 'single value', isIn(alwaysPlus)));
}
Future<void> test_binaryExpression_equal_null_yoda_condition() async {
await analyze('''
void f(int i) {
if (null == i) {
g(i);
} else {
h(i);
}
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is known to be non-nullable at the site of
// the call to h()
assertNoEdge(iNode, kNode);
// But there is an edge from i to j
assertEdge(iNode, jNode, hard: false, guards: [iNode]);
}
Future<void> test_binaryExpression_gt_result_not_null() async {
await analyze('''
bool f(int i, int j) => i > j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('bool f').node);
}
Future<void> test_binaryExpression_gtEq_result_not_null() async {
await analyze('''
bool f(int i, int j) => i >= j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('bool f').node);
}
Future<void> test_binaryExpression_gtGt_result_not_null() async {
await analyze('''
int f(int i, int j) => i >> j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int f').node);
}
Future<void> test_binaryExpression_left_dynamic() async {
await analyze('''
Object f(dynamic x, int y) => x + g(y);
int g(int z) => z;
''');
assertEdge(decoratedTypeAnnotation('int y').node,
decoratedTypeAnnotation('int z').node,
hard: true);
assertNoEdge(decoratedTypeAnnotation('int g').node, anyNode);
assertEdge(inSet(alwaysPlus), decoratedTypeAnnotation('Object f').node,
hard: false);
}
Future<void> test_binaryExpression_lt_result_not_null() async {
await analyze('''
bool f(int i, int j) => i < j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('bool f').node);
}
Future<void> test_binaryExpression_ltEq_result_not_null() async {
await analyze('''
bool f(int i, int j) => i <= j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('bool f').node);
}
Future<void> test_binaryExpression_ltLt_result_not_null() async {
await analyze('''
int f(int i, int j) => i << j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int f').node);
}
Future<void> test_binaryExpression_minus_result_not_null() async {
await analyze('''
int f(int i, int j) => i - j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int f').node);
}
Future<void> test_binaryExpression_notEqual() async {
await analyze('''
bool f(int i, int j) => i != j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('bool f').node);
}
Future<void> test_binaryExpression_notEqual_null() async {
await analyze('''
void f(int i) {
if (i != null) {
h(i);
} else {
g(i);
}
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is known to be non-nullable at the site of
// the call to h()
assertNoEdge(iNode, kNode);
// But there is an edge from i to j
assertEdge(iNode, jNode, hard: false, guards: [iNode]);
}
Future<void> test_binaryExpression_percent_result_not_null() async {
await analyze('''
int f(int i, int j) => i % j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int f').node);
}
Future<void> test_binaryExpression_plus_left_check() async {
await analyze('''
int f(int i, int j) => i + j;
''');
assertNullCheck(checkExpression('i +'),
assertEdge(decoratedTypeAnnotation('int i').node, never, hard: true));
}
Future<void> test_binaryExpression_plus_left_check_custom() async {
await analyze('''
class Int {
Int operator+(Int other) => this;
}
Int f(Int i, Int j) => i + j;
''');
assertNullCheck(checkExpression('i +'),
assertEdge(decoratedTypeAnnotation('Int i').node, never, hard: true));
}
Future<void> test_binaryExpression_plus_result_custom() async {
await analyze('''
class Int {
Int operator+(Int other) => this;
}
Int f(Int i, Int j) => (i + j);
''');
assertNullCheck(
checkExpression('(i + j)'),
assertEdge(decoratedTypeAnnotation('Int operator+').node,
decoratedTypeAnnotation('Int f').node,
hard: false));
}
Future<void> test_binaryExpression_plus_result_not_null() async {
await analyze('''
int f(int i, int j) => i + j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int f').node);
}
Future<void> test_binaryExpression_plus_right_check() async {
await analyze('''
int f(int i, int j) => i + j;
''');
assertNullCheck(
checkExpression('j;'),
assertEdge(decoratedTypeAnnotation('int j').node, inSet(pointsToNever),
hard: true));
}
Future<void> test_binaryExpression_plus_right_check_custom() async {
await analyze('''
class Int {
Int operator+(Int other) => this;
}
Int f(Int i, Int j) => i + j/*check*/;
''');
assertNullCheck(
checkExpression('j/*check*/'),
assertEdge(decoratedTypeAnnotation('Int j').node,
decoratedTypeAnnotation('Int other').node,
hard: true));
}
Future<void> test_binaryExpression_plus_substituted() async {
await analyze('''
class _C<T, U> {
T operator+(U u) => throw 'foo';
}
Object _f(_C<int, String> c, String s) => c + s;
''');
assertEdge(
decoratedTypeAnnotation('String s').node,
substitutionNode(decoratedTypeAnnotation('String>').node,
decoratedTypeAnnotation('U u').node),
hard: true);
assertEdge(
substitutionNode(decoratedTypeAnnotation('int,').node,
decoratedTypeAnnotation('T operator').node),
decoratedTypeAnnotation('Object _f').node,
hard: false);
}
Future<void> test_binaryExpression_questionQuestion() async {
await analyze('''
int f(int i, int j) => i ?? j;
''');
var left = decoratedTypeAnnotation('int i').node;
var right = decoratedTypeAnnotation('int j').node;
var expression = decoratedExpressionType('??').node;
assertEdge(right, expression, guards: [left], hard: false);
expect(expression.displayName, '?? operator (test.dart:1:24)');
}
Future<void>
test_binaryExpression_questionQuestion_genericReturnType() async {
await analyze('''
class C<E> {
C<E> operator +(C<E> c) => this;
}
C<int> f(C<int> i, C<int> j) => i ?? j;
''');
}
Future<void> test_binaryExpression_right_dynamic() async {
await analyze('''
class C {
C operator+(C other) => other;
}
C f(C x, dynamic y) => x + y;
''');
assertNullCheck(checkExpression('x +'),
assertEdge(decoratedTypeAnnotation('C x').node, never, hard: true));
assertEdge(decoratedTypeAnnotation('C operator').node,
decoratedTypeAnnotation('C f').node,
hard: false);
}
Future<void> test_binaryExpression_slash_result_not_null() async {
await analyze('''
double f(int i, int j) => i / j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('double f').node);
}
Future<void> test_binaryExpression_star_result_not_null() async {
await analyze('''
int f(int i, int j) => i * j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int f').node);
}
Future<void> test_binaryExpression_tildeSlash_result_not_null() async {
await analyze('''
int f(int i, int j) => i ~/ j;
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('int f').node);
}
Future<void> test_boolLiteral() async {
await analyze('''
bool f() {
return true;
}
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('bool').node);
}
Future<void> test_cascadeExpression() async {
await analyze('''
class C {
int x = 0;
}
C f(C c, int i) => c..x = i;
''');
assertEdge(decoratedTypeAnnotation('C c').node,
decoratedTypeAnnotation('C f').node,
hard: false);
}
Future<void> test_cast_type_used_as_non_nullable() async {
await analyze('''
void f(int/*!*/ i) {}
void g(num/*?*/ j) {
f(j as int);
}
''');
assertEdge(decoratedTypeAnnotation('int)').node,
decoratedTypeAnnotation('int/*!*/').node,
hard: true);
}
Future<void> test_catch_clause() async {
await analyze('''
foo() => 1;
main() {
try { foo(); } on Exception catch (e) { print(e); }
}
''');
// No assertions; just checking that it doesn't crash.
}
Future<void> test_catch_clause_no_type() async {
await analyze('''
foo() => 1;
main() {
try { foo(); } catch (e) { print(e); }
}
''');
// No assertions; just checking that it doesn't crash.
}
Future<void>
test_class_alias_synthetic_constructor_with_parameters_complex() async {
await analyze('''
class MyList<T> {}
class C {
C(MyList<int>/*1*/ x);
}
mixin M {}
class D = C with M;
D f(MyList<int>/*2*/ x) => D(x);
''');
var syntheticConstructor = findElement.unnamedConstructor('D');
var constructorType = variables.decoratedElementType(syntheticConstructor);
var constructorParameterType = constructorType.positionalParameters[0];
assertEdge(decoratedTypeAnnotation('MyList<int>/*2*/').node,
constructorParameterType.node,
hard: true);
assertEdge(decoratedTypeAnnotation('int>/*2*/').node,
constructorParameterType.typeArguments[0].node,
hard: false, checkable: false);
assertUnion(constructorParameterType.node,
decoratedTypeAnnotation('MyList<int>/*1*/').node);
assertUnion(constructorParameterType.typeArguments[0].node,
decoratedTypeAnnotation('int>/*1*/').node);
}
Future<void>
test_class_alias_synthetic_constructor_with_parameters_generic() async {
await analyze('''
class C<T> {
C(T t);
}
mixin M {}
class D<U> = C<U> with M;
''');
var syntheticConstructor = findElement.unnamedConstructor('D');
var constructorType = variables.decoratedElementType(syntheticConstructor);
var constructorParameterType = constructorType.positionalParameters[0];
assertUnion(
constructorParameterType.node, decoratedTypeAnnotation('T t').node);
}
Future<void>
test_class_alias_synthetic_constructor_with_parameters_named() async {
await analyze('''
class C {
C({int/*1*/ i});
}
mixin M {}
class D = C with M;
D f(int/*2*/ i) => D(i: i);
''');
var syntheticConstructor = findElement.unnamedConstructor('D');
var constructorType = variables.decoratedElementType(syntheticConstructor);
var constructorParameterType = constructorType.namedParameters['i'];
assertEdge(
decoratedTypeAnnotation('int/*2*/').node, constructorParameterType.node,
hard: true);
assertUnion(constructorParameterType.node,
decoratedTypeAnnotation('int/*1*/').node);
}
Future<void>
test_class_alias_synthetic_constructor_with_parameters_optional() async {
await analyze('''
class C {
C([int/*1*/ i]);
}
mixin M {}
class D = C with M;
D f(int/*2*/ i) => D(i);
''');
var syntheticConstructor = findElement.unnamedConstructor('D');
var constructorType = variables.decoratedElementType(syntheticConstructor);
var constructorParameterType = constructorType.positionalParameters[0];
assertEdge(
decoratedTypeAnnotation('int/*2*/').node, constructorParameterType.node,
hard: true);
assertUnion(constructorParameterType.node,
decoratedTypeAnnotation('int/*1*/').node);
}
Future<void>
test_class_alias_synthetic_constructor_with_parameters_required() async {
await analyze('''
class C {
C(int/*1*/ i);
}
mixin M {}
class D = C with M;
D f(int/*2*/ i) => D(i);
''');
var syntheticConstructor = findElement.unnamedConstructor('D');
var constructorType = variables.decoratedElementType(syntheticConstructor);
var constructorParameterType = constructorType.positionalParameters[0];
assertEdge(
decoratedTypeAnnotation('int/*2*/').node, constructorParameterType.node,
hard: true);
assertUnion(constructorParameterType.node,
decoratedTypeAnnotation('int/*1*/').node);
}
Future<void> test_class_metadata() async {
await analyze('''
@deprecated
class C {}
''');
// No assertions needed; the AnnotationTracker mixin verifies that the
// metadata was visited.
}
Future<void> test_conditionalExpression_condition_check() async {
await analyze('''
int f(bool b, int i, int j) {
return (b ? i : j);
}
''');
var nullable_b = decoratedTypeAnnotation('bool b').node;
var check_b = checkExpression('b ?');
assertNullCheck(check_b, assertEdge(nullable_b, never, hard: true));
}
Future<void> test_conditionalExpression_false_guard() async {
await analyze('int f(int x, int y, int z) => x != null ? null : y = z;');
var guard = decoratedTypeAnnotation('int x').node;
assertEdge(decoratedTypeAnnotation('int z').node,
decoratedTypeAnnotation('int y').node,
hard: false, guards: [guard]);
var conditionalDiscard =
variables.conditionalDiscard(findNode.conditionalExpression('!='));
expect(conditionalDiscard, isNotNull);
expect(conditionalDiscard.trueGuard, isNull);
expect(conditionalDiscard.falseGuard, same(guard));
}
Future<void> test_conditionalExpression_functionTyped_namedParameter() async {
await analyze('''
void f(bool b, void Function({int p}) x, void Function({int p}) y) {
(b ? x : y);
}
''');
var xType =
decoratedGenericFunctionTypeAnnotation('void Function({int p}) x');
var yType =
decoratedGenericFunctionTypeAnnotation('void Function({int p}) y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertGLB(resultType.namedParameters['p'].node,
xType.namedParameters['p'].node, yType.namedParameters['p'].node);
}
Future<void>
test_conditionalExpression_functionTyped_normalParameter() async {
await analyze('''
void f(bool b, void Function(int) x, void Function(int) y) {
(b ? x : y);
}
''');
var xType = decoratedGenericFunctionTypeAnnotation('void Function(int) x');
var yType = decoratedGenericFunctionTypeAnnotation('void Function(int) y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertGLB(resultType.positionalParameters[0].node,
xType.positionalParameters[0].node, yType.positionalParameters[0].node);
}
Future<void>
test_conditionalExpression_functionTyped_normalParameters() async {
await analyze('''
void f(bool b, void Function(int, int) x, void Function(int, int) y) {
(b ? x : y);
}
''');
var xType =
decoratedGenericFunctionTypeAnnotation('void Function(int, int) x');
var yType =
decoratedGenericFunctionTypeAnnotation('void Function(int, int) y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertGLB(resultType.positionalParameters[0].node,
xType.positionalParameters[0].node, yType.positionalParameters[0].node);
assertGLB(resultType.positionalParameters[1].node,
xType.positionalParameters[1].node, yType.positionalParameters[1].node);
}
Future<void>
test_conditionalExpression_functionTyped_optionalParameter() async {
await analyze('''
void f(bool b, void Function([int]) x, void Function([int]) y) {
(b ? x : y);
}
''');
var xType =
decoratedGenericFunctionTypeAnnotation('void Function([int]) x');
var yType =
decoratedGenericFunctionTypeAnnotation('void Function([int]) y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertGLB(resultType.positionalParameters[0].node,
xType.positionalParameters[0].node, yType.positionalParameters[0].node);
}
Future<void> test_conditionalExpression_functionTyped_returnType() async {
await analyze('''
void f(bool b, int Function() x, int Function() y) {
(b ? x : y);
}
''');
var xType = decoratedGenericFunctionTypeAnnotation('int Function() x');
var yType = decoratedGenericFunctionTypeAnnotation('int Function() y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertLUB(resultType.returnType.node, xType.returnType.node,
yType.returnType.node);
}
Future<void>
test_conditionalExpression_functionTyped_returnType_void() async {
await analyze('''
void f(bool b, void Function() x, void Function() y) {
(b ? x : y);
}
''');
var xType = decoratedGenericFunctionTypeAnnotation('void Function() x');
var yType = decoratedGenericFunctionTypeAnnotation('void Function() y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
expect(resultType.returnType.node.isImmutable, false);
}
Future<void> test_conditionalExpression_general() async {
await analyze('''
int f(bool b, int i, int j) {
return (b ? i : j);
}
''');
var nullable_i = decoratedTypeAnnotation('int i').node;
var nullable_j = decoratedTypeAnnotation('int j').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertLUB(nullable_conditional, nullable_i, nullable_j);
var nullable_return = decoratedTypeAnnotation('int f').node;
assertNullCheck(checkExpression('(b ? i : j)'),
assertEdge(nullable_conditional, nullable_return, hard: false));
}
Future<void> test_conditionalExpression_generic() async {
await analyze('''
void f(bool b, Map<int, String> x, Map<int, String> y) {
(b ? x : y);
}
''');
var xType = decoratedTypeAnnotation('Map<int, String> x');
var yType = decoratedTypeAnnotation('Map<int, String> y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertLUB(resultType.typeArguments[0].node, xType.typeArguments[0].node,
yType.typeArguments[0].node);
assertLUB(resultType.typeArguments[1].node, xType.typeArguments[1].node,
yType.typeArguments[1].node);
}
Future<void> test_conditionalExpression_generic_lub() async {
await analyze('''
class A<T> {}
class B<T> extends A<T/*b*/> {}
class C<T> extends A<T/*c*/> {}
A<num> f(bool b, B<num> x, C<num> y) {
return (b ? x : y);
}
''');
var bType = decoratedTypeAnnotation('B<num> x');
var cType = decoratedTypeAnnotation('C<num> y');
var bInA = decoratedTypeAnnotation('T/*b*/');
var cInA = decoratedTypeAnnotation('T/*c*/');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, bType.node, cType.node);
assertLUB(
resultType.typeArguments[0].node,
substitutionNode(bType.typeArguments[0].node, bInA.node),
substitutionNode(cType.typeArguments[0].node, cInA.node));
}
Future<void> test_conditionalExpression_generic_lub_leftSubtype() async {
await analyze('''
class A<T> {}
class B<T> extends A<T/*b*/> {}
A<num> f(bool b, B<num> x, A<num> y) {
return (b ? x : y);
}
''');
var aType = decoratedTypeAnnotation('A<num> y');
var bType = decoratedTypeAnnotation('B<num> x');
var bInA = decoratedTypeAnnotation('T/*b*/');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, bType.node, aType.node);
assertLUB(
resultType.typeArguments[0].node,
substitutionNode(bType.typeArguments[0].node, bInA.node),
aType.typeArguments[0].node);
}
Future<void> test_conditionalExpression_generic_lub_rightSubtype() async {
await analyze('''
class A<T> {}
class B<T> extends A<T/*b*/> {}
A<num> f(bool b, A<num> x, B<num> y) {
return (b ? x : y);
}
''');
var aType = decoratedTypeAnnotation('A<num> x');
var bType = decoratedTypeAnnotation('B<num> y');
var bInA = decoratedTypeAnnotation('T/*b*/');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, aType.node, bType.node);
assertLUB(resultType.typeArguments[0].node, aType.typeArguments[0].node,
substitutionNode(bType.typeArguments[0].node, bInA.node));
}
Future<void> test_conditionalExpression_generic_typeParameter_bound() async {
await analyze('''
List<num> f<T extends List<num>>(bool b, List<num> x, T y) {
return (b ? x : y);
}
''');
var aType = decoratedTypeAnnotation('List<num> x');
var bType = decoratedTypeAnnotation('T y');
var bBound = decoratedTypeAnnotation('List<num>>');
var resultType = decoratedExpressionType('(b ?');
assertLUB(
resultType.node, aType.node, substitutionNode(bBound.node, bType.node));
assertLUB(resultType.typeArguments[0].node, aType.typeArguments[0].node,
bBound.typeArguments[0].node);
}
Future<void> test_conditionalExpression_left_never() async {
await analyze('''
List<int> f(bool b, List<int> i) {
return (b ? (throw i) : i);
}
''');
var nullable_i = decoratedTypeAnnotation('List<int> i').node;
var nullable_conditional =
decoratedExpressionType('(b ?').node as NullabilityNodeForLUB;
var nullable_throw = nullable_conditional.left;
assertNoUpstreamNullability(nullable_throw);
assertLUB(nullable_conditional, nullable_throw, nullable_i);
}
Future<void> test_conditionalExpression_left_non_null() async {
await analyze('''
int f(bool b, int i) {
return (b ? (throw i) : i);
}
''');
var nullable_i = decoratedTypeAnnotation('int i').node;
var nullable_conditional =
decoratedExpressionType('(b ?').node as NullabilityNodeForLUB;
var nullable_throw = nullable_conditional.left;
assertNoUpstreamNullability(nullable_throw);
assertLUB(nullable_conditional, nullable_throw, nullable_i);
}
Future<void> test_conditionalExpression_left_null() async {
await analyze('''
int f(bool b, int i) {
return (b ? null : i);
}
''');
var nullable_i = decoratedTypeAnnotation('int i').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertLUB(nullable_conditional, inSet(alwaysPlus), nullable_i);
}
Future<void> test_conditionalExpression_left_null_right_function() async {
await analyze('''
bool Function<T>(int) g(bool b, bool Function<T>(int) f) {
return (b ? null : f);
}
''');
var nullable_i =
decoratedGenericFunctionTypeAnnotation('bool Function<T>(int) f').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertLUB(nullable_conditional, inSet(alwaysPlus), nullable_i);
}
Future<void>
test_conditionalExpression_left_null_right_parameterType() async {
await analyze('''
T g<T>(bool b, T t) {
return (b ? null : t);
}
''');
var nullable_t = decoratedTypeAnnotation('T t').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertLUB(nullable_conditional, inSet(alwaysPlus), nullable_t);
}
Future<void> test_conditionalExpression_left_null_right_typeArgs() async {
await analyze('''
List<int> f(bool b, List<int> l) {
return (b ? null : l);
}
''');
var nullable_i = decoratedTypeAnnotation('List<int> l').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertLUB(nullable_conditional, inSet(alwaysPlus), nullable_i);
}
Future<void> test_conditionalExpression_nullTyped_nullParameter() async {
await analyze('''
void f(bool b, void Function(Null p) x, void Function(List<int> p) y) {
(b ? x : y);
}
''');
var xType =
decoratedGenericFunctionTypeAnnotation('void Function(Null p) x');
var yType =
decoratedGenericFunctionTypeAnnotation('void Function(List<int> p) y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertGLB(resultType.positionalParameters[0].node,
xType.positionalParameters[0].node, yType.positionalParameters[0].node);
}
Future<void> test_conditionalExpression_parameterType() async {
await analyze('''
T g<T>(bool b, T x, T y) {
return (b ? x : y);
}
''');
var nullable_x = decoratedTypeAnnotation('T x').node;
var nullable_y = decoratedTypeAnnotation('T y').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertLUB(nullable_conditional, nullable_x, nullable_y);
}
Future<void> test_conditionalExpression_right_never() async {
await analyze('''
List<int> f(bool b, List<int> i) {
return (b ? i : (throw i));
}
''');
var nullable_i = decoratedTypeAnnotation('List<int> i').node;
var nullable_conditional =
decoratedExpressionType('(b ?').node as NullabilityNodeForLUB;
var nullable_throw = nullable_conditional.right;
assertNoUpstreamNullability(nullable_throw);
assertLUB(nullable_conditional, nullable_i, nullable_throw);
}
Future<void> test_conditionalExpression_right_non_null() async {
await analyze('''
int f(bool b, int i) {
return (b ? i : (throw i));
}
''');
var nullable_i = decoratedTypeAnnotation('int i').node;
var nullable_conditional =
decoratedExpressionType('(b ?').node as NullabilityNodeForLUB;
var nullable_throw = nullable_conditional.right;
assertNoUpstreamNullability(nullable_throw);
assertLUB(nullable_conditional, nullable_i, nullable_throw);
}
Future<void> test_conditionalExpression_right_null() async {
await analyze('''
int f(bool b, int i) {
return (b ? i : null);
}
''');
var nullable_i = decoratedTypeAnnotation('int i').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertLUB(nullable_conditional, nullable_i, inSet(alwaysPlus));
}
Future<void> test_conditionalExpression_right_null_left_function() async {
await analyze('''
bool Function<T>(int) g(bool b, bool Function<T>(int) f) {
return (b ? f : null);
}
''');
var nullable_i =
decoratedGenericFunctionTypeAnnotation('bool Function<T>(int) f').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertLUB(nullable_conditional, nullable_i, inSet(alwaysPlus));
}
Future<void> test_conditionalExpression_right_null_left_typeArgs() async {
await analyze('''
List<int> f(bool b, List<int> l) {
return (b ? l : null);
}
''');
var nullable_i = decoratedTypeAnnotation('List<int> l').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertLUB(nullable_conditional, nullable_i, inSet(alwaysPlus));
}
Future<void>
test_conditionalExpression_right_null_left_typeParameter() async {
await analyze('''
T f<T>(bool b, T t) {
return (b ? t : null);
}
''');
var nullable_t = decoratedTypeAnnotation('T t').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertLUB(nullable_conditional, nullable_t, inSet(alwaysPlus));
}
Future<void> test_conditionalExpression_true_guard() async {
await analyze('int f(int x, int y, int z) => x == null ? y = z : null;');
var guard = decoratedTypeAnnotation('int x').node;
assertEdge(decoratedTypeAnnotation('int z').node,
decoratedTypeAnnotation('int y').node,
hard: false, guards: [guard]);
var conditionalDiscard =
variables.conditionalDiscard(findNode.conditionalExpression('=='));
expect(conditionalDiscard, isNotNull);
expect(conditionalDiscard.trueGuard, same(guard));
expect(conditionalDiscard.falseGuard, isNull);
}
Future<void> test_conditionalExpression_typeParameter_bound() async {
await analyze('''
num f<T extends num>(bool b, num x, T y) {
return (b ? x : y);
}
''');
var aType = decoratedTypeAnnotation('num x');
var bType = decoratedTypeAnnotation('T y');
var bBound = decoratedTypeAnnotation('num>');
var resultType = decoratedExpressionType('(b ?');
assertLUB(
resultType.node, aType.node, substitutionNode(bBound.node, bType.node));
}
Future<void> test_conditionalExpression_typeParameter_bound_bound() async {
await analyze('''
num f<T extends R, R extends num>(bool b, num x, T y) {
return (b ? x : y);
}
''');
var aType = decoratedTypeAnnotation('num x');
var bType = decoratedTypeAnnotation('T y');
var bBound = decoratedTypeAnnotation('R,');
var bBoundBound = decoratedTypeAnnotation('num>');
var resultType = decoratedExpressionType('(b ?');
assertLUB(
resultType.node,
aType.node,
substitutionNode(
bBoundBound.node, substitutionNode(bBound.node, bType.node)));
}
Future<void> test_conditionalExpression_typeParameter_dynamic() async {
// "dynamic" can short circuit LUB, incorrectly we may lose nullabilities.
await analyze('''
dynamic f<T extends num>(bool b, dynamic x, T y) {
return (b ? x : y);
}
''');
var aType = decoratedTypeAnnotation('dynamic x');
var bType = decoratedTypeAnnotation('T y');
var bBound = decoratedTypeAnnotation('num>');
var resultType = decoratedExpressionType('(b ?');
assertLUB(
resultType.node, aType.node, substitutionNode(bBound.node, bType.node));
}
Future<void> test_conditionalExpression_typeParameters_bound() async {
await analyze('''
num f<T extends num, R extends num>(bool b, R x, T y) {
return (b ? x : y);
}
''');
var aType = decoratedTypeAnnotation('R x');
var bType = decoratedTypeAnnotation('T y');
var aBound = decoratedTypeAnnotation('num>');
var bBound = decoratedTypeAnnotation('num,');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, substitutionNode(aBound.node, aType.node),
substitutionNode(bBound.node, bType.node));
}
Future<void>
test_conditionalExpression_typeParameters_bound_left_to_right() async {
await analyze('''
R f<T extends R, R>(bool b, R x, T y) {
return (b ? x : y);
}
''');
var aType = decoratedTypeAnnotation('R x');
var bType = decoratedTypeAnnotation('T y');
var bBound = decoratedTypeAnnotation('R,');
var resultType = decoratedExpressionType('(b ?');
assertLUB(
resultType.node, aType.node, substitutionNode(bBound.node, bType.node));
}
Future<void> test_constructor_default_parameter_value_bool() async {
await analyze('''
class C {
C([bool b = true]);
}
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('bool b').node);
}
Future<void> test_constructor_named() async {
await analyze('''
class C {
C.named();
}
''');
// No assertions; just need to make sure that the test doesn't cause an
// exception to be thrown.
}
Future<void> test_constructor_superInitializer() async {
await analyze('''
class C {
C.named(int i);
}
class D extends C {
D(int j) : super.named(j);
}
''');
var namedConstructor = findElement.constructor('named', of: 'C');
var constructorType = variables.decoratedElementType(namedConstructor);
var constructorParameterType = constructorType.positionalParameters[0];
assertEdge(
decoratedTypeAnnotation('int j').node, constructorParameterType.node,
hard: true);
}
Future<void> test_constructor_superInitializer_withTypeArgument() async {
await analyze('''
class C<T> {
C.named(T/*1*/ i);
}
class D extends C<int/*2*/> {
D(int/*3*/ j) : super.named(j);
}
''');
var nullable_t1 = decoratedTypeAnnotation('T/*1*/').node;
var nullable_int2 = decoratedTypeAnnotation('int/*2*/').node;
var nullable_int3 = decoratedTypeAnnotation('int/*3*/').node;
assertEdge(nullable_int3, substitutionNode(nullable_int2, nullable_t1),
hard: true);
}
Future<void> test_constructor_superInitializer_withTypeVariable() async {
await analyze('''
class C<T> {
C.named(T/*1*/ i);
}
class D<U> extends C<U/*2*/> {
D(U/*3*/ j) : super.named(j);
}
''');
var nullable_t1 = decoratedTypeAnnotation('T/*1*/').node;
var nullable_u2 = decoratedTypeAnnotation('U/*2*/').node;
var nullable_u3 = decoratedTypeAnnotation('U/*3*/').node;
assertEdge(nullable_u3, substitutionNode(nullable_u2, nullable_t1),
hard: true);
}
Future<void> test_constructorDeclaration_returnType_generic() async {
await analyze('''
class C<T, U> {
C();
}
''');
var constructor = findElement.unnamedConstructor('C');
var constructorDecoratedType = variables.decoratedElementType(constructor);
_assertType(constructorDecoratedType.type, 'C<T, U> Function()');
expect(constructorDecoratedType.node, same(never));
expect(constructorDecoratedType.typeFormals, isEmpty);
expect(constructorDecoratedType.returnType.node, same(never));
_assertType(constructorDecoratedType.returnType.type, 'C<T, U>');
var typeArguments = constructorDecoratedType.returnType.typeArguments;
expect(typeArguments, hasLength(2));
_assertType(typeArguments[0].type, 'T');
expect(typeArguments[0].node, same(never));
_assertType(typeArguments[1].type, 'U');
expect(typeArguments[1].node, same(never));
}
Future<void> test_constructorDeclaration_returnType_generic_implicit() async {
await analyze('''
class C<T, U> {}
''');
var constructor = findElement.unnamedConstructor('C');
var constructorDecoratedType = variables.decoratedElementType(constructor);
_assertType(constructorDecoratedType.type, 'C<T, U> Function()');
expect(constructorDecoratedType.node, same(never));
expect(constructorDecoratedType.typeFormals, isEmpty);
expect(constructorDecoratedType.returnType.node, same(never));
_assertType(constructorDecoratedType.returnType.type, 'C<T, U>');
var typeArguments = constructorDecoratedType.returnType.typeArguments;
expect(typeArguments, hasLength(2));
_assertType(typeArguments[0].type, 'T');
expect(typeArguments[0].node, same(never));
_assertType(typeArguments[1].type, 'U');
expect(typeArguments[1].node, same(never));
}
Future<void> test_constructorDeclaration_returnType_simple() async {
await analyze('''
class C {
C();
}
''');
var constructorDecoratedType =
variables.decoratedElementType(findElement.unnamedConstructor('C'));
_assertType(constructorDecoratedType.type, 'C Function()');
expect(constructorDecoratedType.node, same(never));
expect(constructorDecoratedType.typeFormals, isEmpty);
expect(constructorDecoratedType.returnType.node, same(never));
expect(constructorDecoratedType.returnType.typeArguments, isEmpty);
}
Future<void> test_constructorDeclaration_returnType_simple_implicit() async {
await analyze('''
class C {}
''');
var constructorDecoratedType =
variables.decoratedElementType(findElement.unnamedConstructor('C'));
_assertType(constructorDecoratedType.type, 'C Function()');
expect(constructorDecoratedType.node, same(never));
expect(constructorDecoratedType.typeFormals, isEmpty);
expect(constructorDecoratedType.returnType.node, same(never));
expect(constructorDecoratedType.returnType.typeArguments, isEmpty);
}
Future<void> test_constructorFieldInitializer_generic() async {
await analyze('''
class C<T> {
C(T/*1*/ x) : f = x;
T/*2*/ f;
}
''');
assertEdge(decoratedTypeAnnotation('T/*1*/').node,
decoratedTypeAnnotation('T/*2*/').node,
hard: true);
}
Future<void> test_constructorFieldInitializer_simple() async {
await analyze('''
class C {
C(int/*1*/ i) : f = i;
int/*2*/ f;
}
''');
assertEdge(decoratedTypeAnnotation('int/*1*/').node,
decoratedTypeAnnotation('int/*2*/').node,
hard: true);
}
Future<void> test_constructorFieldInitializer_via_this() async {
await analyze('''
class C {
C(int/*1*/ i) : this.f = i;
int/*2*/ f;
}
''');
assertEdge(decoratedTypeAnnotation('int/*1*/').node,
decoratedTypeAnnotation('int/*2*/').node,
hard: true);
}
Future<void> test_do_while_condition() async {
await analyze('''
void f(bool b) {
do {} while (b);
}
''');
assertNullCheck(checkExpression('b);'),
assertEdge(decoratedTypeAnnotation('bool b').node, never, hard: true));
}
Future<void> test_doubleLiteral() async {
await analyze('''
double f() {
return 1.0;
}
''');
assertNoUpstreamNullability(decoratedTypeAnnotation('double').node);
}
Future<void> test_dummyNode_fromEqualityComparison_left() async {
await analyze('''
f() {
int i;
if (i == 7) {}
}
''');
var nullable_i = decoratedTypeAnnotation('int i').node;
assertDummyEdge(nullable_i);
}
Future<void> test_dummyNode_fromEqualityComparison_right() async {
await analyze('''
f() {
int i;
if (7 == i) {}
}
''');
var nullable_i = decoratedTypeAnnotation('int i').node;
assertDummyEdge(nullable_i);
}
Future<void> test_dummyNode_fromExpressionStatement() async {
await analyze('''
f() {
int i;
i;
}
''');
var nullable_i = decoratedTypeAnnotation('int i').node;
assertDummyEdge(nullable_i);
}
Future<void> test_dummyNode_fromForLoopUpdaters() async {
await analyze('''
f() {
int i;
int j;
for (;; i, j) {}
}
''');
var nullable_i = decoratedTypeAnnotation('int i').node;
var nullable_j = decoratedTypeAnnotation('int j').node;
assertDummyEdge(nullable_i);
assertDummyEdge(nullable_j);
}
Future<void> test_dummyNode_fromForLoopVariables() async {
await analyze('''
f() {
int i;
for (i;;) {}
}
''');
var nullable_i = decoratedTypeAnnotation('int i').node;
assertDummyEdge(nullable_i);
}
Future<void> test_edgeOrigin_call_from_function() async {
await analyze('''
void f(int i) {}
void g(int j) {
f(j);
}
''');
assertEdge(decoratedTypeAnnotation('int j').node,
decoratedTypeAnnotation('int i').node,
hard: true,
codeReference:
matchCodeRef(offset: findNode.simple('j);').offset, function: 'g'));
}
Future<void> test_edgeOrigin_call_from_method() async {
await analyze('''
class C {
void f(int i) {}
void g(int j) {
f(j);
}
}
''');
assertEdge(decoratedTypeAnnotation('int j').node,
decoratedTypeAnnotation('int i').node,
hard: true,
codeReference: matchCodeRef(
offset: findNode.simple('j);').offset, function: 'C.g'));
}
Future<void> test_export_metadata() async {
await analyze('''
@deprecated
export 'dart:async';
''');
// No assertions needed; the AnnotationTracker mixin verifies that the
// metadata was visited.
}
Future<void> test_extension_metadata() async {
await analyze('''
@deprecated
extension E on String {}
''');
// No assertions needed; the AnnotationTracker mixin verifies that the
// metadata was visited.
}
Future<void> test_extension_on_class_with_generic_type_arguments() async {
await analyze('''
class C<T> {}
void f(C<List> x) {}
extension E on C<List> {
g() => f(this);
}
''');
// No assertions yet. This test crashes. When it stops crashing, consider
// adding assertion(s).
}
Future<void> test_extension_on_function_type() async {
await analyze('''
extension CurryFunction<R, S, T> on R Function(S, T) {
/// Curry a binary function with its first argument.
R Function(T) curry(S first) => (T second) => this(first, second);
}
''');
// No assertions yet. This test crashes. When it stops crashing, consider
// adding assertion(s).
}
Future<void> test_field_final_does_not_override_setter() async {
await analyze('''
abstract class A {
void set i(int value);
}
abstract class C implements A {
final int i;
C(this.i);
}
''');
var baseNode = decoratedTypeAnnotation('int value').node;
var derivedNode = decoratedTypeAnnotation('int i').node;
assertNoEdge(derivedNode, baseNode);
assertNoEdge(baseNode, derivedNode);
}
Future<void> test_field_initialized_in_constructor() async {
await analyze('''
class C {
int i;
C() : i = 0;
}
''');
// There is no edge from always to the type of i, because it is initialized
// in the constructor.
assertNoEdge(always, decoratedTypeAnnotation('int').node);
}
Future<void> test_field_metadata() async {
await analyze('''
class A {
const A();
}
class C {
@A()
int f;
}
''');
// No assertions needed; the AnnotationTracker mixin verifies that the
// metadata was visited.
}
Future<void> test_field_overrides_field() async {
await analyze('''
abstract class A {
int i; // A
}
class C implements A {
int i; // C
}
''');
var baseNode = decoratedTypeAnnotation('int i; // A').node;
var derivedNode = decoratedTypeAnnotation('int i; // C').node;
assertEdge(baseNode, derivedNode, hard: true);
assertEdge(derivedNode, baseNode, hard: true);
}
Future<void> test_field_overrides_field_final() async {
await analyze('''
abstract class A {
final int i; // A
A(this.i);
}
class C implements A {
int i; // C
}
''');
var baseNode = decoratedTypeAnnotation('int i; // A').node;
var derivedNode = decoratedTypeAnnotation('int i; // C').node;
assertEdge(derivedNode, baseNode, hard: true);
assertNoEdge(baseNode, derivedNode);
}
Future<void> test_field_overrides_getter() async {
await analyze('''
abstract class A {
int get i;
}
class C implements A {
int i;
}
''');
var baseNode = decoratedTypeAnnotation('int get i').node;
var derivedNode = decoratedTypeAnnotation('int i').node;
assertEdge(derivedNode, baseNode, hard: true);
assertNoEdge(baseNode, derivedNode);
}
Future<void> test_field_overrides_setter() async {
await analyze('''
abstract class A {
void set i(int value);
}
class C implements A {
int i;
}
''');
var baseNode = decoratedTypeAnnotation('int value').node;
var derivedNode = decoratedTypeAnnotation('int i').node;
assertEdge(baseNode, derivedNode, hard: true);
assertNoEdge(derivedNode, baseNode);
}
Future<void> test_field_static_implicitInitializer() async {
await analyze('''
class C {
static int i;
}
''');
assertEdge(always, decoratedTypeAnnotation('int').node, hard: false);
}
Future<void> test_field_type_inferred() async {
await analyze('''
int f() => 1;
class C {
var x = f();
}
''');
var xType =
variables.decoratedElementType(findNode.simple('x').staticElement);
assertEdge(decoratedTypeAnnotation('int').node, xType.node, hard: false);
}
Future<void> test_fieldFormalParameter_function_typed() async {
await analyze('''
class C {
int Function(int, {int j}) f;
C(int this.f(int i, {int j}));
}
''');
var ctorParamType = variables
.decoratedElementType(findElement.unnamedConstructor('C'))
.positionalParameters[0];
var fieldType = variables.decoratedElementType(findElement.field('f'));
assertEdge(ctorParamType.node, fieldType.node, hard: true);
assertEdge(ctorParamType.returnType.node, fieldType.returnType.node,
hard: false, checkable: false);
assertEdge(fieldType.positionalParameters[0].node,
ctorParamType.positionalParameters[0].node,
hard: false, checkable: false);
assertEdge(fieldType.namedParameters['j'].node,
ctorParamType.namedParameters['j'].node,
hard: false, checkable: false);
}
Future<void> test_fieldFormalParameter_typed() async {
await analyze('''
class C {
int i;
C(int this.i);
}
''');
assertEdge(decoratedTypeAnnotation('int this').node,
decoratedTypeAnnotation('int i').node,
hard: true);
}
Future<void> test_fieldFormalParameter_untyped() async {
await analyze('''
class C {
int i;
C.named(this.i);
}
''');
var decoratedConstructorParamType =
decoratedConstructorDeclaration('named').positionalParameters[0];
assertEdge(decoratedConstructorParamType.node,
decoratedTypeAnnotation('int i').node,
hard: true);
}
Future<void> test_firstWhere_edges() async {
await analyze('''
int firstEven(Iterable<int> x)
=> x.firstWhere((x) => x.isEven, orElse: () => null);
''');
// Normally there would be an edge from the return type of `() => null` to
// a substitution node that pointed to the type argument to the type of `x`,
// and another substitution node would point from this to the return type of
// `firstEven`. However, since we may replace `firstWhere` with
// `firstWhereOrNull` in order to avoid having to make `x`'s type argument
// nullable, we need a synthetic edge to ensure that the return type of
// `firstEven` is nullable.
var closureReturnType = decoratedExpressionType('() => null').returnType;
var firstWhereReturnType = variables
.decoratedExpressionType(findNode.methodInvocation('firstWhere'));
assertEdge(closureReturnType.node, firstWhereReturnType.node, hard: false);
// There should also be an edge from a substitution node to the return type
// of `firstWhere`, to account for the normal data flow (when the element is
// found).
var typeParameterType = decoratedTypeAnnotation('int>');
var firstWhereType = variables.decoratedElementType(findNode
.methodInvocation('firstWhere')
.methodName
.staticElement
.declaration);
assert