blob: 68359b0df3a541e549478e6bffb90f44cc1665f0 [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/results.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:nnbd_migration/nnbd_migration.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'abstract_context.dart';
import 'api_test_base.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(_ProvisionalApiTest);
defineReflectiveTests(_ProvisionalApiTestPermissive);
defineReflectiveTests(_ProvisionalApiTestWithReset);
});
}
/// Tests of the provisional API.
@reflectiveTest
class _ProvisionalApiTest extends _ProvisionalApiTestBase
with _ProvisionalApiTestCases {
@override
bool get _usePermissiveMode => false;
}
/// Base class for provisional API tests.
abstract class _ProvisionalApiTestBase extends AbstractContextTest {
String? projectPath;
bool get _usePermissiveMode;
void setUp() {
projectPath = convertPath(testsPath);
super.setUp();
}
/// Hook invoked between stages of processing inputs.
void _betweenStages() {}
/// Verifies that migration of the files in [input] produces the output in
/// [expectedOutput].
///
/// Optional parameter [removeViaComments] indicates whether dead code should
/// be removed in its entirety (the default) or removed by commenting it out.
Future<void> _checkMultipleFileChanges(
Map<String, String> input, Map<String, dynamic> expectedOutput,
{Map<String, String> migratedInput = const {},
bool removeViaComments = false,
bool warnOnWeakCode = false,
bool allowErrors = false}) async {
for (var path in migratedInput.keys) {
newFile(path, migratedInput[path]!);
}
for (var path in input.keys) {
newFile(path, input[path]!);
}
var listener = TestMigrationListener();
var migration = NullabilityMigration(listener,
permissive: _usePermissiveMode,
removeViaComments: removeViaComments,
warnOnWeakCode: warnOnWeakCode);
for (var path in input.keys) {
var resolvedLibrary = await session.getResolvedLibrary(path);
if (resolvedLibrary is ResolvedLibraryResult) {
for (var unit in resolvedLibrary.units) {
var errors =
unit.errors.where((e) => e.severity == Severity.error).toList();
if (!allowErrors && errors.isNotEmpty) {
fail('Unexpected error(s): $errors');
}
migration.prepareInput(unit);
}
}
}
expect(migration.unmigratedDependencies, isEmpty);
_betweenStages();
for (var path in input.keys) {
var resolvedLibrary = await session.getResolvedLibrary(path);
if (resolvedLibrary is ResolvedLibraryResult) {
for (var unit in resolvedLibrary.units) {
migration.processInput(unit);
}
}
}
_betweenStages();
for (var path in input.keys) {
var resolvedLibrary = await session.getResolvedLibrary(path);
if (resolvedLibrary is ResolvedLibraryResult) {
for (var unit in resolvedLibrary.units) {
migration.finalizeInput(unit);
}
}
}
migration.finish();
var sourceEdits = <String, List<SourceEdit>>{};
for (var entry in listener.edits.entries) {
var path = entry.key.fullName;
expect(expectedOutput.keys, contains(path));
sourceEdits[path] = entry.value;
}
for (var path in expectedOutput.keys) {
var sourceEditsForPath = sourceEdits[path] ?? [];
sourceEditsForPath.sort((a, b) => b.offset.compareTo(a.offset));
expect(SourceEdit.applySequence(input[path]!, sourceEditsForPath),
expectedOutput[path]);
}
}
/// Verifies that migraiton of the single file with the given [content]
/// produces the [expected] output.
///
/// Optional parameter [removeViaComments] indicates whether dead code should
/// be removed in its entirety (the default) or removed by commenting it out.
Future<void> _checkSingleFileChanges(String content, dynamic expected,
{Map<String, String> migratedInput = const {},
bool removeViaComments = false,
bool warnOnWeakCode = false,
bool allowErrors = false}) async {
var sourcePath = convertPath('$testsPath/lib/test.dart');
await _checkMultipleFileChanges(
{sourcePath: content}, {sourcePath: expected},
migratedInput: migratedInput,
removeViaComments: removeViaComments,
warnOnWeakCode: warnOnWeakCode,
allowErrors: allowErrors);
}
}
/// Mixin containing test cases for the provisional API.
mixin _ProvisionalApiTestCases on _ProvisionalApiTestBase {
Future<void> test_accept_required_hint() async {
var content = '''
f({/*required*/ int i}) {}
''';
var expected = '''
f({required int i}) {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_accept_required_hint_nullable() async {
var content = '''
f({/*required*/ int i}) {}
g() {
f(i: null);
}
''';
var expected = '''
f({required int? i}) {}
g() {
f(i: null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_add_explicit_parameter_type() async {
var content = '''
abstract class C {
void m<T>(T Function(T) callback);
}
void test(C c) {
c.m((value) => value + 1);
}
''';
// Under the new NNBD rules, `value` gets an inferred type of `Object?`. We
// need to change this to `dynamic` to avoid an "undefined operator +"
// error.
var expected = '''
abstract class C {
void m<T>(T Function(T) callback);
}
void test(C c) {
c.m((dynamic value) => value + 1);
}
''';
await _checkSingleFileChanges(content, expected);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/40476')
Future<void> test_add_explicit_parameter_type_interpolation() async {
var content = r'''
abstract class C {
void m<T>(T Function(T) callback);
}
void test(C c) {
c.m((value) => '$value';
}
''';
// Under the new NNBD rules, `value` gets an inferred type of `Object?`,
// whereas it previously had a type of `dynamic`. But we don't need to fix
// it because `Object?` supports `toString`.
var expected = r'''
abstract class C {
void m<T>(T Function(T) callback);
}
void test(C c) {
c.m((value) => '$value';
}
''';
await _checkSingleFileChanges(content, expected);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/40476')
Future<void> test_add_explicit_parameter_type_object_method() async {
var content = '''
abstract class C {
void m<T>(T Function(T) callback);
}
void test(C c) {
c.m((value) => value.toString());
}
''';
// Under the new NNBD rules, `value` gets an inferred type of `Object?`,
// whereas it previously had a type of `dynamic`. But we don't need to fix
// it because `Object?` supports `toString`.
var expected = '''
abstract class C {
void m<T>(T Function(T) callback);
}
void test(C c) {
c.m((value) => value.toString());
}
''';
await _checkSingleFileChanges(content, expected);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/40476')
Future<void> test_add_explicit_parameter_type_unused() async {
var content = '''
abstract class C {
void m<T>(T Function(T) callback);
}
void test(C c) {
c.m((value) => 0);
}
''';
// Under the new NNBD rules, `value` gets an inferred type of `Object?`,
// whereas it previously had a type of `dynamic`. But we don't need to fix
// it because it's unused.
var expected = '''
abstract class C {
void m<T>(T Function(T) callback);
}
void test(C c) {
c.m((value) => 0);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_add_required() async {
var content = '''
int f({String s}) => s.length;
''';
var expected = '''
int f({required String s}) => s.length;
''';
await _checkSingleFileChanges(content, expected);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/39404')
Future<void> test_add_type_parameter_bound() async {
/// After a migration, a bound may be made nullable. Instantiate-to-bounds
/// may need to be made explicit where the migration engine prefers a
/// non-null type.
var content = '''
class C<T extends num/*?*/> {}
void main() {
C c = C();
}
''';
var expected = '''
class C<T extends num?> {}
void main() {
C<num> c = C();
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_ambiguous_bang_hint_after_as() async {
var content = '''
T f<T>(Object/*?*/ x) => x as T/*!*/;
''';
// The `/*!*/` is considered to apply to the type `T`, not to the expression
// `x as T`, so we shouldn't produce `(x as T)!`.
var expected = '''
T f<T>(Object? x) => x as T;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_ambiguous_bang_hint_after_as_assigned() async {
var content = '''
T f<T>(Object/*?*/ x, T/*!*/ y) => y = x as T/*!*/;
''';
// The `/*!*/` is considered to apply to the type `T`, not to the expression
// `y = x as T`, so we shouldn't produce `(y = x as T)!`.
var expected = '''
T f<T>(Object? x, T y) => y = x as T;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_ambiguous_bang_hint_after_is() async {
var content = '''
bool f<T>(Object/*?*/ x) => x is T/*!*/;
''';
// The `/*!*/` is considered to apply to the type `T`, not to the expression
// `x is T`, so we shouldn't produce `(x is T)!`.
var expected = '''
bool f<T>(Object? x) => x is T;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_ambiguous_bang_hint_after_is_conditional() async {
var content = '''
dynamic f<T>(Object/*?*/ x, dynamic y) => y ? y : x is T/*!*/;
''';
// The `/*!*/` is considered to apply to the type `T`, not to the expression
// `y ? y : x is T`, so we shouldn't produce `(y ? y : x is T)!`.
var expected = '''
dynamic f<T>(Object? x, dynamic y) => y ? y : x is T;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_ambiguous_closure_parameter_in_local_variable() async {
var content = '''
Object f<T>(Object Function(T) callback, Object obj) => 0;
g() {
var y = f<Map<String, int>>(
(x) => x.keys,
f<List<bool>>(
(x) => x.last, 0));
}
''';
var expected = '''
Object f<T>(Object Function(T) callback, Object obj) => 0;
g() {
var y = f<Map<String, int>>(
(x) => x.keys,
f<List<bool>>(
(x) => x.last, 0));
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_angular_contentChild_field() async {
addAngularPackage();
var content = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
// Initialize this.bar in the constructor just so the migration tool doesn't
// decide to make it nullable due to the lack of initializer.
MyComponent(this.bar);
@ContentChild('foo')
Element bar;
}
''';
var expected = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
// Initialize this.bar in the constructor just so the migration tool doesn't
// decide to make it nullable due to the lack of initializer.
MyComponent(this.bar);
@ContentChild('foo')
Element? bar;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_angular_contentChild_field_not_late() async {
addAngularPackage();
var content = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
@ContentChild('foo')
Element bar;
Element baz;
f(Element e) {
bar = e;
baz = e;
}
g() => bar.id;
h() => baz.id;
}
''';
// `late` heuristics are disabled for `bar` since it's marked with
// `ContentChild`. But they do apply to `baz`.
var expected = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
@ContentChild('foo')
Element? bar;
late Element baz;
f(Element e) {
bar = e;
baz = e;
}
g() => bar!.id;
h() => baz.id;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_angular_contentChildren_field_not_late() async {
addAngularPackage();
var content = '''
import 'dart:html';
import 'package:angular/angular.dart';
class myComponent {
@ContentChildren('foo')
Element bar;
Element baz;
f(Element e) {
bar = e;
baz = e;
}
g() => bar.id;
h() => baz.id;
}
''';
// `late` heuristics are disabled for `bar` since it's marked with
// `ContentChildren`. But they do apply to `baz`.
var expected = '''
import 'dart:html';
import 'package:angular/angular.dart';
class myComponent {
@ContentChildren('foo')
Element? bar;
late Element baz;
f(Element e) {
bar = e;
baz = e;
}
g() => bar!.id;
h() => baz.id;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_angular_optional_constructor_param() async {
addAngularPackage();
var content = '''
import 'package:angular/angular.dart';
class MyComponent {
MyComponent(@Optional() String foo);
}
''';
var expected = '''
import 'package:angular/angular.dart';
class MyComponent {
MyComponent(@Optional() String? foo);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_angular_optional_constructor_param_field_formal() async {
addAngularPackage();
var content = '''
import 'package:angular/angular.dart';
class MyComponent {
String foo;
MyComponent(@Optional() this.foo);
}
''';
var expected = '''
import 'package:angular/angular.dart';
class MyComponent {
String? foo;
MyComponent(@Optional() this.foo);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_angular_optional_constructor_param_internal() async {
addAngularPackage(internalUris: true);
var content = '''
import 'package:angular/angular.dart';
class MyComponent {
MyComponent(@Optional() String foo);
}
''';
var expected = '''
import 'package:angular/angular.dart';
class MyComponent {
MyComponent(@Optional() String? foo);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_angular_viewChild_field() async {
addAngularPackage();
var content = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
// Initialize this.bar in the constructor just so the migration tool doesn't
// decide to make it nullable due to the lack of initializer.
MyComponent(this.bar);
@ViewChild('foo')
Element bar;
}
''';
var expected = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
// Initialize this.bar in the constructor just so the migration tool doesn't
// decide to make it nullable due to the lack of initializer.
MyComponent(this.bar);
@ViewChild('foo')
Element? bar;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_angular_viewChild_field_internal() async {
addAngularPackage(internalUris: true);
var content = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
// Initialize this.bar in the constructor just so the migration tool doesn't
// decide to make it nullable due to the lack of initializer.
MyComponent(this.bar);
@ViewChild('foo')
Element bar;
}
''';
var expected = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
// Initialize this.bar in the constructor just so the migration tool doesn't
// decide to make it nullable due to the lack of initializer.
MyComponent(this.bar);
@ViewChild('foo')
Element? bar;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_angular_viewChild_field_not_late() async {
addAngularPackage();
var content = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
@ViewChild('foo')
Element bar;
Element baz;
f(Element e) {
bar = e;
baz = e;
}
g() => bar.id;
h() => baz.id;
}
''';
// `late` heuristics are disabled for `bar` since it's marked with
// `ViewChild`. But they do apply to `baz`.
var expected = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
@ViewChild('foo')
Element? bar;
late Element baz;
f(Element e) {
bar = e;
baz = e;
}
g() => bar!.id;
h() => baz.id;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_angular_viewChild_setter() async {
addAngularPackage();
var content = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
@ViewChild('foo')
set bar(Element element) {}
}
''';
var expected = '''
import 'dart:html';
import 'package:angular/angular.dart';
class MyComponent {
@ViewChild('foo')
set bar(Element? element) {}
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_angular_viewChildren_field_not_late() async {
addAngularPackage();
var content = '''
import 'dart:html';
import 'package:angular/angular.dart';
class myComponent {
@ViewChildren('foo')
Element bar;
Element baz;
f(Element e) {
bar = e;
baz = e;
}
g() => bar.id;
h() => baz.id;
}
''';
// `late` heuristics are disabled for `bar` since it's marked with
// `ViewChildren`. But they do apply to `baz`.
var expected = '''
import 'dart:html';
import 'package:angular/angular.dart';
class myComponent {
@ViewChildren('foo')
Element? bar;
late Element baz;
f(Element e) {
bar = e;
baz = e;
}
g() => bar!.id;
h() => baz.id;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_annotation_named_constructor() async {
var content = '''
class C {
final List<Object> values;
const factory C.ints(List<int> list) = C;
const C(this.values);
}
@C.ints([1, 2, 3])
class D {}
''';
var expected = '''
class C {
final List<Object> values;
const factory C.ints(List<int> list) = C;
const C(this.values);
}
@C.ints([1, 2, 3])
class D {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_argumentError_checkNotNull_implies_non_null_intent() async {
var content = '''
void f(int i) {
ArgumentError.checkNotNull(i);
}
void g(bool b, int i) {
if (b) f(i);
}
main() {
g(false, null);
}
''';
var expected = '''
void f(int i) {
ArgumentError.checkNotNull(i);
}
void g(bool b, int? i) {
if (b) f(i!);
}
main() {
g(false, null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_as_allows_null() async {
var content = '''
int f(Object o) => (o as int)?.gcd(1);
main() {
f(null);
}
''';
var expected = '''
int? f(Object? o) => (o as int?)?.gcd(1);
main() {
f(null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_assign_null_to_generic_type() async {
var content = '''
main() {
List<int> x = null;
}
''';
var expected = '''
main() {
List<int>? x = null;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_assignment_to_promoted_var_can_undo_promotion() async {
var content = '''
abstract class C {
void test() {
var x = f();
while (x != null) {
x = f();
}
}
int/*?*/ f();
}
''';
var expected = '''
abstract class C {
void test() {
var x = f();
while (x != null) {
x = f();
}
}
int? f();
}
''';
// Prior to the fix for https://github.com/dart-lang/sdk/issues/41411,
// migration would consider the LHS of `x = f()` to have context type
// non-nullable `int`, so it would add a null check to the value returned
// from `f`.
await _checkSingleFileChanges(content, expected);
}
Future<void> test_avoid_redundant_future_or() async {
// FutureOr<int?> and FutureOr<int?>? are equivalent types; we never insert
// the redundant second `?`.
var content = '''
import 'dart:async';
abstract class C {
FutureOr<int/*?*/> f();
FutureOr<int>/*?*/ g();
FutureOr<int> h(bool b) => b ? f() : g();
}
''';
var expected = '''
import 'dart:async';
abstract class C {
FutureOr<int?> f();
FutureOr<int>? g();
FutureOr<int?> h(bool b) => b ? f() : g();
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_await_null() async {
var content = '''
Future<int> test() async {
return await null;
}
''';
var expected = '''
Future<int?> test() async {
return await null;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void>
test_back_propagation_stops_at_implicitly_typed_variables() async {
var content = '''
class C {
int v;
C(this.v);
}
f(C c) {
var x = c.v;
print(x + 1);
}
main() {
C(null);
}
''';
var expected = '''
class C {
int? v;
C(this.v);
}
f(C c) {
var x = c.v!;
print(x + 1);
}
main() {
C(null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_built_value_non_nullable_getter() async {
addBuiltValuePackage();
var root = '$projectPath/lib';
var path1 = convertPath('$root/lib.dart');
var file1 = r'''
import 'package:built_value/built_value.dart';
part 'lib.g.dart';
abstract class Foo implements Built<Foo, FooBuilder> {
int get value;
Foo._();
factory Foo([void Function(FooBuilder) updates]) = _$Foo;
}
''';
var expected1 = r'''
import 'package:built_value/built_value.dart';
part 'lib.g.dart';
abstract class Foo implements Built<Foo, FooBuilder> {
int get value;
Foo._();
factory Foo([void Function(FooBuilder) updates]) = _$Foo;
}
''';
// Note: in a real-world scenario the generated file would be in a different
// directory but we don't need to simulate that detail for this test. Also,
// the generated file would have a lot more code in it, but we don't need to
// simulate all the details of what is generated.
var path2 = convertPath('$root/lib.g.dart');
var file2 = r'''
part of 'lib.dart';
class _$Foo extends Foo {
@override
final int value;
factory _$Foo([void Function(FooBuilder) updates]) => throw '';
_$Foo._({this.value}) : super._() {
BuiltValueNullFieldError.checkNotNull(value, 'Foo', 'value');
}
}
class FooBuilder implements Builder<Foo, FooBuilder> {
int get value => throw '';
}
''';
await _checkMultipleFileChanges(
{path1: file1, path2: file2}, {path1: expected1, path2: anything});
}
Future<void> test_built_value_nullable_getter() async {
addBuiltValuePackage();
var root = '$projectPath/lib';
var path1 = convertPath('$root/lib.dart');
var file1 = r'''
import 'package:built_value/built_value.dart';
part 'lib.g.dart';
abstract class Foo implements Built<Foo, FooBuilder> {
@nullable
int get value;
Foo._();
factory Foo([void Function(FooBuilder) updates]) = _$Foo;
}
''';
var expected1 = r'''
import 'package:built_value/built_value.dart';
part 'lib.g.dart';
abstract class Foo implements Built<Foo, FooBuilder> {
int? get value;
Foo._();
factory Foo([void Function(FooBuilder) updates]) = _$Foo;
}
''';
// Note: in a real-world scenario the generated file would be in a different
// directory but we don't need to simulate that detail for this test. Also,
// the generated file would have a lot more code in it, but we don't need to
// simulate all the details of what is generated.
var path2 = convertPath('$root/lib.g.dart');
var file2 = r'''
part of 'lib.dart';
class _$Foo extends Foo {
@override
final int value;
factory _$Foo([void Function(FooBuilder) updates]) => throw '';
_$Foo._({this.value}) : super._();
}
class FooBuilder implements Builder<Foo, FooBuilder> {
int get value => throw '';
}
''';
await _checkMultipleFileChanges(
{path1: file1, path2: file2}, {path1: expected1, path2: anything});
}
Future<void> test_built_value_nullable_getter_interface_only() async {
addBuiltValuePackage();
var content = '''
import 'package:built_value/built_value.dart';
abstract class Foo {
@nullable
int get value;
}
''';
var expected = '''
import 'package:built_value/built_value.dart';
abstract class Foo {
int? get value;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_call_already_migrated_extension() async {
var content = '''
import 'already_migrated.dart';
void f() {
<int>[].g();
}
''';
var alreadyMigrated = '''
// @dart=2.12
extension Ext<T> on List<T> {
g() {}
}
''';
var expected = '''
import 'already_migrated.dart';
void f() {
<int>[].g();
}
''';
await _checkSingleFileChanges(content, expected, migratedInput: {
'$projectPath/lib/already_migrated.dart': alreadyMigrated
});
}
Future<void> test_call_generic_function_returns_generic_class() async {
var content = '''
class B<E> implements List<E/*?*/> {
final C c;
B(this.c);
B<T> cast<T>() => c._castFrom<E, T>(this);
noSuchMethod(invocation) => super.noSuchMethod(invocation);
}
abstract class C {
B<T> _castFrom<S, T>(B<S> source);
}
''';
var expected = '''
class B<E> implements List<E?> {
final C c;
B(this.c);
B<T> cast<T>() => c._castFrom<E, T>(this);
noSuchMethod(invocation) => super.noSuchMethod(invocation);
}
abstract class C {
B<T> _castFrom<S, T>(B<S> source);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_call_migrated_base_class_method_non_nullable() async {
var content = '''
abstract class M<V> implements Map<String, V> {}
void f(bool b, M<int> m, int i) {
if (b) {
m['x'] = i;
}
}
void g(bool b, M<int> m) {
f(b, m, null);
}
''';
var expected = '''
abstract class M<V> implements Map<String, V> {}
void f(bool b, M<int?> m, int? i) {
if (b) {
m['x'] = i;
}
}
void g(bool b, M<int?> m) {
f(b, m, null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_call_migrated_base_class_method_nullable() async {
var content = '''
abstract class M<V> implements Map<String, V> {}
int f(M<int> m) => m['x'];
''';
var expected = '''
abstract class M<V> implements Map<String, V> {}
int? f(M<int> m) => m['x'];
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_call_tearoff() async {
var content = '''
class C {
void call() {}
}
void Function() f(C c) => c;
''';
var expected = '''
class C {
void call() {}
}
void Function() f(C c) => c;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_call_tearoff_already_migrated() async {
var content = '''
import 'already_migrated.dart';
void Function() f(C c) => c;
''';
var alreadyMigrated = '''
// @dart=2.12
class C {
void call() {}
}
''';
var expected = '''
import 'already_migrated.dart';
void Function() f(C c) => c;
''';
await _checkSingleFileChanges(content, expected, migratedInput: {
'$projectPath/lib/already_migrated.dart': alreadyMigrated
});
}
Future<void>
test_call_tearoff_already_migrated_propagate_nullability() async {
var content = '''
import 'already_migrated.dart';
Map<int, String> Function() f(C c) => c;
''';
var alreadyMigrated = '''
// @dart=2.12
class C {
Map<int, String?> call() => {};
}
''';
var expected = '''
import 'already_migrated.dart';
Map<int, String?> Function() f(C c) => c;
''';
await _checkSingleFileChanges(content, expected, migratedInput: {
'$projectPath/lib/already_migrated.dart': alreadyMigrated
});
}
Future<void> test_call_tearoff_already_migrated_with_substitution() async {
var content = '''
import 'already_migrated.dart';
Map<int, String> Function() f(C<String/*?*/> c) => c;
''';
var alreadyMigrated = '''
// @dart=2.12
class C<T> {
Map<int, T> call() => {};
}
''';
var expected = '''
import 'already_migrated.dart';
Map<int, String?> Function() f(C<String?> c) => c;
''';
await _checkSingleFileChanges(content, expected, migratedInput: {
'$projectPath/lib/already_migrated.dart': alreadyMigrated
});
}
Future<void> test_call_tearoff_futureOr() async {
var content = '''
import 'dart:async';
class C {
void call() {}
}
FutureOr<void Function()> f(C c) => c;
''';
var expected = '''
import 'dart:async';
class C {
void call() {}
}
FutureOr<void Function()> f(C c) => c;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_call_tearoff_inherited() async {
var content = '''
class B {
void call() {}
}
class C extends B {}
void Function() f(C c) => c;
''';
var expected = '''
class B {
void call() {}
}
class C extends B {}
void Function() f(C c) => c;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_call_tearoff_inherited_propagate_nullability() async {
var content = '''
class B {
Map<int, String> call() => {1: null};
}
class C extends B {}
Map<int, String> Function() f(C c) => c;
''';
var expected = '''
class B {
Map<int, String?> call() => {1: null};
}
class C extends B {}
Map<int, String?> Function() f(C c) => c;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_call_tearoff_propagate_nullability() async {
var content = '''
class C {
Map<int, String> call() => {1: null};
}
Map<int, String> Function() f(C c) => c;
''';
var expected = '''
class C {
Map<int, String?> call() => {1: null};
}
Map<int, String?> Function() f(C c) => c;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_call_tearoff_raw_function() async {
var content = '''
class C {
void call() {}
}
Function f(C c) => c;
''';
var expected = '''
class C {
void call() {}
}
Function f(C c) => c;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_catch_simple() async {
var content = '''
void f() {
try {} catch (ex, st) {}
}
''';
var expected = '''
void f() {
try {} catch (ex, st) {}
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_catch_simple_with_modifications() async {
var content = '''
void f(String x, StackTrace y) {
try {} catch (ex, st) {
ex = x;
st = y;
}
}
main() {
f(null, null);
}
''';
var expected = '''
void f(String? x, StackTrace? y) {
try {} catch (ex, st) {
ex = x;
st = y!;
}
}
main() {
f(null, null);
}
''';
// Note: using allowErrors=true because variables introduced by a catch
// clause are final
await _checkSingleFileChanges(content, expected, allowErrors: true);
}
Future<void> test_catch_with_on() async {
var content = '''
void f() {
try {} on String catch (ex, st) {}
}
''';
var expected = '''
void f() {
try {} on String catch (ex, st) {}
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_catch_with_on_with_modifications() async {
var content = '''
void f(String x, StackTrace y) {
try {} on String catch (ex, st) {
ex = x;
st = y;
}
}
main() {
f(null, null);
}
''';
var expected = '''
void f(String? x, StackTrace? y) {
try {} on String? catch (ex, st) {
ex = x;
st = y!;
}
}
main() {
f(null, null);
}
''';
// Note: using allowErrors=true because variables introduced by a catch
// clause are final
await _checkSingleFileChanges(content, expected, allowErrors: true);
}
Future<void> test_class_alias_synthetic_constructor_with_parameters() async {
var content = '''
void main() {
D d = D(null);
}
class C {
C(int i);
}
mixin M {}
class D = C with M;
''';
var expected = '''
void main() {
D d = D(null);
}
class C {
C(int? i);
}
mixin M {}
class D = C with M;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void>
test_class_alias_synthetic_constructor_with_parameters_and_subclass() async {
var content = '''
void main() {
E e = E(null);
}
class C {
C(int i);
}
mixin M {}
class D = C with M;
class E extends D {
E(int i) : super(i);
}
''';
var expected = '''
void main() {
E e = E(null);
}
class C {
C(int? i);
}
mixin M {}
class D = C with M;
class E extends D {
E(int? i) : super(i);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_class_type_param_bound_references_class() async {
var content = '''
class Node<T extends Node<T>> {
final List<T> nodes = <T>[];
}
class C extends Node<C> {}
main() {
var x = C();
x.nodes.add(x);
}
''';
var expected = '''
class Node<T extends Node<T>> {
final List<T> nodes = <T>[];
}
class C extends Node<C> {}
main() {
var x = C();
x.nodes.add(x);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_class_with_default_constructor() async {
var content = '''
void main() => f(Foo());
f(Foo f) {}
class Foo {}
''';
var expected = '''
void main() => f(Foo());
f(Foo f) {}
class Foo {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_code_inside_switch_does_not_imply_non_null_intent() async {
var content = '''
int f(int i, int j) {
switch (i) {
case 0:
return j + 1;
default:
return 0;
}
}
int g(int i, int j) {
if (i == 0) {
return f(i, j);
} else {
return 0;
}
}
main() {
g(0, null);
}
''';
var expected = '''
int f(int i, int? j) {
switch (i) {
case 0:
return j! + 1;
default:
return 0;
}
}
int g(int i, int? j) {
if (i == 0) {
return f(i, j);
} else {
return 0;
}
}
main() {
g(0, null);
}
''';
// Note: prior to the fix for https://github.com/dart-lang/sdk/issues/41407,
// we would consider the use of `j` in `f` to establish non-null intent, so
// the null check would be erroneously placed in `g`'s call to `f`.
await _checkSingleFileChanges(content, expected, warnOnWeakCode: true);
}
Future<void> test_collection_literal_typed_list() async {
var content = '''
void f(int/*?*/ x, int/*?*/ y) {
g(<int>[x, y]);
}
g(List<int/*!*/>/*!*/ z) {}
''';
var expected = '''
void f(int? x, int? y) {
g(<int>[x!, y!]);
}
g(List<int> z) {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_collection_literal_typed_map() async {
var content = '''
void f(int/*?*/ x, int/*?*/ y) {
g(<int, int>{x: y});
}
g(Map<int/*!*/, int/*!*/>/*!*/ z) {}
''';
var expected = '''
void f(int? x, int? y) {
g(<int, int>{x!: y!});
}
g(Map<int, int> z) {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_collection_literal_typed_set() async {
var content = '''
void f(int/*?*/ x, int/*?*/ y) {
g(<int>{x, y});
}
g(Set<int/*!*/>/*!*/ z) {}
''';
var expected = '''
void f(int? x, int? y) {
g(<int>{x!, y!});
}
g(Set<int> z) {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_collection_literal_untyped_list() async {
var content = '''
void f(int/*?*/ x, int/*?*/ y) {
g([x, y]);
}
g(List<int/*!*/>/*!*/ z) {}
''';
var expected = '''
void f(int? x, int? y) {
g([x!, y!]);
}
g(List<int> z) {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_collection_literal_untyped_map() async {
var content = '''
void f(int/*?*/ x, int/*?*/ y) {
g({x: y});
}
g(Map<int/*!*/, int/*!*/>/*!*/ z) {}
''';
var expected = '''
void f(int? x, int? y) {
g({x!: y!});
}
g(Map<int, int> z) {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_collection_literal_untyped_set() async {
var content = '''
void f(int/*?*/ x, int/*?*/ y) {
g({x, y});
}
g(Set<int/*!*/>/*!*/ z) {}
''';
var expected = '''
void f(int? x, int? y) {
g({x!, y!});
}
g(Set<int> z) {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_comment_bang_implies_non_null_intent() async {
var content = '''
void f(int/*!*/ i) {}
void g(bool b, int i) {
if (b) f(i);
}
main() {
g(false, null);
}
''';
var expected = '''
void f(int i) {}
void g(bool b, int? i) {
if (b) f(i!);
}
main() {
g(false, null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_comment_question_implies_nullable() async {
var content = '''
void _f() {
int/*?*/ i = 0;
}
''';
var expected = '''
void _f() {
int? i = 0;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void>
test_conditional_assert_statement_does_not_imply_non_null_intent() async {
var content = '''
void f(bool b, int i) {
if (b) return;
assert(i != null);
}
void g(bool b, int i) {
if (b) f(b, i);
}
main() {
g(true, null);
}
''';
var expected = '''
void f(bool b, int? i) {
if (b) return;
assert(i != null);
}
void g(bool b, int? i) {
if (b) f(b, i);
}
main() {
g(true, null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void>
test_conditional_dereference_does_not_imply_non_null_intent() async {
var content = '''
void f(bool b, int i) {
if (b) i.abs();
}
void g(bool b, int i) {
if (b) f(b, i);
}
main() {
g(false, null);
}
''';
var expected = '''
void f(bool b, int? i) {
if (b) i!.abs();
}
void g(bool b, int? i) {
if (b) f(b, i);
}
main() {
g(false, null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_conditional_expression_guard_subexpression() async {
var content = '''
void f(String s, int x, int/*?*/ n) {
s == null ? (x = n) : (x = s.length);
}
''';
var expected = '''
void f(String s, int x, int? n) {
s == null ? (x = n!) : (x = s.length);
}
''';
await _checkSingleFileChanges(content, expected, warnOnWeakCode: true);
}
Future<void> test_conditional_expression_guard_value_ifFalse() async {
var content = 'int f(String s, int/*?*/ n) => s != null ? s.length : n;';
var expected = 'int f(String s, int? n) => s != null ? s.length : n!;';
await _checkSingleFileChanges(content, expected, warnOnWeakCode: true);
}
Future<void> test_conditional_expression_guard_value_ifTrue() async {
var content = 'int f(String s, int/*?*/ n) => s == null ? n : s.length;';
var expected = 'int f(String s, int? n) => s == null ? n! : s.length;';
await _checkSingleFileChanges(content, expected, warnOnWeakCode: true);
}
Future<void>
test_conditional_non_null_usage_does_not_imply_non_null_intent() async {
var content = '''
void f(bool b, int i, int j) {
if (b) i.gcd(j);
}
void g(bool b, int i, int j) {
if (b) f(b, i, j);
}
main() {
g(false, 0, null);
}
''';
var expected = '''
void f(bool b, int i, int? j) {
if (b) i.gcd(j!);
}
void g(bool b, int i, int? j) {
if (b) f(b, i, j);
}
main() {
g(false, 0, null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void>
test_conditional_usage_does_not_propagate_non_null_intent() async {
var content = '''
void f(int i) {
assert(i != null);
}
void g(bool b, int i) {
if (b) f(i);
}
void h(bool b1, bool b2, int i) {
if (b1) g(b2, i);
}
main() {
h(true, false, null);
}
''';
var expected = '''
void f(int i) {
assert(i != null);
}
void g(bool b, int? i) {
if (b) f(i!);
}
void h(bool b1, bool b2, int? i) {
if (b1) g(b2, i);
}
main() {
h(true, false, null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_conditionalExpression_typeParameter_bound() async {
var content = '''
num f1<T extends num>(bool b, num x, T y) => b ? x : y;
num f2<T extends num>(bool b, num x, T y) => b ? x : y;
num f3<T extends num>(bool b, num x, T y) => b ? x : y;
num f4<T extends num>(bool b, num x, T y) => b ? x : y;
void main() {
int x1 = f1<int/*?*/>(true, 0, null);
int x2 = f2<int/*!*/>(true, 0, null);
int x3 = f3<int>(true, null, 0);
int x4 = f4<int>(true, 0, 0);
}
''';
var expected = '''
num? f1<T extends num?>(bool b, num x, T y) => b ? x : y;
num? f2<T extends num>(bool b, num x, T? y) => b ? x : y;
num? f3<T extends num>(bool b, num? x, T y) => b ? x : y;
num f4<T extends num>(bool b, num x, T y) => b ? x : y;
void main() {
int? x1 = f1<int?>(true, 0, null) as int?;
int? x2 = f2<int>(true, 0, null) as int?;
int? x3 = f3<int>(true, null, 0) as int?;
int x4 = f4<int>(true, 0, 0) as int;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_constructor_field_formal_resolves_to_getter() async {
var content = '''
class C {
int get i => 0;
C(this.i);
}
''';
// It doesn't matter what the migration produces; we just want to make sure
// there isn't a crash.
await _checkSingleFileChanges(content, anything, allowErrors: true);
}
Future<void> test_constructor_field_formal_resolves_to_setter() async {
var content = '''
class C {
set i(int value) {}
C(this.i);
}
''';
// It doesn't matter what the migration produces; we just want to make sure
// there isn't a crash.
await _checkSingleFileChanges(content, anything, allowErrors: true);
}
Future<void> test_constructor_field_formal_unresolved() async {
var content = '''
class C {
C(this.i);
}
''';
// It doesn't matter what the migration produces; we just want to make sure
// there isn't a crash.
await _checkSingleFileChanges(content, anything, allowErrors: true);
}
Future<void> test_constructor_optional_param_factory() async {
var content = '''
class C {
factory C([int x]) => C._();
C._([int x = 0]);
}
''';
var expected = '''
class C {
factory C([int? x]) => C._();
C._([int x = 0]);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void>
test_constructor_optional_param_factory_redirecting_named() async {
var content = '''
class C {
factory C({int x}) = C._;
C._({int x = 0});
}
''';
var expected = '''
class C {
factory C({int x}) = C._;
C._({int x = 0});
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void>
test_constructor_optional_param_factory_redirecting_unnamed() async {
var content = '''
class C {
factory C([int x]) = C._;
C._([int x = 0]);
}
''';
var expected = '''
class C {
factory C([int x]) = C._;
C._([int x = 0]);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_constructor_optional_param_normal() async {
var content = '''
class C {
C([int x]);
}
''';
var expected = '''
class C {
C([int? x]);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_constructor_optional_param_redirecting() async {
var content = '''
class C {
C([int x]) : this._();
C._([int x = 0]);
}
''';
var expected = '''
class C {
C([int? x]) : this._();
C._([int x = 0]);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_constructorDeclaration_factory_non_null_return() async {
var content = '''
class C {
C._();
factory C() {
C c = f();
return c;
}
}
C f() => null;
''';
var expected = '''
class C {
C._();
factory C() {
C c = f()!;
return c;
}
}
C? f() => null;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_constructorDeclaration_factory_simple() async {
var content = '''
class C {
C._();
factory C(int i) => C._();
}
main() {
C(null);
}
''';
var expected = '''
class C {
C._();
factory C(int? i) => C._();
}
main() {
C(null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_constructorDeclaration_named() async {
var content = '''
class C {
C.named(int i);
}
main() {
C.named(null);
}
''';
var expected = '''
class C {
C.named(int? i);
}
main() {
C.named(null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_constructorDeclaration_namedParameter() async {
var content = '''
class C {
C({Key key});
}
class Key {}
''';
var expected = '''
class C {
C({Key? key});
}
class Key {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_convert_required() async {
addMetaPackage();
var content = '''
import 'package:meta/meta.dart';
void f({@required String s}) {}
''';
var expected = '''
import 'package:meta/meta.dart';
void f({required String s}) {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_custom_future() async {
var content = '''
class CustomFuture<T> implements Future<T> {
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
f(CustomFuture<List<int>> x) async => (await x).first;
''';
var expected = '''
class CustomFuture<T> implements Future<T> {
@override
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
f(CustomFuture<List<int>> x) async => (await x).first;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_assignment_field() async {
var content = '''
class C {
int x = 0;
}
void f(C c) {
c.x = null;
}
''';
var expected = '''
class C {
int? x = 0;
}
void f(C c) {
c.x = null;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_assignment_field_in_cascade() async {
var content = '''
class C {
int x = 0;
}
void f(C c) {
c..x = null;
}
''';
var expected = '''
class C {
int? x = 0;
}
void f(C c) {
c..x = null;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_assignment_local() async {
var content = '''
void main() {
int i = 0;
i = null;
}
''';
var expected = '''
void main() {
int? i = 0;
i = null;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_assignment_setter() async {
var content = '''
class C {
void set s(int value) {}
}
void f(C c) {
c.s = null;
}
''';
var expected = '''
class C {
void set s(int? value) {}
}
void f(C c) {
c.s = null;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_field_read() async {
var content = '''
class C {
int/*?*/ f = 0;
}
int f(C c) => c.f;
''';
var expected = '''
class C {
int? f = 0;
}
int? f(C c) => c.f;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_function_return_type() async {
var content = '''
int Function() f(int Function() x) => x;
int g() => null;
main() {
f(g);
}
''';
var expected = '''
int? Function() f(int? Function() x) => x;
int? g() => null;
main() {
f(g);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_generic_contravariant_inward() async {
var content = '''
class C<T> {
void f(T t) {}
}
void g(C<int> c, int i) {
c.f(i);
}
void test(C<int> c) {
g(c, null);
}
''';
// Default behavior is to add nullability at the call site. Rationale: this
// is correct in the common case where the generic parameter represents the
// type of an item in a container. Also, if there are many callers that are
// largely independent, adding nullability to the callee would likely
// propagate to a field in the class, and thence (via return values of other
// methods) to most users of the class. Whereas if we add nullability at
// the call site it's possible that other call sites won't need it.
var expected = '''
class C<T> {
void f(T t) {}
}
void g(C<int?> c, int? i) {
c.f(i);
}
void test(C<int?> c) {
g(c, null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_generic_contravariant_inward_function() async {
var content = '''
T f<T>(T t) => t;
int g(int x) => f<int>(x);
void h() {
g(null);
}
''';
// As with the generic class case (see
// [test_data_flow_generic_contravariant_inward_function]), we favor adding
// nullability at the call site, so that other uses of `f` don't necessarily
// see a nullable return value.
var expected = '''
T f<T>(T t) => t;
int? g(int? x) => f<int?>(x);
void h() {
g(null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void>
test_data_flow_generic_contravariant_inward_using_core_class() async {
var content = '''
void f(List<int> x, int i) {
x.add(i);
}
void test(List<int> x) {
f(x, null);
}
''';
var expected = '''
void f(List<int?> x, int? i) {
x.add(i);
}
void test(List<int?> x) {
f(x, null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_generic_covariant_outward() async {
var content = '''
class C<T> {
T getValue() => null;
}
int f(C<int> x) => x.getValue();
''';
var expected = '''
class C<T> {
T? getValue() => null;
}
int? f(C<int> x) => x.getValue();
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_generic_covariant_substituted() async {
var content = '''
abstract class C<T> {
T getValue();
}
int f(C<int/*?*/> x) => x.getValue();
''';
var expected = '''
abstract class C<T> {
T getValue();
}
int? f(C<int?> x) => x.getValue();
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_indexed_get_index_value() async {
var content = '''
class C {
int operator[](int i) => 1;
}
int f(C c) => c[null];
''';
var expected = '''
class C {
int operator[](int? i) => 1;
}
int f(C c) => c[null];
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_indexed_get_value() async {
var content = '''
class C {
int operator[](int i) => null;
}
int f(C c) => c[0];
''';
var expected = '''
class C {
int? operator[](int i) => null;
}
int? f(C c) => c[0];
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_indexed_set_index_value() async {
var content = '''
class C {
void operator[]=(int i, int j) {}
}
void f(C c) {
c[null] = 0;
}
''';
var expected = '''
class C {
void operator[]=(int? i, int j) {}
}
void f(C c) {
c[null] = 0;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_indexed_set_index_value_in_cascade() async {
var content = '''
class C {
void operator[]=(int i, int j) {}
}
void f(C c) {
c..[null] = 0;
}
''';
var expected = '''
class C {
void operator[]=(int? i, int j) {}
}
void f(C c) {
c..[null] = 0;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_indexed_set_value() async {
var content = '''
class C {
void operator[]=(int i, int j) {}
}
void f(C c) {
c[0] = null;
}
''';
var expected = '''
class C {
void operator[]=(int i, int? j) {}
}
void f(C c) {
c[0] = null;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_inward() async {
var content = '''
int f(int i) => 0;
int g(int i) => f(i);
void test() {
g(null);
}
''';
var expected = '''
int f(int? i) => 0;
int g(int? i) => f(i);
void test() {
g(null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_inward_missing_type() async {
var content = '''
int f(int i) => 0;
int g(i) => f(i); // TODO(danrubel): suggest type
void test() {
g(null);
}
''';
var expected = '''
int f(int? i) => 0;
int g(i) => f(i); // TODO(danrubel): suggest type
void test() {
g(null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_local_declaration() async {
var content = '''
void f(int i) {
int j = i;
}
main() {
f(null);
}
''';
var expected = '''
void f(int? i) {
int? j = i;
}
main() {
f(null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_local_reference() async {
var content = '''
void f(int i) {}
void g(int i) {
int j = i;
f(i);
}
main() {
g(null);
}
''';
var expected = '''
void f(int? i) {}
void g(int? i) {
int? j = i;
f(i);
}
main() {
g(null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_method_call_in_cascade() async {
var content = '''
class C {
void m(int x) {}
}
void f(C c) {
c..m(null);
}
''';
var expected = '''
class C {
void m(int? x) {}
}
void f(C c) {
c..m(null);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_outward() async {
var content = '''
int f(int i) => null;
int g(int i) => f(i);
''';
var expected = '''
int? f(int i) => null;
int? g(int i) => f(i);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_data_flow_outward_missing_type() async {
var content = '''
f(int i) => null; // TODO(danrubel): suggest type
int g(int i) => f(i);
''';
var expected = '''
f(int i) => null; // TODO(danrubel): suggest type
int? g(int i) => f(i);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_definitely_assigned_value() async {
var content = '''
String f(bool b) {
String s;
if (b) {
s = 'true';
} else {
s = 'false';
}
return s;
}
''';
var expected = '''
String f(bool b) {
String s;
if (b) {
s = 'true';
} else {
s = 'false';
}
return s;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_discard_simple_condition_keep_else() async {
var content = '''
int f(int i) {
if (i == null) {
return null;
} else {
return i + 1;
}
}
''';
var expected = '''
int f(int i) {
/* if (i == null) {
return null;
} else {
*/ return i + 1; /*
} */
}
''';
await _checkSingleFileChanges(content, expected, removeViaComments: true);
}
Future<void> test_discard_simple_condition_keep_then() async {
var content = '''
int f(int i) {
if (i != null) {
return i + 1;
} else {
return null;
}
}
''';
var expected = '''
int f(int i) {
/* if (i != null) {
*/ return i + 1; /*
} else {
return null;
} */
}
''';
await _checkSingleFileChanges(content, expected, removeViaComments: true);
}
Future<void> test_do_not_add_question_to_null_type() async {
var content = '''
Null f() => null;
''';
var expected = '''
Null f() => null;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_do_not_propagate_non_null_intent_into_callback() async {
var content = '''
void f(int/*!*/ Function(int) callback) {
callback(null);
}
int g(int x) => x;
void test() {
f(g);
}
''';
// Even though `g` is passed to `f`'s `callback` parameter, non-null intent
// is not allowed to propagate backward from the return type of `callback`
// to the return type of `g`, because `g` might be used elsewhere in a
// context where it's important for its return type to be nullable. So no
// null check is added to `g`, and instead a cast (which is guaranteed to
// fail) is added at the site of the call to `f`.
//
// Note: https://github.com/dart-lang/sdk/issues/40471 tracks the fact that
// we ought to alert the user to the presence of such casts.
var expected = '''
void f(int Function(int?) callback) {
callback(null);
}
int? g(int? x) => x;
void test() {
f(g as int Function(int?));
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_do_not_surround_named_expression() async {
var content = '''
void f(int/*?*/ x, int/*?*/ y) {
g(named: <int>[x, y]);
}
g({List<int/*!*/>/*!*/ named}) {}
''';
var expected = '''
void f(int? x, int? y) {
g(named: <int>[x!, y!]);
}
g({required List<int> named}) {}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_downcast_dynamic_function_to_functionType() async {
var content = '''
void f(Function a) {
int Function<T>(String y) f1 = a;
Function b = null;
int Function<T>(String y) f2 = b;
}
''';
// Don't assume any new nullabilities, but keep known nullabilities.
var expected = '''
void f(Function a) {
int Function<T>(String y) f1 = a as int Function<T>(String);
Function? b = null;
int Function<T>(String y)? f2 = b as int Function<T>(String)?;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_downcast_dynamic_to_functionType() async {
var content = '''
void f(dynamic a) {
int Function<T>(String y) f1 = a;
dynamic b = null;
int Function<T>(String y) f2 = b;
}
''';
// Don't assume any new nullabilities, but keep known nullabilities.
var expected = '''
void f(dynamic a) {
int Function<T>(String y) f1 = a;
dynamic b = null;
int Function<T>(String y)? f2 = b;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_downcast_dynamic_type_argument() async {
// This pattern is common and seems to have this as a best migration. It is
// less clear, but plausible, that this holds for other types of type
// parameter downcasts.
var content = '''
List<int> f(List a) => a;
void main() {
f(<int>[null]);
}
''';
var expected = '''
List<int?> f(List a) => a as List<int?>;
void main() {
f(<int?>[null]);
}
''';
await _checkSingleFileChanges(content, expected);
}
@failingTest
Future<void> test_downcast_not_widest_type_type_parameters() async {
// Fails because a hard assignment from List<int/*1*/> to List<int/*2*/>
// doesn't create a hard edge from 1 to 2. Perhaps this is correct. In this
// example it seems incorrect.
var content = '''
void f(dynamic a) {
List<int> hardToNonNullNonNull = a;
List<int> hardToNullNonNull = a;
List<int> hardToNonNullNull = a;
List<int/*!*/>/*!*/ nonNullNonNull;
List<int/*?*/>/*!*/ nullNonNull;
List<int/*!*/>/*?*/ nonNullNull;
nonNullNonNull = hardToNonNullNonNull
nonNullNull = hardToNonNullNull
nullNonNull = hardToNullNonNull
}
''';
var expected = '''
void f(dynamic a) {
List<int> hardToNonNullNonNull = a;
List<int?> hardToNullNonNull = a;
List<int>? hardToNonNullNull = a;
List<int> nonNullNonNull;
List<int?> nullNonNull;
List<int>? nonNullNull;
nonNullNonNull = hardToNonNullNonNull
nonNullNull = hardToNonNullNull
nullNonNull = hardToNullNonNull
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_downcast_to_null() async {
// This probably doesn't arise too often for real-world code, since it is
// most likely a mistake. Still, we want to make sure we don't crash.
var content = '''
test() {
var x = List.filled(3, null);
x[0] = 1;
}
''';
var expected = '''
test() {
var x = List.filled(3, null);
x[0] = 1 as Null;
}
''';
// Note: using allowErrors=true because casting a literal int to a Null is
// an error
await _checkSingleFileChanges(content, expected, allowErrors: true);
}
Future<void> test_downcast_type_argument_preserve_nullability() async {
// There are no examples in front of us yet where anyone downcasts a type
// with a nullable type parameter. This is maybe correct, maybe not, and it
// unblocks us to find out which at a later point in time.
var content = '''
List<int> f(Iterable<num> a) => a;
void main() {
f(<num>[null]);
}
''';
var expected = '''
List<int?> f(Iterable<num?> a) => a as List<int?>;
void main() {
f(<num?>[null]);
}
''';
await _checkSingleFileChanges(content, expected);
}
@failingTest
Future<void> test_downcast_widest_type_from_related_type_parameters() async {
var content = '''
List<int> f(Iterable<int/*?*/> a) => a;
''';
var expected = '''
List<int?> f(Iterable<int?> a) => a;
''';
await _checkSingleFileChanges(content, expected);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/39368')
Future<void> test_downcast_widest_type_from_top_type_parameters() async {
var content = '''
List<int> f1(dynamic a) => a;
List<int> f2(Object b) => b;
''';
// Note: even though the type `dynamic` permits `null`, the migration engine
// sees that there is no code path that could cause `f1` to be passed a null
// value, so it leaves its return type as non-nullable.
var expected = '''
List<int?> f1(dynamic a) => a;
List<int?> f2(Object b) => b;
''';
await _checkSingleFileChanges(content, expected);
}
@failingTest
Future<void>
test_downcast_widest_type_from_unrelated_type_parameters() async {
var content = '''
abstract class C<A, B> implements List<A> {}
C<int, num> f(List<int> a) => a;
''';
var expected = '''
abstract class C<A, B> implements List<A> {}
C<int, num?> f(List<int> a) => a;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_dynamic_dispatch_to_object_method() async {
var content = '''
String f(dynamic x) => x.toString();
''';
var expected = '''
String f(dynamic x) => x.toString();
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_dynamic_method_call() async {
var content = '''
class C {
int g(int i) => i;
}
int f(bool b, dynamic d) {
if (b) return 0;
return d.g(null);
}
main() {
f(true, null);
f(false, C());
}
''';
// `d.g(null)` is a dynamic call, so we can't tell that it will target `C.g`
// at runtime. So we can't figure out that we need to make g's argument and
// return types nullable.
//
// We do, however, make f's return type nullable, since there is no way of
// knowing whether a dynamic call will return `null`.
var expected = '''
class C {
int g(int i) => i;
}
int? f(bool b, dynamic d) {
if (b) return 0;
return d.g(null);
}
main() {
f(true, null);
f(false, C());
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_dynamic_property_access() async {
var content = '''
class C {
int get g => 0;
}
int f(bool b, dynamic d) {
if (b) return 0;
return d.g;
}
main() {
f(true, null);
f(false, C());
}
''';
var expected = '''
class C {
int get g => 0;
}
int? f(bool b, dynamic d) {
if (b) return 0;
return d.g;
}
main() {
f(true, null);
f(false, C());
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_dynamic_toString() async {
var content = '''
String f(dynamic x) => x.toString();
''';
var expected = '''
String f(dynamic x) => x.toString();
''';
await _checkSingleFileChanges(content, expected);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/40174')
Future<void> test_eliminate_dead_if_inside_for_element() async {
var content = '''
List<int> _f(List<int/*!*/> xs) => [for(var x in xs) if (x == null) 1];
''';
var expected = '''
List<int> _f(List<int> xs) => [];
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_enum() async {
var content = '''
enum E {
value
}
E f() => E.value;
int g() => f().index;
void h() {
for(var value in E.values) {}
E.values.forEach((value) {});
f().toString();
f().runtimeType;
f().hashCode;
f().noSuchMethod(throw '');
f() == f();
}
''';
var expected = '''
enum E {
value
}
E f() => E.value;
int g() => f().index;
void h() {
for(var value in E.values) {}
E.values.forEach((value) {});
f().toString();
f().runtimeType;
f().hashCode;
f().noSuchMethod(throw '');
f() == f();
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_exact_nullability_counterexample() async {
var content = '''
void f(List<int> x) {
x.add(1);
}
void g() {
f([null]);
}
void h(List<int> x) {
f(x);
}
''';
// The `null` in `g` causes `f`'s `x` argument to have type `List<int?>`.
// Even though `f` calls a method that uses `List`'s type parameter
// contravariantly (the `add` method), that is not sufficient to cause exact
// nullability propagation, since value passed to `add` has a
// non-nullable type. So nullability is *not* propagated back to `h`.
var expected = '''
void f(List<int?> x) {
x.add(1);
}
void g() {
f([null]);
}
void h(List<int> x) {
f(x);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_exact_nullability_doesnt_affect_function_args() async {
// Test attempting to create a bug from #40625. Currently passes, but if it
// breaks, that bug may need to be reopened.
var content = '''
class C<T> {
int Function(T) f;
}
void main(dynamic d) {
C<String> c = d;
int Function(String) f1 = c.f; // should not have a nullable arg
c.f(null); // exact nullability induced here
}
''';
var expected = '''
class C<T> {
int Function(T)? f;
}
void main(dynamic d) {
C<String?> c = d;
int Function(String)? f1 = c.f; // should not have a nullable arg
c.f!(null); // exact nullability induced here
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_exact_nullability_doesnt_affect_function_returns() async {
// Test attempting to create a bug from #40625. Currently passes, but if it
// breaks, that bug may need to be reopened.
var content = '''
class C<T> {
T Function(String) f;
}
int Function(String) f1; // should not have a nullable return
void main(dynamic d) {
C<int> c = d;
c.f = f1;
c.f = (_) => null; // exact nullability induced here
}
''';
var expected = '''
class C<T> {
T Function(String)? f;
}
int Function(String)? f1; // should not have a nullable return
void main(dynamic d) {
C<int?> c = d;
c.f = f1;
c.f = (_) => null; // exact nullability induced here
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_exact_nullability_doesnt_affect_typedef_args() async {
// Test attempting to create a bug from #40625. Currently passes, but if it
// breaks, that bug may need to be reopened.
var content = '''
typedef F<T> = int Function(T);
F<String> f1;
void main() {
f1(null); // induce exact nullability
int Function(String) f2 = f1; // shouldn't have a nullable arg
}
''';
var expected = '''
typedef F<T> = int Function(T);
F<String?>? f1;
void main() {
f1!(null); // induce exact nullability
int Function(String)? f2 = f1; // shouldn't have a nullable arg
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_exact_nullability_doesnt_affect_typedef_returns() async {
// Test attempting to create a bug from #40625. Currently passes, but if it
// breaks, that bug may need to be reopened.
var content = '''
typedef F<T> = T Function(String);
int Function(String) f1; // should not have a nullable return
void main() {
F<int> f2 = f1;
f2 = (_) => null; // exact nullability induced here
}
''';
var expected = '''
typedef F<T> = T Function(String);
int Function(String)? f1; // should not have a nullable return
void main() {
F<int?>? f2 = f1;
f2 = (_) => null; // exact nullability induced here
}
''';
await _checkSingleFileChanges(content, expected);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/41409')
Future<void> test_exact_nullability_in_nested_list() async {
var content = '''
f(List<int/*?*/> y) {
var x = <List<int>>[];
x.add(y);
}
''';
var expected = '''
f(List<int?> y) {
var x = <List<int?>>[];
x.add(y);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_explicit_nullable_overrides_hard_edge() async {
var content = '''
int f(int/*?*/ i) => i + 1;
''';
var expected = '''
int f(int? i) => i! + 1;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_expression_bang_hint() async {
var content = '''
int f(int/*?*/ i) => i/*!*/;
''';
var expected = '''
int f(int? i) => i!;
''';
await _checkSingleFileChanges(content, expected, removeViaComments: true);
}
Future<void> test_expression_bang_hint_in_as() async {
var content = '''
int f(num/*?*/ i) => i as int/*!*/;
''';
var expected = '''
int f(num? i) => i as int;
''';
await _checkSingleFileChanges(content, expected, removeViaComments: true);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/41788')
Future<void> test_expression_bang_hint_in_as_wrapped() async {
var content = '''
int f(num/*?*/ i) => (i as int)/*!*/;
''';
var expected = '''
int f(num? i) => (i as int?)!;
''';
await _checkSingleFileChanges(content, expected, removeViaComments: true);
}
Future<void> test_expression_bang_hint_unnecessary() async {
var content = '''
int/*?*/ f(int/*?*/ i) => i/*!*/;
''';
// The user requested a null check so we should add it even if it's not
// required to avoid compile errors.
var expected = '''
int? f(int? i) => i!;
''';
await _checkSingleFileChanges(content, expected, removeViaComments: true);
}
Future<void> test_expression_bang_hint_unnecessary_literal() async {
var content = 'int/*?*/ f() => 1/*!*/;';
// The user requested a null check so we should add it even if it's not
// required to avoid compile errors.
var expected = 'int? f() => 1!;';
await _checkSingleFileChanges(content, expected, removeViaComments: true);
}
Future<void> test_expression_bang_hint_with_cast() async {
var content = 'int f(Object/*?*/ o) => o/*!*/;';
var expected = 'int f(Object? o) => o! as int;';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_expression_nullable_cast_then_checked() async {
var content = '''
int/*!*/ f(num/*?*/ i) => (i as int);
''';
var expected = '''
int f(num? i) => (i as int);
''';
await _checkSingleFileChanges(content, expected, removeViaComments: true);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/41788')
Future<void> test_expression_wrapped_with_null_check_and_null_intent() async {
var content = '''
int/*!*/ f(int/*?*/ i) => (i)/*!*/;
''';
var expected = '''
int f(int? i) => i!;
''';
await _checkSingleFileChanges(content, expected, removeViaComments: true);
}
Future<void> test_extension_complex() async {
var content = '''
import 'already_migrated.dart';
class D<V> extends C<V> {}
abstract class Foo {
D<List<int>> get z;
List<int> test() => z.x ?? [];
}
''';
var alreadyMigrated = '''
// @dart=2.12
extension E<T> on C<T> {
T? get x => y;
}
class C<U> {
U? y;
}
''';
var expected = '''
import 'already_migrated.dart';
class D<V> extends C<V> {}
abstract class Foo {
D<List<int>> get z;
List<int> test() => z.x ?? [];
}
''';
await _checkSingleFileChanges(content, expected, migratedInput: {
'$projectPath/lib/already_migrated.dart': alreadyMigrated
});
}
Future<void> test_extension_extended_type_nullability_intent() async {
var content = '''
extension E on C {
String foo() => this.bar();
}
class C {
String bar() => null;
}
void test(C c, bool b) {
if (b) {
c.foo();
}
}
main() {
test(null, false);
}
''';
// The call to `bar` from `foo` should be taken as a demonstration that the
// extension E is not intended to apply to nullable types, so the call to
// `foo` should be null checked.
var expected = '''
extension E on C {
String? foo() => this.bar();
}
class C {
String? bar() => null;
}
void test(C? c, bool b) {
if (b) {
c!.foo();
}
}
main() {
test(null, false);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_null_check_non_nullable_binary() async {
var content = '''
class C {}
extension E on C/*!*/ {
void operator+(int other) {}
}
void f(C c, bool b) {
if (b) {
c + 0;
}
}
void g() => f(null, false);
''';
var expected = '''
class C {}
extension E on C {
void operator+(int other) {}
}
void f(C? c, bool b) {
if (b) {
c! + 0;
}
}
void g() => f(null, false);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_null_check_non_nullable_generic() async {
var content = '''
class C {}
extension E<T extends Object/*!*/> on T/*!*/ {
void m() {}
}
void f(C c, bool b) {
if (b) {
c.m();
}
}
void g() => f(null, false);
''';
var expected = '''
class C {}
extension E<T extends Object> on T {
void m() {}
}
void f(C? c, bool b) {
if (b) {
c!.m();
}
}
void g() => f(null, false);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_null_check_non_nullable_index() async {
var content = '''
class C {}
extension E on C/*!*/ {
void operator[](int index) {}
}
void f(C c, bool b) {
if (b) {
c[0];
}
}
void g() => f(null, false);
''';
var expected = '''
class C {}
extension E on C {
void operator[](int index) {}
}
void f(C? c, bool b) {
if (b) {
c![0];
}
}
void g() => f(null, false);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_null_check_non_nullable_method() async {
var content = '''
class C {}
extension E on C/*!*/ {
void m() {}
}
void f(C c, bool b) {
if (b) {
c.m();
}
}
void g() => f(null, false);
''';
var expected = '''
class C {}
extension E on C {
void m() {}
}
void f(C? c, bool b) {
if (b) {
c!.m();
}
}
void g() => f(null, false);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_null_check_non_nullable_prefix() async {
var content = '''
class C {}
extension E on C/*!*/ {
void operator-() {}
}
void f(C c, bool b) {
if (b) {
-c;
}
}
void g() => f(null, false);
''';
var expected = '''
class C {}
extension E on C {
void operator-() {}
}
void f(C? c, bool b) {
if (b) {
-c!;
}
}
void g() => f(null, false);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_null_check_nullable() async {
var content = '''
class C {}
extension E on C/*?*/ {
void m() {}
}
void f(C c, bool b) {
if (b) {
c.m();
}
}
void g() => f(null, false);
''';
var expected = '''
class C {}
extension E on C? {
void m() {}
}
void f(C? c, bool b) {
if (b) {
c.m();
}
}
void g() => f(null, false);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_null_check_nullable_generic() async {
var content = '''
class C {}
extension E<T extends Object/*?*/> on T/*!*/ {
void m() {}
}
void f(C c, bool b) {
if (b) {
c.m();
}
}
void g() => f(null, false);
''';
var expected = '''
class C {}
extension E<T extends Object?> on T {
void m() {}
}
void f(C? c, bool b) {
if (b) {
c.m();
}
}
void g() => f(null, false);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_null_check_target() async {
var content = '''
extension E on int/*!*/ {
int get plusOne => this + 1;
}
int f(int/*?*/ x) => x.plusOne;
''';
var expected = '''
extension E on int {
int get plusOne => this + 1;
}
int f(int? x) => x!.plusOne;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_nullable_target() async {
var content = '''
extension E on int {
int get one => 1;
}
int f(int/*?*/ x) => x.one;
''';
var expected = '''
extension E on int? {
int get one => 1;
}
int f(int? x) => x.one;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_nullableOnType_addsNullCheckToThis() async {
var content = '''
extension E on String /*?*/ {
void m() => this.length;
}
''';
var expected = '''
extension E on String? {
void m() => this!.length;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_nullableOnType_typeArgument() async {
var content = '''
extension E on List<String> {
void m() {}
}
void f(List<String> list) => list.m();
void g() => f([null]);
''';
var expected = '''
extension E on List<String?> {
void m() {}
}
void f(List<String?> list) => list.m();
void g() => f([null]);
''';
await _checkSingleFileChanges(content, expected);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/40023')
Future<void> test_extension_nullableOnType_typeVariable() async {
var content = '''
extension E<T> on List<T> {
void m() {}
}
void f<U>(List<U> list) => list.m();
void g() => f([null]);
''';
var expected = '''
extension E<T> on List<T?> {
void m() {}
}
void f<U>(List<U?> list) => list.m();
void g() => f([null]);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_nullableOnType_viaExplicitInvocation() async {
var content = '''
class C {}
extension E on C {
void m() {}
}
void f() => E(null).m();
''';
var expected = '''
class C {}
extension E on C? {
void m() {}
}
void f() => E(null).m();
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_nullableOnType_viaImplicitInvocation() async {
var content = '''
class C {}
extension E on C {
void m() {}
}
void f(C c) => c.m();
void g() => f(null);
''';
var expected = '''
class C {}
extension E on C? {
void m() {}
}
void f(C? c) => c.m();
void g() => f(null);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_on_generic_type() async {
var content = '''
class C<T> {
final T value;
C(this.value);
}
extension E<T> on Future<C<T/*?*/>> {
Future<T> get asyncValue async => (await this).value;
}
''';
var expected = '''
class C<T> {
final T value;
C(this.value);
}
extension E<T> on Future<C<T?>> {
Future<T?> get asyncValue async => (await this).value;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_on_type_param_implementation() async {
var content = '''
abstract class C {
C _clone();
}
extension Cloner<T extends C> on T {
T clone() => _clone() as T;
}
''';
var expected = '''
abstract class C {
C _clone();
}
extension Cloner<T extends C> on T {
T clone() => _clone() as T;
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_on_type_param_usage() async {
var content = '''
abstract class C {
C _clone();
}
extension Cloner<T extends C> on T {
T clone() => throw Exception();
}
C f(C c) => c.clone();
''';
var expected = '''
abstract class C {
C _clone();
}
extension Cloner<T extends C> on T {
T clone() => throw Exception();
}
C f(C c) => c.clone();
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_on_type_substitution() async {
var content = '''
extension E<T> on T {
T get foo => this;
}
List<int> f(List<int/*?*/> x) => x.foo;
''';
// To see that the return type of `f` must be `List<int?`, the migration
// tool needs to substitute the actual type argument (`T=List<int?>`) into
// the extension's "on" type.
var expected = '''
extension E<T> on T {
T get foo => this;
}
List<int?> f(List<int?> x) => x.foo;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_override() async {
var content = '''
extension E on int {
int get plusOne => this + 1;
}
int f(int x) => E(x).plusOne;
''';
var expected = '''
extension E on int {
int get plusOne => this + 1;
}
int f(int x) => E(x).plusOne;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_override_null_check_target() async {
var content = '''
extension E on int/*!*/ {
int get plusOne => this + 1;
}
int f(int/*?*/ x) => E(x).plusOne;
''';
var expected = '''
extension E on int {
int get plusOne => this + 1;
}
int f(int? x) => E(x!).plusOne;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_override_nullable_result_type() async {
var content = '''
extension E on int {
int get nullValue => null;
}
int f(int x) => E(x).nullValue;
''';
var expected = '''
extension E on int {
int? get nullValue => null;
}
int? f(int x) => E(x).nullValue;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_override_nullable_target() async {
var content = '''
extension E on int {
int get one => 1;
}
int f(int/*?*/ x) => E(x).one;
''';
var expected = '''
extension E on int? {
int get one => 1;
}
int f(int? x) => E(x).one;
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_extension_use_can_imply_non_null_intent() async {
var content = '''
extension E<T extends Object/*!*/> on T/*!*/ {
void foo() {}
}
f(int i) {
i.foo();
}
g(bool b, int/*?*/ j) {
if (b) {
f(j);
}
}
''';
// Since the extension declaration says `T extends Object/*!*/`, `i` will
// not be made nullable.
var expected = '''
extension E<T extends Object> on T {
void foo() {}
}
f(int i) {
i.foo();
}
g(bool b, int? j) {
if (b) {
f(j!);
}
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_external_constructor() async {
var content = '''
class C {
external C(dynamic Function(dynamic) callback);
static Object g(Object Function(Object) callback) => C(callback);
}
''';
var expected = '''
class C {
external C(dynamic Function(dynamic) callback);
static Object g(Object Function(Object?) callback) => C(callback);
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_external_function() async {
var content = '''
external dynamic f();
Object g() => f();
''';
var expected = '''
external dynamic f();
Object? g() => f();
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_external_function_implicit_return() async {
var content = '''
external f();
Object g() => f();
''';
var expected = '''
external f();
Object? g() => f();
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_external_function_implicit_variance() async {
var content = '''
external void f(callback(x));
void g(Object Function(Object) callback) => f(callback);
''';
var expected = '''
external void f(callback(x));
void g(Object Function(Object?) callback) => f(callback);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_external_function_implicit_variance_complex() async {
var content = '''
external void f(callback(x()));
void g(Object Function(Object Function()) callback) => f(callback);
''';
var expected = '''
external void f(callback(x()));
void g(Object Function(Object? Function()) callback) => f(callback);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_external_function_variance() async {
var content = '''
external void f(dynamic Function(dynamic) callback);
void g(Object Function(Object) callback) => f(callback);
''';
var expected = '''
external void f(dynamic Function(dynamic) callback);
void g(Object Function(Object?) callback) => f(callback);
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_external_method() async {
var content = '''
class C {
external dynamic f();
Object g() => f();
}
''';
var expected = '''
class C {
external dynamic f();
Object? g() => f();
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_external_method_implicit() async {
var content = '''
class C {
external f();
Object g() => f();
}
''';
var expected = '''
class C {
external f();
Object? g() => f();
}
''';
await _checkSingleFileChanges(content, expected);
}
Future<void> test_field_final_uninitalized_used() async {
var content = '''
class C {
final String s;
f() {
g(s);
}
}
g(String /*!*/ s) {}
''';
var expected = '''
class C {
late final String s;
f() {
g(s);
}
}
g(String s) {}
''';
// Note: using allowErrors=true because an uninitialized field is an error
await _checkSingleFileChanges(content, expected, allowErrors: true);
}
Future<void> test_field_formal_param_typed() async {
var content = '''
class C {
int i;
C(int this.i);
}
main() {
C(null);
}
''';
var expected = '''
class C {
int? i;