mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
5349d8c602
Previously, if a variable with a generic type was null-checked, and the migration engine did not add a question mark to the type of the variable itself, e.g.: f<T extends Object?>(T x) { if (x == null) { ... } } Then the migration tool would erroneously consider the null check unnecessary. It failed to account for the fact that the bound on the generic type might be nullable. This change fixes the tool so that it now properly accounts for the bound on the generic type variable. Bug: https://b.corp.google.com/issues/247496662 Change-Id: I2b131e75b7e3fc5db0b668f1064340d404442c82 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/264501 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com>
10472 lines
212 KiB
Dart
10472 lines
212 KiB
Dart
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'package: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 migration 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_at_required_to_required_in_redirecting_factory() async {
|
|
// Redirecting factory constructors have special logic to suppress some of
|
|
// the usual heuristics for adding `required`, since it's allowed for a
|
|
// redirecting factory constructor to have a non-required non-nullable
|
|
// argument with no default. But we need to make sure that we still convert
|
|
// `@required` to `required`.
|
|
addMetaPackage();
|
|
var content = r'''
|
|
import 'package:meta/meta.dart';
|
|
abstract class A {
|
|
int get v;
|
|
A._();
|
|
factory A({@required int v}) = B._;
|
|
}
|
|
class B extends A {
|
|
@override
|
|
final int v;
|
|
B._({this.v}) : super._();
|
|
}
|
|
''';
|
|
var expected = r'''
|
|
import 'package:meta/meta.dart';
|
|
abstract class A {
|
|
int? get v;
|
|
A._();
|
|
factory A({required int v}) = B._;
|
|
}
|
|
class B extends A {
|
|
@override
|
|
final int? v;
|
|
B._({this.v}) : super._();
|
|
}
|
|
''';
|
|
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_await_nullable_future_to_non_nullable() async {
|
|
var content = '''
|
|
Future<String> foo() async => null;
|
|
|
|
Future<String/*!*/> bar() async {
|
|
return await foo();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
Future<String?> foo() async => null;
|
|
|
|
Future<String> bar() async {
|
|
return (await foo())!;
|
|
}
|
|
''';
|
|
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_uninitialized_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;
|
|
C(int? this.i);
|
|
}
|
|
main() {
|
|
C(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_formal_param_typed_non_nullable() async {
|
|
var content = '''
|
|
class C {
|
|
int/*!*/ i;
|
|
C(int this.i);
|
|
}
|
|
void f(int i, bool b) {
|
|
if (b) {
|
|
C(i);
|
|
}
|
|
}
|
|
main() {
|
|
f(null, false);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i;
|
|
C(int this.i);
|
|
}
|
|
void f(int? i, bool b) {
|
|
if (b) {
|
|
C(i!);
|
|
}
|
|
}
|
|
main() {
|
|
f(null, false);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_formal_param_untyped() async {
|
|
var content = '''
|
|
class C {
|
|
int i;
|
|
C(this.i);
|
|
}
|
|
main() {
|
|
C(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int? i;
|
|
C(this.i);
|
|
}
|
|
main() {
|
|
C(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_formal_parameters_do_not_promote() async {
|
|
var content = '''
|
|
class A {}
|
|
|
|
class B extends A {}
|
|
|
|
class C extends A {}
|
|
|
|
abstract class D {
|
|
final A x;
|
|
D(this.x) {
|
|
if (x is B) {
|
|
visitB(x);
|
|
} else {
|
|
visitC(x as C);
|
|
}
|
|
}
|
|
|
|
void visitB(B b);
|
|
|
|
void visitC(C c);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class A {}
|
|
|
|
class B extends A {}
|
|
|
|
class C extends A {}
|
|
|
|
abstract class D {
|
|
final A x;
|
|
D(this.x) {
|
|
if (x is B) {
|
|
visitB(x as B);
|
|
} else {
|
|
visitC(x as C);
|
|
}
|
|
}
|
|
|
|
void visitB(B b);
|
|
|
|
void visitC(C c);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_initialized_at_declaration_site() async {
|
|
var content = '''
|
|
class C {
|
|
int i = 0;
|
|
C();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i = 0;
|
|
C();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_field_initialized_at_declaration_site_no_constructor() async {
|
|
var content = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_initialized_in_constructor() async {
|
|
var content = '''
|
|
class C {
|
|
int i;
|
|
C() : i = 0;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i;
|
|
C() : i = 0;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_field_initialized_in_constructor_with_factories_and_redirects() async {
|
|
var content = '''
|
|
class C {
|
|
int i;
|
|
C() : i = 0;
|
|
factory C.factoryConstructor() => C();
|
|
factory C.factoryRedirect() = D;
|
|
C.redirect() : this();
|
|
}
|
|
class D extends C {}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i;
|
|
C() : i = 0;
|
|
factory C.factoryConstructor() => C();
|
|
factory C.factoryRedirect() = D;
|
|
C.redirect() : this();
|
|
}
|
|
class D extends C {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_initializer_simple() async {
|
|
var content = '''
|
|
class C {
|
|
int f;
|
|
C(int i) : f = i;
|
|
}
|
|
main() {
|
|
C(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int? f;
|
|
C(int? i) : f = i;
|
|
}
|
|
main() {
|
|
C(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_initializer_typed_list_literal() async {
|
|
var content = '''
|
|
class C {
|
|
List<int> f;
|
|
C() : f = <int>[null];
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
List<int?> f;
|
|
C() : f = <int?>[null];
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_initializer_untyped_list_literal() async {
|
|
var content = '''
|
|
class C {
|
|
List<int> f;
|
|
C() : f = [null];
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
List<int?> f;
|
|
C() : f = [null];
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_initializer_untyped_map_literal() async {
|
|
var content = '''
|
|
class C {
|
|
Map<String, int> f;
|
|
C() : f = {"foo": null};
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
Map<String, int?> f;
|
|
C() : f = {"foo": null};
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_initializer_untyped_set_literal() async {
|
|
var content = '''
|
|
class C {
|
|
Set<int> f;
|
|
C() : f = {null};
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
Set<int?> f;
|
|
C() : f = {null};
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_not_initialized() async {
|
|
var content = '''
|
|
class C {
|
|
int i;
|
|
C();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int? i;
|
|
C();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_not_initialized_no_constructor() async {
|
|
var content = '''
|
|
class C {
|
|
int i;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int? i;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_overrides_field_in_mixin() async {
|
|
var content = '''
|
|
class C extends Object with M {
|
|
int x;
|
|
}
|
|
|
|
mixin M {
|
|
int x;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C extends Object with M {
|
|
int? x;
|
|
}
|
|
|
|
mixin M {
|
|
int? x;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_overrides_getter() async {
|
|
var content = '''
|
|
abstract class C {
|
|
int get i;
|
|
}
|
|
class D implements C {
|
|
@override
|
|
final int i;
|
|
D._() : i = computeI();
|
|
}
|
|
int computeI() => null;
|
|
''';
|
|
var expected = '''
|
|
abstract class C {
|
|
int? get i;
|
|
}
|
|
class D implements C {
|
|
@override
|
|
final int? i;
|
|
D._() : i = computeI();
|
|
}
|
|
int? computeI() => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_type_inferred() async {
|
|
var content = '''
|
|
int f() => null;
|
|
class C {
|
|
var x = 1;
|
|
void g() {
|
|
x = f();
|
|
}
|
|
}
|
|
''';
|
|
// The type of x is inferred as non-nullable from its initializer, but we
|
|
// try to assign a nullable value to it. So an explicit type must be added.
|
|
var expected = '''
|
|
int? f() => null;
|
|
class C {
|
|
int? x = 1;
|
|
void g() {
|
|
x = f();
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_uninitialized_used() async {
|
|
var content = '''
|
|
class C {
|
|
String s;
|
|
|
|
f() {
|
|
g(s);
|
|
}
|
|
}
|
|
g(String /*!*/ s) {}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
late String s;
|
|
|
|
f() {
|
|
g(s);
|
|
}
|
|
}
|
|
g(String s) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_field_uninitialized_used_hint() async {
|
|
var content = '''
|
|
class C {
|
|
String /*?*/ s;
|
|
|
|
f() {
|
|
g(s);
|
|
}
|
|
}
|
|
g(String /*!*/ s) {}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
String? s;
|
|
|
|
f() {
|
|
g(s!);
|
|
}
|
|
}
|
|
g(String s) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_firstWhere_complex_target() async {
|
|
// See https://github.com/dart-lang/sdk/issues/43956
|
|
var content = '''
|
|
Iterable<Match> allMatches(String str) => 'x'.allMatches(str);
|
|
|
|
Match matchAsPrefix(String str, [int start = 0]) {
|
|
return allMatches(str)
|
|
.firstWhere((match) => match.start == start, orElse: () => null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableExtension;
|
|
|
|
Iterable<Match> allMatches(String str) => 'x'.allMatches(str);
|
|
|
|
Match? matchAsPrefix(String str, [int start = 0]) {
|
|
return allMatches(str)
|
|
.firstWhereOrNull((match) => match.start == start);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_firstWhere_non_nullable() async {
|
|
var content = '''
|
|
int firstEven(Iterable<int> x)
|
|
=> x.firstWhere((x) => x.isEven, orElse: () => null);
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableExtension;
|
|
|
|
int? firstEven(Iterable<int> x)
|
|
=> x.firstWhereOrNull((x) => x.isEven);
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_firstWhere_non_nullable_with_cast() async {
|
|
var content = '''
|
|
int firstNonZero(Iterable<num> x)
|
|
=> x.firstWhere((x) => x != 0, orElse: () => null);
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableExtension;
|
|
|
|
int? firstNonZero(Iterable<num> x)
|
|
=> x.firstWhereOrNull((x) => x != 0) as int?;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_firstWhere_non_nullable_with_non_null_assertion() async {
|
|
var content = '''
|
|
int/*!*/ firstEven(Iterable<int> x)
|
|
=> x.firstWhere((x) => x.isEven, orElse: () => null);
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableExtension;
|
|
|
|
int firstEven(Iterable<int> x)
|
|
=> x.firstWhereOrNull((x) => x.isEven)!;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_firstWhere_nullable() async {
|
|
var content = '''
|
|
int firstEven(Iterable<int> x)
|
|
=> x.firstWhere((x) => x.isEven, orElse: () => null);
|
|
f() => firstEven([null]);
|
|
''';
|
|
var expected = '''
|
|
int? firstEven(Iterable<int?> x)
|
|
=> x.firstWhere((x) => x!.isEven, orElse: () => null);
|
|
f() => firstEven([null]);
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_firstWhere_nullable_with_cast() async {
|
|
var content = '''
|
|
int firstNonZero(Iterable<num> x)
|
|
=> x.firstWhere((x) => x != 0, orElse: () => null);
|
|
f() => firstNonZero([null]);
|
|
''';
|
|
var expected = '''
|
|
int? firstNonZero(Iterable<num?> x)
|
|
=> x.firstWhere((x) => x != 0, orElse: () => null) as int?;
|
|
f() => firstNonZero([null]);
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_flow_analysis_complex() async {
|
|
var content = '''
|
|
int f(int x) {
|
|
while (x == null) {
|
|
x = g(x);
|
|
}
|
|
return x;
|
|
}
|
|
int g(int x) => x == null ? 1 : null;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
// Flow analysis can tell that the loop only exits if x is non-null, so the
|
|
// return type of `f` can remain `int`, and no null check is needed.
|
|
var expected = '''
|
|
int f(int? x) {
|
|
while (x == null) {
|
|
x = g(x);
|
|
}
|
|
return x;
|
|
}
|
|
int? g(int? x) => x == null ? 1 : null;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_flow_analysis_simple() async {
|
|
var content = '''
|
|
int f(int x) {
|
|
if (x == null) {
|
|
return 0;
|
|
} else {
|
|
return x;
|
|
}
|
|
}
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int f(int? x) {
|
|
if (x == null) {
|
|
return 0;
|
|
} else {
|
|
return x;
|
|
}
|
|
}
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_for_each_basic() async {
|
|
var content = '''
|
|
void f(List<int> l) {
|
|
for (var x in l) {
|
|
g(x);
|
|
}
|
|
}
|
|
void g(int x) {}
|
|
main() {
|
|
f([null]);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(List<int?> l) {
|
|
for (var x in l) {
|
|
g(x);
|
|
}
|
|
}
|
|
void g(int? x) {}
|
|
main() {
|
|
f([null]);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_for_each_variable_initialized() async {
|
|
var content = '''
|
|
int sum(List<int> list) {
|
|
int total = 0;
|
|
for (var i in list) {
|
|
total = total + i;
|
|
}
|
|
return total;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int sum(List<int> list) {
|
|
int total = 0;
|
|
for (var i in list) {
|
|
total = total + i;
|
|
}
|
|
return total;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_function_expression() async {
|
|
var content = '''
|
|
int f(int i) {
|
|
var g = (int j) => i;
|
|
return g(i);
|
|
}
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int? f(int? i) {
|
|
var g = (int? j) => i;
|
|
return g(i);
|
|
}
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_function_expression_invocation() async {
|
|
var content = '''
|
|
abstract class C {
|
|
void Function(int) f();
|
|
int/*?*/ Function() g();
|
|
}
|
|
int test(C c) {
|
|
c.f()(null);
|
|
return c.g()();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
abstract class C {
|
|
void Function(int?) f();
|
|
int? Function() g();
|
|
}
|
|
int? test(C c) {
|
|
c.f()(null);
|
|
return c.g()();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_function_expression_invocation_via_getter() async {
|
|
var content = '''
|
|
abstract class C {
|
|
void Function(int) get f;
|
|
int/*?*/ Function() get g;
|
|
}
|
|
int test(C c) {
|
|
c.f(null);
|
|
return c.g();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
abstract class C {
|
|
void Function(int?) get f;
|
|
int? Function() get g;
|
|
}
|
|
int? test(C c) {
|
|
c.f(null);
|
|
return c.g();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_function_expression_return() async {
|
|
var content = '''
|
|
void test({String foo}) async {
|
|
var f = () {
|
|
return "hello";
|
|
};
|
|
|
|
foo.length;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void test({required String foo}) async {
|
|
var f = () {
|
|
return "hello";
|
|
};
|
|
|
|
foo.length;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_function_typed_field_formal_param() async {
|
|
var content = '''
|
|
class C {
|
|
void Function(int) f;
|
|
C(void this.f(int i));
|
|
}
|
|
main() {
|
|
C(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
void Function(int)? f;
|
|
C(void this.f(int i)?);
|
|
}
|
|
main() {
|
|
C(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_function_typed_field_formal_param_accepts_hint() async {
|
|
var content = '''
|
|
class C {
|
|
void Function(int) f;
|
|
C(void this.f(int i) /*?*/);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
void Function(int)? f;
|
|
C(void this.f(int i)?);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_function_typed_field_formal_param_inner_types() async {
|
|
var content = '''
|
|
class C {
|
|
int Function(int) f;
|
|
C(int this.f(int i));
|
|
}
|
|
int g(int i) => i;
|
|
int test(int i) => C(g).f(i);
|
|
main() {
|
|
test(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int? Function(int?) f;
|
|
C(int? this.f(int? i));
|
|
}
|
|
int? g(int? i) => i;
|
|
int? test(int? i) => C(g).f(i);
|
|
main() {
|
|
test(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_function_typed_formal_param() async {
|
|
var content = '''
|
|
void f(g()) {}
|
|
void main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(g()?) {}
|
|
void main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_function_typed_formal_param_accepts_hint() async {
|
|
var content = '''
|
|
void f(g() /*?*/) {}
|
|
''';
|
|
var expected = '''
|
|
void f(g()?) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_function_typed_formal_param_inner_types() async {
|
|
var content = '''
|
|
int f(int callback(int i), int j) => callback(j);
|
|
int g(int i) => i;
|
|
int test(int i) => f(g, i);
|
|
main() {
|
|
test(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int? f(int? callback(int? i), int? j) => callback(j);
|
|
int? g(int? i) => i;
|
|
int? test(int? i) => f(g, i);
|
|
main() {
|
|
test(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_future_nullability_mismatch() async {
|
|
var content = '''
|
|
String foo;
|
|
|
|
Future<String> getNullableFoo() async {
|
|
return foo;
|
|
}
|
|
|
|
Future<String/*!*/> getFoo() {
|
|
return getNullableFoo();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
String? foo;
|
|
|
|
Future<String?> getNullableFoo() async {
|
|
return foo;
|
|
}
|
|
|
|
Future<String> getFoo() {
|
|
return getNullableFoo().then((value) => value!);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_future_or_t_downcast_to_t() async {
|
|
var content = '''
|
|
import 'dart:async';
|
|
void f(
|
|
FutureOr<int> foi1,
|
|
FutureOr<int/*?*/> foi2,
|
|
FutureOr<int>/*?*/ foi3,
|
|
FutureOr<int/*?*/>/*?*/ foi4
|
|
) {
|
|
int i1 = foi1;
|
|
int i2 = foi2;
|
|
int i3 = foi3;
|
|
int i4 = foi4;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'dart:async';
|
|
void f(
|
|
FutureOr<int> foi1,
|
|
FutureOr<int?> foi2,
|
|
FutureOr<int>? foi3,
|
|
FutureOr<int?> foi4
|
|
) {
|
|
int i1 = foi1 as int;
|
|
int? i2 = foi2 as int?;
|
|
int? i3 = foi3 as int?;
|
|
int? i4 = foi4 as int?;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_future_type_mismatch() async {
|
|
var content = '''
|
|
Future<List<int>> getNullableInts() async {
|
|
return [null];
|
|
}
|
|
|
|
Future<List<int/*!*/>> getInts() {
|
|
return getNullableInts();
|
|
}
|
|
''';
|
|
// TODO(paulberry): this is not a good migration. Really we should produce
|
|
// `getNullableInts().then((value) => value.cast());`.
|
|
var expected = '''
|
|
Future<List<int?>> getNullableInts() async {
|
|
return [null];
|
|
}
|
|
|
|
Future<List<int>> getInts() {
|
|
return getNullableInts().then((value) => value as List<int>);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_generic_exact_propagation() async {
|
|
var content = '''
|
|
class C<T> {
|
|
List<T> values;
|
|
C() : values = <T>[];
|
|
void add(T t) => values.add(t);
|
|
T operator[](int i) => values[i];
|
|
}
|
|
void f() {
|
|
C<int> x = new C<int>();
|
|
g(x);
|
|
}
|
|
void g(C<int> y) {
|
|
y.add(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<T> {
|
|
List<T> values;
|
|
C() : values = <T>[];
|
|
void add(T t) => values.add(t);
|
|
T operator[](int i) => values[i];
|
|
}
|
|
void f() {
|
|
C<int?> x = new C<int?>();
|
|
g(x);
|
|
}
|
|
void g(C<int?> y) {
|
|
y.add(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_generic_exact_propagation_premigratedListClass() async {
|
|
var content = '''
|
|
void f() {
|
|
List<int> x = new List<int>();
|
|
g(x);
|
|
}
|
|
void g(List<int> y) {
|
|
y.add(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f() {
|
|
List<int?> x = new List<int?>();
|
|
g(x);
|
|
}
|
|
void g(List<int?> y) {
|
|
y.add(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_generic_function_type_syntax_inferred_dynamic_return() async {
|
|
var content = '''
|
|
abstract class C {
|
|
Function() f();
|
|
}
|
|
Object g(C c) => c.f()();
|
|
''';
|
|
// Note: even though the type `dynamic` permits `null`, the migration engine
|
|
// sees that there is no code path that could cause `g` to return a null
|
|
// value, so it leaves its return type as `Object`, and there is an implicit
|
|
// downcast.
|
|
var expected = '''
|
|
abstract class C {
|
|
Function() f();
|
|
}
|
|
Object g(C c) => c.f()();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_generic_typedef_respects_explicit_nullability_of_type_arg() async {
|
|
var content = '''
|
|
class C {
|
|
final Comparator<int/*!*/> comparison;
|
|
C(int Function(int, int) comparison) : comparison = comparison;
|
|
void test() {
|
|
comparison(f(), f());
|
|
}
|
|
}
|
|
int f() => null;
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
final Comparator<int> comparison;
|
|
C(int Function(int, int) comparison) : comparison = comparison;
|
|
void test() {
|
|
comparison(f()!, f()!);
|
|
}
|
|
}
|
|
int? f() => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_genericType_noTypeArguments() async {
|
|
var content = '''
|
|
void f(C c) {}
|
|
class C<E> {}
|
|
''';
|
|
var expected = '''
|
|
void f(C c) {}
|
|
class C<E> {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/39404')
|
|
Future<void> test_genericType_noTypeArguments_use_bound() async {
|
|
var content = '''
|
|
abstract class C<T extends Object> { // (1)
|
|
void put(T t);
|
|
T get();
|
|
}
|
|
Object f(C c) => c.get(); // (2)
|
|
void g(C<int> c) { // (3)
|
|
c.put(null); // (4)
|
|
}
|
|
''';
|
|
// (4) forces `...C<int?>...` at (3), which means (1) must be
|
|
// `...extends Object?`. Therefore (2) is equivalent to
|
|
// `...f(C<Object?> c)...`, so the return type of `f` is `Object?`.
|
|
var expected = '''
|
|
abstract class C<T extends Object?> { // (1)
|
|
void put(T t);
|
|
T get();
|
|
}
|
|
Object? f(C c) => c.get(); // (2)
|
|
void g(C<int?> c) { // (3)
|
|
c.put(null); // (4)
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_getter_implicit_returnType_overrides_implicit_getter() async {
|
|
var content = '''
|
|
class A {
|
|
final String s = "x";
|
|
}
|
|
class C implements A {
|
|
get s => false ? "y" : null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class A {
|
|
final String? s = "x";
|
|
}
|
|
class C implements A {
|
|
get s => false ? "y" : null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_overrides_implicit_getter() async {
|
|
var content = '''
|
|
class A {
|
|
final String s = "x";
|
|
}
|
|
class C implements A {
|
|
String get s => false ? "y" : null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class A {
|
|
final String? s = "x";
|
|
}
|
|
class C implements A {
|
|
String? get s => false ? "y" : null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_overrides_implicit_getter_with_generics() async {
|
|
var content = '''
|
|
class A<T> {
|
|
final T value;
|
|
A(this.value);
|
|
}
|
|
class C implements A<String/*!*/> {
|
|
String get value => false ? "y" : null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class A<T> {
|
|
final T? value;
|
|
A(this.value);
|
|
}
|
|
class C implements A<String> {
|
|
String? get value => false ? "y" : null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_getter_in_interface() async {
|
|
var content = '''
|
|
class B {
|
|
int get x => null;
|
|
}
|
|
abstract class C implements B {
|
|
void set x(int value) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B {
|
|
int? get x => null;
|
|
}
|
|
abstract class C implements B {
|
|
void set x(int? value) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_getter_in_interface_field() async {
|
|
var content = '''
|
|
class B {
|
|
final int x = null;
|
|
}
|
|
abstract class C implements B {
|
|
void set x(int value) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B {
|
|
final int? x = null;
|
|
}
|
|
abstract class C implements B {
|
|
void set x(int? value) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_getter_in_interfaces() async {
|
|
var content = '''
|
|
class B1 {
|
|
int get x => null;
|
|
}
|
|
class B2 {
|
|
int get x => null;
|
|
}
|
|
abstract class C implements B1, B2 {
|
|
void set x(int value) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B1 {
|
|
int? get x => null;
|
|
}
|
|
class B2 {
|
|
int? get x => null;
|
|
}
|
|
abstract class C implements B1, B2 {
|
|
void set x(int? value) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_getter_in_superclass() async {
|
|
var content = '''
|
|
class B {
|
|
int get x => null;
|
|
}
|
|
class C extends B {
|
|
void set x(int value) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B {
|
|
int? get x => null;
|
|
}
|
|
class C extends B {
|
|
void set x(int? value) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_getter_in_superclass_substituted() async {
|
|
var content = '''
|
|
class B<T> {
|
|
T get x => throw '';
|
|
}
|
|
class C extends B<List<int/*?*/>> {
|
|
void set x(List<int> value) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B<T> {
|
|
T get x => throw '';
|
|
}
|
|
class C extends B<List<int?>> {
|
|
void set x(List<int?> value) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_setter_in_interface() async {
|
|
var content = '''
|
|
class B {
|
|
void set x(int value) {}
|
|
}
|
|
abstract class C implements B {
|
|
int get x => null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B {
|
|
void set x(int? value) {}
|
|
}
|
|
abstract class C implements B {
|
|
int? get x => null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_setter_in_interface_field() async {
|
|
var content = '''
|
|
class B {
|
|
int x;
|
|
B(this.x);
|
|
}
|
|
abstract class C implements B {
|
|
int get x => null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B {
|
|
int? x;
|
|
B(this.x);
|
|
}
|
|
abstract class C implements B {
|
|
int? get x => null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_setter_in_interfaces() async {
|
|
var content = '''
|
|
class B1 {
|
|
void set x(int value) {}
|
|
}
|
|
class B2 {
|
|
void set x(int value) {}
|
|
}
|
|
abstract class C implements B1, B2 {
|
|
int get x => null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B1 {
|
|
void set x(int? value) {}
|
|
}
|
|
class B2 {
|
|
void set x(int? value) {}
|
|
}
|
|
abstract class C implements B1, B2 {
|
|
int? get x => null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_setter_in_superclass() async {
|
|
var content = '''
|
|
class B {
|
|
void set x(int value) {}
|
|
}
|
|
class C extends B {
|
|
int get x => null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B {
|
|
void set x(int? value) {}
|
|
}
|
|
class C extends B {
|
|
int? get x => null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_setter_in_superclass_substituted() async {
|
|
var content = '''
|
|
class B<T> {
|
|
void set x(T value) {}
|
|
}
|
|
class C extends B<List<int>> {
|
|
List<int> get x => [null];
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B<T> {
|
|
void set x(T value) {}
|
|
}
|
|
class C extends B<List<int?>> {
|
|
List<int?> get x => [null];
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_single_class() async {
|
|
var content = '''
|
|
class C {
|
|
int get x => null;
|
|
void set x(int value) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int? get x => null;
|
|
void set x(int? value) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_single_class_generic() async {
|
|
var content = '''
|
|
class C<T extends Object/*!*/> {
|
|
T get x => null;
|
|
void set x(T value) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<T extends Object> {
|
|
T? get x => null;
|
|
void set x(T? value) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_static() async {
|
|
var content = '''
|
|
class C {
|
|
static int get x => null;
|
|
static void set x(int value) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
static int? get x => null;
|
|
static void set x(int? value) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_setter_top_level() async {
|
|
var content = '''
|
|
int get x => null;
|
|
void set x(int value) {}
|
|
''';
|
|
var expected = '''
|
|
int? get x => null;
|
|
void set x(int? value) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_getter_topLevel() async {
|
|
var content = '''
|
|
int get g => 0;
|
|
''';
|
|
var expected = '''
|
|
int get g => 0;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_hint_contradicts_exact_nullability() async {
|
|
var content = '''
|
|
void f(List<int> x) {
|
|
x.add(null);
|
|
}
|
|
void g() {
|
|
f(<int/*!*/>[]);
|
|
}
|
|
''';
|
|
// `f.x` needs to change to List<int?> to allow `null` to be added to the
|
|
// list. Ordinarily this would be propagated back to the explicit list in
|
|
// `g`, but we don't override the hint `/*!*/`.
|
|
//
|
|
// TODO(paulberry): we should probably issue some sort of warning to the
|
|
// user instead.
|
|
var expected = '''
|
|
void f(List<int?> x) {
|
|
x.add(null);
|
|
}
|
|
void g() {
|
|
f(<int>[]);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_ifStatement_nullCheck_noElse() async {
|
|
var content = '''
|
|
int f(int x) {
|
|
if (x == null) return 0;
|
|
return x;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int f(int x) {
|
|
return x;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_implicit_parameter_type_override_does_not_union() async {
|
|
var content = '''
|
|
abstract class A {
|
|
void f(int/*?*/ i);
|
|
}
|
|
abstract class B {
|
|
void f(int i);
|
|
}
|
|
class C implements A, B {
|
|
void f(i) {}
|
|
}
|
|
''';
|
|
// Even though the parameter type of C.f is implicit, its nullability
|
|
// shouldn't be unioned with that of A and B, because that would
|
|
// unnecessarily force B.f's parameter type to be nullable.
|
|
var expected = '''
|
|
abstract class A {
|
|
void f(int? i);
|
|
}
|
|
abstract class B {
|
|
void f(int i);
|
|
}
|
|
class C implements A, B {
|
|
void f(i) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_implicit_return_type_override_does_not_union() async {
|
|
var content = '''
|
|
abstract class A {
|
|
int/*?*/ f();
|
|
}
|
|
abstract class B {
|
|
int f();
|
|
}
|
|
class C implements A, B {
|
|
f() => 0;
|
|
}
|
|
''';
|
|
// Even though the return type of C.f is implicit, its nullability shouldn't
|
|
// be unioned with that of A and B, because that would unnecessarily force
|
|
// B.f's return type to be nullable.
|
|
var expected = '''
|
|
abstract class A {
|
|
int? f();
|
|
}
|
|
abstract class B {
|
|
int f();
|
|
}
|
|
class C implements A, B {
|
|
f() => 0;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_implicit_tearoff_type_arguments() async {
|
|
var content = '''
|
|
T f<T>(T t) => t;
|
|
int Function(int) g() => f;
|
|
''';
|
|
var expected = '''
|
|
T f<T>(T t) => t;
|
|
int Function(int) g() => f;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_implicit_type_parameter_bound_nullable() async {
|
|
var content = '''
|
|
class C<T> {
|
|
f(T t) {
|
|
Object o = t;
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<T> {
|
|
f(T t) {
|
|
Object? o = t;
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/49689')
|
|
Future<void> test_infer_late_with_cascaded_usage() async {
|
|
var content = '''
|
|
class A {
|
|
B b;
|
|
}
|
|
class B {
|
|
void f() {}
|
|
void g() {}
|
|
}
|
|
foo(A a) {
|
|
a.b..f()..g();
|
|
}
|
|
bar(A a) {
|
|
a.b = B();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class A {
|
|
late B b;
|
|
}
|
|
class B {
|
|
void f() {}
|
|
void g() {}
|
|
}
|
|
foo(A a) {
|
|
a.b..f()..g();
|
|
}
|
|
bar(A a) {
|
|
a.b = B();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/39376')
|
|
Future<void> test_infer_required() async {
|
|
var content = '''
|
|
void _f(bool b, {int x}) {
|
|
if (b) {
|
|
print(x + 1);
|
|
}
|
|
}
|
|
main() {
|
|
_f(true, x: 1);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void _f(bool b, {required int x}) {
|
|
if (b) {
|
|
print(x + 1);
|
|
}
|
|
}
|
|
main() {
|
|
_f(true, x: 1);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_inferred_method_return_type_non_nullable() async {
|
|
var content = '''
|
|
class B {
|
|
int f() => 1;
|
|
}
|
|
class C extends B {
|
|
f() => 1;
|
|
}
|
|
int g(C c) => c.f();
|
|
''';
|
|
// B.f's return type is `int`. Since C.f's return type is inferred from
|
|
// B.f's, it has a return type of `int` too. Therefore g's return type
|
|
// must be `int`.
|
|
var expected = '''
|
|
class B {
|
|
int f() => 1;
|
|
}
|
|
class C extends B {
|
|
f() => 1;
|
|
}
|
|
int g(C c) => c.f();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_insert_as_prefixed_type() async {
|
|
var content = '''
|
|
import 'dart:async' as a;
|
|
Future<int> f(Object o) => o;
|
|
''';
|
|
var expected = '''
|
|
import 'dart:async' as a;
|
|
Future<int> f(Object o) => o as a.Future<int>;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/40871')
|
|
Future<void> test_insert_type_with_prefix() async {
|
|
var content = '''
|
|
import 'dart:async' as a;
|
|
a.Future f(Object o) {
|
|
return o;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'dart:async' as a;
|
|
a.Future f(Object o) {
|
|
return o as a.Future;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/38469')
|
|
Future<void> test_inserted_nodes_properly_wrapped() async {
|
|
addMetaPackage();
|
|
var content = '''
|
|
class C {
|
|
C operator+(C other) => null;
|
|
}
|
|
void f(C x, C y) {
|
|
C z = x + y;
|
|
assert(z != null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
C operator+(C other) => null;
|
|
}
|
|
void f(C x, C y) {
|
|
C z = (x + y)!;
|
|
assert(z != null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_instance_creation_generic() async {
|
|
var content = '''
|
|
class C<T> {
|
|
C(T t);
|
|
}
|
|
main() {
|
|
C<int> c = C<int>(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<T> {
|
|
C(T t);
|
|
}
|
|
main() {
|
|
C<int?> c = C<int?>(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_instance_creation_generic_explicit_nonNullable() async {
|
|
var content = '''
|
|
class C<T extends Object/*!*/> {
|
|
C(T/*!*/ t);
|
|
}
|
|
test(int/*?*/ n) {
|
|
C<int> c = C<int>(n);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<T extends Object> {
|
|
C(T t);
|
|
}
|
|
test(int? n) {
|
|
C<int> c = C<int>(n!);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_instance_creation_generic_explicit_nonNullableParam() async {
|
|
var content = '''
|
|
class C<T> {
|
|
C(T/*!*/ t);
|
|
}
|
|
main() {
|
|
C<int> c = C<int>(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<T> {
|
|
C(T t);
|
|
}
|
|
main() {
|
|
C<int?> c = C<int?>(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_instance_creation_generic_implicit_nonNullable() async {
|
|
var content = '''
|
|
class C<T extends Object/*!*/> {
|
|
C(T/*!*/ t);
|
|
}
|
|
test(int/*?*/ n) {
|
|
C<int> c = C(n);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<T extends Object> {
|
|
C(T t);
|
|
}
|
|
test(int? n) {
|
|
C<int> c = C(n!);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_instance_creation_generic_implicit_nonNullableParam() async {
|
|
var content = '''
|
|
class C<T> {
|
|
C(T/*!*/ t);
|
|
}
|
|
main() {
|
|
C<int> c = C(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<T> {
|
|
C(T t);
|
|
}
|
|
main() {
|
|
C<int?> c = C(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_instanceCreation_noTypeArguments_noParameters() async {
|
|
var content = '''
|
|
void main() {
|
|
C c = C();
|
|
c.length;
|
|
}
|
|
class C {
|
|
int get length => 0;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void main() {
|
|
C c = C();
|
|
c.length;
|
|
}
|
|
class C {
|
|
int get length => 0;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_int_double_coercion() async {
|
|
var content = '''
|
|
double f() => 0;
|
|
''';
|
|
var expected = '''
|
|
double f() => 0;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_is_promotion_implies_non_nullable() async {
|
|
var content = '''
|
|
bool f(Object o) => o is int && o.isEven;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
bool f(Object? o) => o is int && o.isEven;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_is_promotion_implies_non_nullable_generic() async {
|
|
var content = '''
|
|
int f<T>(T o) => o is List ? o.length : 0;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int f<T>(T o) => o is List ? o.length : 0;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_isExpression_typeName_typeArguments() async {
|
|
var content = '''
|
|
bool f(a) => a is List<int>;
|
|
''';
|
|
var expected = '''
|
|
bool f(a) => a is List<int>;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_isExpression_with_function_type() async {
|
|
var content = '''
|
|
void test(Function f) {
|
|
if (f is void Function()) {
|
|
f();
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void test(Function f) {
|
|
if (f is void Function()) {
|
|
f();
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_issue_40181() async {
|
|
// This contrived example created an "exact nullable" type parameter bound
|
|
// which propagated back to *all* instantiations of that parameter.
|
|
var content = '''
|
|
class B<T extends Object> {
|
|
B([C<T> t]);
|
|
}
|
|
|
|
abstract class C<T extends Object> {
|
|
void f(T t);
|
|
}
|
|
|
|
class E {
|
|
final C<Object> _base;
|
|
E([C base]) : _base = base;
|
|
f(Object t) {
|
|
_base.f(t);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
E e = E();
|
|
e.f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B<T extends Object> {
|
|
B([C<T>? t]);
|
|
}
|
|
|
|
abstract class C<T extends Object?> {
|
|
void f(T t);
|
|
}
|
|
|
|
class E {
|
|
final C<Object?>? _base;
|
|
E([C? base]) : _base = base;
|
|
f(Object? t) {
|
|
_base!.f(t);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
E e = E();
|
|
e.f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_issue_41397() async {
|
|
var content = '''
|
|
void repro(){
|
|
List<dynamic> l = <dynamic>[];
|
|
for(final dynamic e in l) {
|
|
final List<String> a = (e['query'] as String).split('&');
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void repro(){
|
|
List<dynamic> l = <dynamic>[];
|
|
for(final dynamic e in l) {
|
|
final List<String> a = (e['query'] as String).split('&');
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_lastWhere_non_nullable() async {
|
|
var content = '''
|
|
int lastEven(Iterable<int> x)
|
|
=> x.lastWhere((x) => x.isEven, orElse: () => null);
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableExtension;
|
|
|
|
int? lastEven(Iterable<int> x)
|
|
=> x.lastWhereOrNull((x) => x.isEven);
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_lastWhere_nullable() async {
|
|
var content = '''
|
|
int lastEven(Iterable<int> x)
|
|
=> x.lastWhere((x) => x.isEven, orElse: () => null);
|
|
f() => lastEven([null]);
|
|
''';
|
|
var expected = '''
|
|
int? lastEven(Iterable<int?> x)
|
|
=> x.lastWhere((x) => x!.isEven, orElse: () => null);
|
|
f() => lastEven([null]);
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_late_final_hint_instance_field_without_constructor() async {
|
|
var content = '''
|
|
class C {
|
|
/*late final*/ int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
late final int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_late_final_hint_local_variable() async {
|
|
var content = '''
|
|
int f(bool b1, bool b2) {
|
|
/*late final*/ int x;
|
|
if (b1) {
|
|
x = 1;
|
|
}
|
|
if (b2) {
|
|
return x;
|
|
}
|
|
return 0;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int f(bool b1, bool b2) {
|
|
late final int x;
|
|
if (b1) {
|
|
x = 1;
|
|
}
|
|
if (b2) {
|
|
return x;
|
|
}
|
|
return 0;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_late_final_hint_top_level_var() async {
|
|
var content = '''
|
|
/*late final*/ int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
''';
|
|
var expected = '''
|
|
late final int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_late_hint_followed_by_underscore() async {
|
|
var content = '''
|
|
class _C {}
|
|
/*late*/ _C c;
|
|
''';
|
|
var expected = '''
|
|
class _C {}
|
|
late _C c;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_late_hint_instance_field_with_constructor() async {
|
|
var content = '''
|
|
class C {
|
|
C();
|
|
/*late*/ int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
C();
|
|
late int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_late_hint_instance_field_without_constructor() async {
|
|
var content = '''
|
|
class C {
|
|
/*late*/ int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
late int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_late_hint_local_variable() async {
|
|
var content = '''
|
|
int f(bool b1, bool b2) {
|
|
/*late*/ int x;
|
|
if (b1) {
|
|
x = 1;
|
|
}
|
|
if (b2) {
|
|
return x;
|
|
}
|
|
return 0;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int f(bool b1, bool b2) {
|
|
late int x;
|
|
if (b1) {
|
|
x = 1;
|
|
}
|
|
if (b2) {
|
|
return x;
|
|
}
|
|
return 0;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_late_hint_static_field() async {
|
|
var content = '''
|
|
class C {
|
|
static /*late*/ int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
static late int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_late_hint_top_level_var() async {
|
|
var content = '''
|
|
/*late*/ int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
''';
|
|
var expected = '''
|
|
late int x;
|
|
f() {
|
|
x = 1;
|
|
}
|
|
int g() => x;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_leave_downcast_from_dynamic_implicit() async {
|
|
var content = 'int f(dynamic n) => n;';
|
|
var expected = 'int f(dynamic n) => n;';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_libraryWithParts() async {
|
|
var root = '$projectPath/lib';
|
|
var path1 = convertPath('$root/lib.dart');
|
|
var file1 = '''
|
|
part 'src/foo/part.dart';
|
|
''';
|
|
var expected1 = '''
|
|
part 'src/foo/part.dart';
|
|
''';
|
|
var path2 = convertPath('$root/src/foo/part.dart');
|
|
var file2 = '''
|
|
part of '../../lib.dart';
|
|
class C {
|
|
static void m(C c) {}
|
|
}
|
|
''';
|
|
var expected2 = '''
|
|
part of '../../lib.dart';
|
|
class C {
|
|
static void m(C c) {}
|
|
}
|
|
''';
|
|
await _checkMultipleFileChanges(
|
|
{path2: file2, path1: file1}, {path1: expected1, path2: expected2});
|
|
}
|
|
|
|
Future<void> test_libraryWithParts_add_questions() async {
|
|
var root = '$projectPath/lib';
|
|
var path1 = convertPath('$root/lib.dart');
|
|
var file1 = '''
|
|
part 'src/foo/part.dart';
|
|
|
|
int f() => null;
|
|
''';
|
|
var expected1 = '''
|
|
part 'src/foo/part.dart';
|
|
|
|
int? f() => null;
|
|
''';
|
|
var path2 = convertPath('$root/src/foo/part.dart');
|
|
var file2 = '''
|
|
part of '../../lib.dart';
|
|
|
|
int g() => null;
|
|
''';
|
|
var expected2 = '''
|
|
part of '../../lib.dart';
|
|
|
|
int? g() => null;
|
|
''';
|
|
await _checkMultipleFileChanges(
|
|
{path2: file2, path1: file1}, {path1: expected1, path2: expected2});
|
|
}
|
|
|
|
Future<void> test_list_conditional_element() async {
|
|
var content = '''
|
|
void bar(List<String> l) {}
|
|
|
|
void test({String foo}) {
|
|
bar([if (foo != null) foo]);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void bar(List<String> l) {}
|
|
|
|
void test({String? foo}) {
|
|
bar([if (foo != null) foo]);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_literal_null_without_valid_migration() async {
|
|
var content = '''
|
|
void f(int/*!*/ x) {}
|
|
void g() {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(int x) {}
|
|
void g() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_literals_maintain_nullability() async {
|
|
// See #40590. Without exact nullability, this would migrate to
|
|
// `List<int?> list = <int>[1, 2]`. While the function of exact nullability
|
|
// may change, this case should continue to work.
|
|
var content = r'''
|
|
void f() {
|
|
List<int> list = [1, 2];
|
|
list.add(null);
|
|
Set<int> set_ = {1, 2};
|
|
set_.add(null);
|
|
Map<int, int> map = {1: 2};
|
|
map[null] = null;
|
|
}
|
|
''';
|
|
var expected = r'''
|
|
void f() {
|
|
List<int?> list = [1, 2];
|
|
list.add(null);
|
|
Set<int?> set_ = {1, 2};
|
|
set_.add(null);
|
|
Map<int?, int?> map = {1: 2};
|
|
map[null] = null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_loadLibrary_call() async {
|
|
var testPath = convertPath('$testsPath/lib/test.dart');
|
|
var otherPath = convertPath('$testsPath/lib/other.dart');
|
|
var content = {
|
|
testPath: '''
|
|
import 'other.dart' deferred as other;
|
|
Future<Object> f() => other.loadLibrary();
|
|
''',
|
|
otherPath: ''
|
|
};
|
|
var expected = {
|
|
testPath: '''
|
|
import 'other.dart' deferred as other;
|
|
Future<Object?> f() => other.loadLibrary();
|
|
''',
|
|
otherPath: ''
|
|
};
|
|
await _checkMultipleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_loadLibrary_tearOff() async {
|
|
var testPath = convertPath('$testsPath/lib/test.dart');
|
|
var otherPath = convertPath('$testsPath/lib/other.dart');
|
|
var content = {
|
|
testPath: '''
|
|
import 'other.dart' deferred as other;
|
|
Future<Object> Function() f() => other.loadLibrary;
|
|
''',
|
|
otherPath: ''
|
|
};
|
|
var expected = {
|
|
testPath: '''
|
|
import 'other.dart' deferred as other;
|
|
Future<Object?> Function() f() => other.loadLibrary;
|
|
''',
|
|
otherPath: ''
|
|
};
|
|
await _checkMultipleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_local_function() async {
|
|
var content = '''
|
|
int f(int i) {
|
|
int g(int j) => i;
|
|
return g(i);
|
|
}
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int? f(int? i) {
|
|
int? g(int? j) => i;
|
|
return g(i);
|
|
}
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_local_function_doesnt_assign() async {
|
|
var content = '''
|
|
int f() {
|
|
int i;
|
|
g(int j) {
|
|
i = 1;
|
|
};
|
|
((int j) {
|
|
i = 1;
|
|
});
|
|
return i + 1;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int f() {
|
|
late int i;
|
|
g(int j) {
|
|
i = 1;
|
|
};
|
|
((int j) {
|
|
i = 1;
|
|
});
|
|
return i + 1;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_local_function_expression_inhibits_non_null_intent() async {
|
|
var content = '''
|
|
void call(void Function() callback) {
|
|
callback();
|
|
}
|
|
test(int i, int j) {
|
|
call(() {
|
|
i = j;
|
|
});
|
|
print(i + 1);
|
|
}
|
|
main() {
|
|
test(null, 0);
|
|
}
|
|
''';
|
|
// `print(i + 1)` does *not* demonstrate non-null intent for `i` because it
|
|
// is write captured by the local function expression, so it's not
|
|
// guaranteed that a null value of `i` on entry to the function will lead to
|
|
// an exception.
|
|
var expected = '''
|
|
void call(void Function() callback) {
|
|
callback();
|
|
}
|
|
test(int? i, int j) {
|
|
call(() {
|
|
i = j;
|
|
});
|
|
print(i! + 1);
|
|
}
|
|
main() {
|
|
test(null, 0);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_local_function_inhibits_non_null_intent() async {
|
|
var content = '''
|
|
void call(void Function() callback) {
|
|
callback();
|
|
}
|
|
test(int i, int j) {
|
|
void f() {
|
|
i = j;
|
|
}
|
|
call(f);
|
|
print(i + 1);
|
|
}
|
|
main() {
|
|
test(null, 0);
|
|
}
|
|
''';
|
|
// `print(i + 1)` does *not* demonstrate non-null intent for `i` because it
|
|
// is write captured by the local function expression, so it's not
|
|
// guaranteed that a null value of `i` on entry to the function will lead to
|
|
// an exception.
|
|
var expected = '''
|
|
void call(void Function() callback) {
|
|
callback();
|
|
}
|
|
test(int? i, int j) {
|
|
void f() {
|
|
i = j;
|
|
}
|
|
call(f);
|
|
print(i! + 1);
|
|
}
|
|
main() {
|
|
test(null, 0);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_local_function_return() async {
|
|
var content = '''
|
|
void test({String foo}) async {
|
|
String f() {
|
|
return "hello";
|
|
}
|
|
|
|
foo.length;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void test({required String foo}) async {
|
|
String f() {
|
|
return "hello";
|
|
}
|
|
|
|
foo.length;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_localVariable_type_inferred() async {
|
|
var content = '''
|
|
int f() => null;
|
|
void main() {
|
|
var x = 1;
|
|
x = f();
|
|
}
|
|
''';
|
|
// The type of x is inferred as non-nullable from its initializer, but we
|
|
// try to assign a nullable value to it. So an explicit type must be added.
|
|
var expected = '''
|
|
int? f() => null;
|
|
void main() {
|
|
int? x = 1;
|
|
x = f();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_localVariable_uninitialized_assigned_non_nullable() async {
|
|
var content = '''
|
|
f() {
|
|
String s;
|
|
if (1 == 2) s = g();
|
|
h(s);
|
|
}
|
|
String /*!*/ g() => "Hello";
|
|
h(String /*!*/ s) {}
|
|
''';
|
|
var expected = '''
|
|
f() {
|
|
late String s;
|
|
if (1 == 2) s = g();
|
|
h(s);
|
|
}
|
|
String g() => "Hello";
|
|
h(String s) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_localVariable_uninitialized_used() async {
|
|
var content = '''
|
|
f() {
|
|
String s;
|
|
if (1 == 2) s = "Hello";
|
|
g(s);
|
|
}
|
|
g(String /*!*/ s) {}
|
|
''';
|
|
var expected = '''
|
|
f() {
|
|
late String s;
|
|
if (1 == 2) s = "Hello";
|
|
g(s);
|
|
}
|
|
g(String s) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_localVariable_uninitialized_usedInComparison() async {
|
|
var content = '''
|
|
f() {
|
|
String s;
|
|
if (s == null) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
f() {
|
|
String? s;
|
|
if (s == null) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_localVariable_uninitialized_usedInExpressionStatement() async {
|
|
var content = '''
|
|
f() {
|
|
String s;
|
|
s;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
f() {
|
|
String? s;
|
|
s;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_localVariable_uninitialized_usedInForUpdaters() async {
|
|
var content = '''
|
|
f() {
|
|
String s;
|
|
for (s;;) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
f() {
|
|
String? s;
|
|
for (s;;) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_localVariable_uninitialized_usedInForVariable() async {
|
|
var content = '''
|
|
f() {
|
|
String s;
|
|
for (;; s) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
f() {
|
|
String? s;
|
|
for (;; s) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_loop_var_is_field() async {
|
|
var content = '''
|
|
class C {
|
|
int x;
|
|
C(this.x);
|
|
f(List<int/*?*/> y) {
|
|
for (x in y) {}
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int? x;
|
|
C(this.x);
|
|
f(List<int?> y) {
|
|
for (x in y) {}
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_loop_var_is_inherited_field_with_substitution() async {
|
|
var content = '''
|
|
class B<T> {
|
|
T x;
|
|
B(this.x);
|
|
}
|
|
abstract class C implements B<int> {
|
|
f(List<int/*?*/> y) {
|
|
for (x in y) {}
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B<T> {
|
|
T x;
|
|
B(this.x);
|
|
}
|
|
abstract class C implements B<int?> {
|
|
f(List<int?> y) {
|
|
for (x in y) {}
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_make_downcast_explicit() async {
|
|
var content = 'int f(num n) => n;';
|
|
var expected = 'int f(num n) => n as int;';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_many_type_variables() async {
|
|
try {
|
|
assert(false);
|
|
} catch (_) {
|
|
// When assertions are enabled, this test fails, so skip it.
|
|
// See https://github.com/dart-lang/sdk/issues/43945.
|
|
return;
|
|
}
|
|
var content = '''
|
|
void test(C<int> x, double Function<S>(C<S>) y) {
|
|
x.f<double>(y);
|
|
}
|
|
class C<T> {
|
|
U f<U>(U Function<V>(C<V>) z) => throw 'foo';
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void test(C<int> x, double Function<S>(C<S>) y) {
|
|
x.f<double>(y);
|
|
}
|
|
class C<T> {
|
|
U f<U>(U Function<V>(C<V>) z) => throw 'foo';
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected, warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_map_nullable_input() async {
|
|
var content = '''
|
|
Iterable<int> f(List<int> x) => x.map((y) => g(y));
|
|
int g(int x) => x + 1;
|
|
main() {
|
|
f([null]);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
Iterable<int> f(List<int?> x) => x.map((y) => g(y!));
|
|
int g(int x) => x + 1;
|
|
main() {
|
|
f([null]);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_map_nullable_input_tearoff() async {
|
|
var content = '''
|
|
Iterable<int> f(List<int> x) => x.map(g);
|
|
int g(int x) => x + 1;
|
|
main() {
|
|
f([null]);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
Iterable<int> f(List<int?> x) => x.map(g);
|
|
int g(int? x) => x! + 1;
|
|
main() {
|
|
f([null]);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_map_nullable_output() async {
|
|
var content = '''
|
|
Iterable<int> f(List<int> x) => x.map((y) => g(y));
|
|
int g(int x) => null;
|
|
main() {
|
|
f([1]);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
Iterable<int?> f(List<int> x) => x.map((y) => g(y));
|
|
int? g(int x) => null;
|
|
main() {
|
|
f([1]);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_map_read_does_not_require_index_cast() async {
|
|
var content = '''
|
|
int f(Map<String, int> m, Object o) => m[o];
|
|
''';
|
|
var expected = '''
|
|
int? f(Map<String, int> m, Object o) => m[o];
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_map_write_requires_index_cast() async {
|
|
var content = '''
|
|
void f(Map<String, int> m, Object o, int i) => m[o] = i;
|
|
''';
|
|
var expected = '''
|
|
void f(Map<String, int> m, Object o, int i) => m[o as String] = i;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_methodInvocation_extension_invocation() async {
|
|
var content = '''
|
|
extension on bool {
|
|
void f() {}
|
|
}
|
|
bool g<T>(T x) => true;
|
|
void main() {
|
|
g<int>(null).f();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
extension on bool {
|
|
void f() {}
|
|
}
|
|
bool g<T>(T x) => true;
|
|
void main() {
|
|
g<int?>(null).f();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected, warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_methodInvocation_typeArguments_explicit() async {
|
|
var content = '''
|
|
T f<T>(T t) => t;
|
|
void g() {
|
|
int x = f<int>(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
T f<T>(T t) => t;
|
|
void g() {
|
|
int? x = f<int?>(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_methodInvocation_typeArguments_explicit_nonNullable() async {
|
|
var content = '''
|
|
T f<T extends Object/*!*/>(T/*!*/ t) => t;
|
|
void g(int/*?*/ n) {
|
|
int x = f<int>(n);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
T f<T extends Object>(T t) => t;
|
|
void g(int? n) {
|
|
int x = f<int>(n!);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_methodInvocation_typeArguments_explicit_nonNullableParam() async {
|
|
var content = '''
|
|
T f<T>(T/*!*/ t) => t;
|
|
void g() {
|
|
int x = f<int>(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
T f<T>(T t) => t;
|
|
void g() {
|
|
int? x = f<int?>(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_methodInvocation_typeArguments_inferred() async {
|
|
var content = '''
|
|
T f<T>(T t) => t;
|
|
void g() {
|
|
int x = f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
T f<T>(T t) => t;
|
|
void g() {
|
|
int? x = f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_methodInvocation_typeArguments_inferred_nonNullable() async {
|
|
var content = '''
|
|
T f<T extends Object/*!*/>(T/*!*/ t) => t;
|
|
void g(int/*?*/ n) {
|
|
int x = f(n);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
T f<T extends Object>(T t) => t;
|
|
void g(int? n) {
|
|
int x = f(n!);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_methodInvocation_typeArguments_inferred_nonNullableParam() async {
|
|
var content = '''
|
|
T f<T>(T/*!*/ t) => t;
|
|
void g() {
|
|
int x = f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
T f<T>(T t) => t;
|
|
void g() {
|
|
int? x = f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_migrate_reference_to_never() async {
|
|
var content = '''
|
|
import 'dart:io';
|
|
int f() =>
|
|
exit(1); // this returns `Never` which used to cause a crash.
|
|
''';
|
|
var expected = '''
|
|
import 'dart:io';
|
|
int f() =>
|
|
exit(1); // this returns `Never` which used to cause a crash.
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_migratedMethod_namedParameter() async {
|
|
var content = '''
|
|
void f(Iterable<int> a) {
|
|
a.toList(growable: false);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(Iterable<int> a) {
|
|
a.toList(growable: false);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_multiDeclaration_innerUsage() async {
|
|
var content = '''
|
|
void test() {
|
|
// here non-null is OK.
|
|
int i1 = 0, i2 = i1.gcd(2);
|
|
// here non-null is not OK.
|
|
int i3 = 0, i4 = i3.gcd(2), i5 = null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void test() {
|
|
// here non-null is OK.
|
|
int i1 = 0, i2 = i1.gcd(2);
|
|
// here non-null is not OK.
|
|
int? i3 = 0, i4 = i3.gcd(2), i5 = null;
|
|
}
|
|
''';
|
|
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_multiDeclaration_softEdges() async {
|
|
var content = '''
|
|
int nullable(int i1, int i2) {
|
|
int i3 = i1, i4 = i2;
|
|
return i3;
|
|
}
|
|
int nonNull(int i1, int i2) {
|
|
int i3 = i1, i4 = i2;
|
|
return i3;
|
|
}
|
|
int both(int i1, int i2) {
|
|
int i3 = i1, i4 = i2;
|
|
return i3;
|
|
}
|
|
void main() {
|
|
nullable(null, null);
|
|
nonNull(0, 1);
|
|
both(0, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int? nullable(int? i1, int? i2) {
|
|
int? i3 = i1, i4 = i2;
|
|
return i3;
|
|
}
|
|
int nonNull(int i1, int i2) {
|
|
int i3 = i1, i4 = i2;
|
|
return i3;
|
|
}
|
|
int? both(int i1, int? i2) {
|
|
int? i3 = i1, i4 = i2;
|
|
return i3;
|
|
}
|
|
void main() {
|
|
nullable(null, null);
|
|
nonNull(0, 1);
|
|
both(0, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_named_parameter_add_required() async {
|
|
var content = '''
|
|
void f({String s}) {
|
|
assert(s != null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f({required String s}) {
|
|
assert(s != null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_named_parameter_add_required_function_typed() async {
|
|
var content = '''
|
|
void f({void g(int i)}) {
|
|
assert(g != null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f({required void g(int i)}) {
|
|
assert(g != null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_named_parameter_no_default_unused() async {
|
|
var content = '''
|
|
void f({String s}) {}
|
|
main() {
|
|
f();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f({String? s}) {}
|
|
main() {
|
|
f();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_named_parameter_no_default_unused_propagate() async {
|
|
var content = '''
|
|
void f(String s) {}
|
|
void g({String s}) {
|
|
f(s);
|
|
}
|
|
main() {
|
|
g();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(String? s) {}
|
|
void g({String? s}) {
|
|
f(s);
|
|
}
|
|
main() {
|
|
g();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_named_parameter_no_default_unused_required() async {
|
|
// The `@required` annotation overrides the assumption of nullability.
|
|
// The call at `f()` is presumed to be in error.
|
|
addMetaPackage();
|
|
var content = '''
|
|
import 'package:meta/meta.dart';
|
|
void f({@required String s}) {}
|
|
main() {
|
|
f();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'package:meta/meta.dart';
|
|
void f({required String s}) {}
|
|
main() {
|
|
f();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_named_parameter_no_default_used_non_null() async {
|
|
var content = '''
|
|
void f({String s}) {}
|
|
main() {
|
|
f(s: 'x');
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f({String? s}) {}
|
|
main() {
|
|
f(s: 'x');
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_named_parameter_no_default_used_non_null_propagate() async {
|
|
var content = '''
|
|
void f(String s) {}
|
|
void g({String s}) {
|
|
f(s);
|
|
}
|
|
main() {
|
|
g(s: 'x');
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(String? s) {}
|
|
void g({String? s}) {
|
|
f(s);
|
|
}
|
|
main() {
|
|
g(s: 'x');
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_named_parameter_no_default_used_null_option2() async {
|
|
var content = '''
|
|
void f({String s}) {}
|
|
main() {
|
|
f(s: null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f({String? s}) {}
|
|
main() {
|
|
f(s: null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_named_parameter_no_default_used_null_required() async {
|
|
// Explicitly passing `null` forces the parameter to be nullable even though
|
|
// it is required.
|
|
addMetaPackage();
|
|
var content = '''
|
|
import 'package:meta/meta.dart';
|
|
void f({@required String s}) {}
|
|
main() {
|
|
f(s: null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'package:meta/meta.dart';
|
|
void f({required String? s}) {}
|
|
main() {
|
|
f(s: null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_named_parameter_with_non_null_default_unused_option2() async {
|
|
var content = '''
|
|
void f({String s: 'foo'}) {}
|
|
main() {
|
|
f();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f({String s: 'foo'}) {}
|
|
main() {
|
|
f();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_named_parameter_with_non_null_default_used_non_null_option2() async {
|
|
var content = '''
|
|
void f({String s: 'foo'}) {}
|
|
main() {
|
|
f(s: 'bar');
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f({String s: 'foo'}) {}
|
|
main() {
|
|
f(s: 'bar');
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_named_parameter_with_non_null_default_used_null_option2() async {
|
|
var content = '''
|
|
void f({String s: 'foo'}) {}
|
|
main() {
|
|
f(s: null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f({String? s: 'foo'}) {}
|
|
main() {
|
|
f(s: null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_named_parameter_with_null_default_unused_option2() async {
|
|
var content = '''
|
|
void f({String s: null}) {}
|
|
main() {
|
|
f();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f({String? s: null}) {}
|
|
main() {
|
|
f();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_non_null_assertion() async {
|
|
var content = '''
|
|
int f(int i, [int j]) {
|
|
if (i == 0) return i;
|
|
return i + j;
|
|
}
|
|
''';
|
|
|
|
var expected = '''
|
|
int f(int i, [int? j]) {
|
|
if (i == 0) return i;
|
|
return i + j!;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_non_null_intent_field_formal_assert() async {
|
|
var content = '''
|
|
class C {
|
|
int i;
|
|
C(this.i) {
|
|
assert(i != null);
|
|
}
|
|
}
|
|
f(int j, bool b) {
|
|
if (b) {
|
|
C(j);
|
|
}
|
|
}
|
|
g() {
|
|
f(null, false);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i;
|
|
C(this.i) {
|
|
assert(i != null);
|
|
}
|
|
}
|
|
f(int? j, bool b) {
|
|
if (b) {
|
|
C(j!);
|
|
}
|
|
}
|
|
g() {
|
|
f(null, false);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_non_null_intent_field_formal_use() async {
|
|
var content = '''
|
|
class C {
|
|
int i;
|
|
C(this.i) {
|
|
f(i);
|
|
}
|
|
}
|
|
f(int j) {
|
|
assert(j != null);
|
|
}
|
|
g(int k, bool b) {
|
|
if (b) {
|
|
C(k);
|
|
}
|
|
}
|
|
h() {
|
|
g(null, false);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i;
|
|
C(this.i) {
|
|
f(i);
|
|
}
|
|
}
|
|
f(int j) {
|
|
assert(j != null);
|
|
}
|
|
g(int? k, bool b) {
|
|
if (b) {
|
|
C(k!);
|
|
}
|
|
}
|
|
h() {
|
|
g(null, false);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_non_null_intent_propagated_through_substitution_nodes() async {
|
|
var content = '''
|
|
abstract class C {
|
|
void f(List<int/*!*/> x, int y) {
|
|
x.add(y);
|
|
}
|
|
int/*?*/ g();
|
|
void test() {
|
|
f(<int>[], g());
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
abstract class C {
|
|
void f(List<int> x, int y) {
|
|
x.add(y);
|
|
}
|
|
int? g();
|
|
void test() {
|
|
f(<int>[], g()!);
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_non_nullable_hint_comment_overrides_uncheckable_edge() async {
|
|
var content = '''
|
|
Iterable<int> f(List<int> x) => x.map(g);
|
|
int g(int/*!*/ x) => x + 1;
|
|
main() {
|
|
f([null]);
|
|
}
|
|
''';
|
|
// TODO(paulberry): we should do something to flag the fact that g can't be
|
|
// safely passed to f.
|
|
var expected = '''
|
|
Iterable<int> f(List<int?> x) => x.map(g as int Function(int?));
|
|
int g(int x) => x + 1;
|
|
main() {
|
|
f([null]);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_not_definitely_assigned_value() async {
|
|
var content = '''
|
|
String f(bool b) {
|
|
String s;
|
|
if (b) {
|
|
s = 'true';
|
|
}
|
|
return s;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
String? f(bool b) {
|
|
String? s;
|
|
if (b) {
|
|
s = 'true';
|
|
}
|
|
return s;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_followed_by_if_null() async {
|
|
var content = '''
|
|
typedef MapGetter = Map<String, String> Function();
|
|
void f(Map<String, String> m) {}
|
|
void g(MapGetter/*?*/ mapGetter) {
|
|
f(mapGetter?.call() ?? {});
|
|
}
|
|
''';
|
|
var expected = '''
|
|
typedef MapGetter = Map<String, String> Function();
|
|
void f(Map<String, String> m) {}
|
|
void g(MapGetter? mapGetter) {
|
|
f(mapGetter?.call() ?? {});
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_closure_param_not_nullable() async {
|
|
// The null-aware access on `i` is *not* considered a strong enough signal
|
|
// that `i` is meant to be nullable, because the migration tool can see all
|
|
// callers of the closure, so it can tell whether it needs to be nullable or
|
|
// not.
|
|
//
|
|
// (Note: this is not strictly true, because the closure could be called
|
|
// from elsewhere. But it's a heuristic that seems to be usually right in
|
|
// the cases we've found so far.)
|
|
var content = '''
|
|
main() {
|
|
var x = (int i) => i?.abs();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
main() {
|
|
var x = (int i) => i.abs();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_local_param_not_nullable() async {
|
|
// The null-aware access on `i` is *not* considered a strong enough signal
|
|
// that `i` is meant to be nullable, because the migration tool can see all
|
|
// callers of `f`, so it can tell whether it needs to be nullable or not.
|
|
//
|
|
// (Note: this is not strictly true, because the local function could be
|
|
// torn off and called from elsewhere. But it's a heuristic that seems to
|
|
// be usually right in the cases we've found so far.)
|
|
var content = '''
|
|
main() {
|
|
int f(int i) => i?.abs();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
main() {
|
|
int f(int i) => i.abs();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_migrated_get() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D get g;
|
|
}
|
|
abstract class D {
|
|
int f2();
|
|
}
|
|
''';
|
|
// Since `.g` is a non-nullable getter in an already-migrated class, the
|
|
// `?.` can safely be replaced with `.`. We can safely make this change
|
|
// even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.g?.f2();
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.g.f2();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_migrated_get_null_shorting() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D get g;
|
|
}
|
|
abstract class D {
|
|
int f2();
|
|
}
|
|
''';
|
|
// Since `.g` is a non-nullable getter in an already-migrated class, the
|
|
// `?.` can safely be replaced with `.`. We can safely make this change
|
|
// even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C/*?*/ c) => c?.g?.f2();
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C? c) => c?.g.f2();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_migrated_method() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D f();
|
|
}
|
|
abstract class D {
|
|
int f2();
|
|
}
|
|
''';
|
|
// Since `.f()` is a method with a non-nullable return type in an
|
|
// already-migrated class, the `?.` can safely be replaced with `.`. We can
|
|
// safely make this change even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.f()?.f2();
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.f().f2();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_migrated_method_null_shorting() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D f();
|
|
}
|
|
abstract class D {
|
|
int f2();
|
|
}
|
|
''';
|
|
// Since `.f()` is a method with a non-nullable return type in an
|
|
// already-migrated class, the `?.` can safely be replaced with `.`. We can
|
|
// safely make this change even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C/*?*/ c) => c?.f()?.f2();
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C? c) => c?.f().f2();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_nullable_get() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D? get g;
|
|
}
|
|
abstract class D {
|
|
int f2();
|
|
}
|
|
''';
|
|
// Since `.g` is a nullable getter in an already-migrated class, we don't
|
|
// replace `?.` with `.`.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.g?.f2();
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.g?.f2();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput});
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_nullable_method() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D? f();
|
|
}
|
|
abstract class D {
|
|
int f2();
|
|
}
|
|
''';
|
|
// Since `.f()` is a method with a nullable return type in an
|
|
// already-migrated class, we don't replace `?.` with `.`.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.f()?.f2();
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.f()?.f2();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput});
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_private_param_not_nullable() async {
|
|
// The null-aware access on `i` is *not* considered a strong enough signal
|
|
// that `i` is meant to be nullable, because the migration tool can see all
|
|
// callers of `_f`, so it can tell whether it needs to be nullable or not.
|
|
var content = 'int _f(int i) => i?.abs();';
|
|
var expected = 'int _f(int i) => i.abs();';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_public_param_implies_nullable() async {
|
|
// The null-aware access on `i` is considered a strong signal that `i` is
|
|
// meant to be nullable.
|
|
var content = 'int f(int i) => i?.abs();';
|
|
var expected = 'int? f(int? i) => i?.abs();';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_null_aware_call_on_public_param_overridable_by_hint() async {
|
|
// The null-aware access on `i` is considered a strong signal that `i` is
|
|
// meant to be nullable, but an explicit `/*!*/` is a stronger signal.
|
|
var content = 'int f(int/*!*/ i) => i?.abs();';
|
|
var expected = 'int f(int i) => i.abs();';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_null_aware_call_on_public_param_overridable_by_intent() async {
|
|
// The null-aware access on `i` is considered a strong signal that `i` is
|
|
// meant to be nullable, but non-null intent is a stronger signal.
|
|
var content = '''
|
|
int f(int i) {
|
|
print(i + 1);
|
|
return i?.abs();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int f(int i) {
|
|
print(i + 1);
|
|
return i.abs();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_unmigrated_get() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class D {
|
|
int f2();
|
|
}
|
|
''';
|
|
// Since `.g` is unmigrated, we don't replace `?.` with `.` in "warn on weak
|
|
// code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
abstract class C {
|
|
D get g;
|
|
}
|
|
f(C c) => c.g?.f2();
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
abstract class C {
|
|
D get g;
|
|
}
|
|
f(C c) => c.g?.f2();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_on_unmigrated_method() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class D {
|
|
int f2();
|
|
}
|
|
''';
|
|
// Since `.f()` is unmigrated, we don't replace `?.` with `.` in "warn on
|
|
// weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
abstract class C {
|
|
D f();
|
|
}
|
|
f(C c) => c.f()?.f2();
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
abstract class C {
|
|
D f();
|
|
}
|
|
f(C c) => c.f()?.f2();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_call_tearoff() async {
|
|
// Kind of a weird use case because `f?.call` is equivalent to `f`, but
|
|
// let's make sure we analyze it correctly.
|
|
var content =
|
|
'int Function(int) g(int/*?*/ Function(int)/*?*/ f) => f?.call;';
|
|
var expected = 'int? Function(int)? g(int? Function(int)? f) => f?.call;';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_get_on_closure_param_not_nullable() async {
|
|
// The null-aware access on `i` is *not* considered a strong enough signal
|
|
// that `i` is meant to be nullable, because the migration tool can see all
|
|
// callers of the closure, so it can tell whether it needs to be nullable or
|
|
// not.
|
|
//
|
|
// (Note: this is not strictly true, because the closure could be called
|
|
// from elsewhere. But it's a heuristic that seems to be usually right in
|
|
// the cases we've found so far.)
|
|
var content = '''
|
|
main() {
|
|
var x = (int i) => i?.isEven;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
main() {
|
|
var x = (int i) => i.isEven;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_get_on_local_param_not_nullable() async {
|
|
// The null-aware access on `i` is *not* considered a strong enough signal
|
|
// that `i` is meant to be nullable, because the migration tool can see all
|
|
// callers of `f`, so it can tell whether it needs to be nullable or not.
|
|
//
|
|
// (Note: this is not strictly true, because the local function could be
|
|
// torn off and called from elsewhere. But it's a heuristic that seems to
|
|
// be usually right in the cases we've found so far.)
|
|
var content = '''
|
|
main() {
|
|
bool f(int i) => i?.isEven;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
main() {
|
|
bool f(int i) => i.isEven;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_get_on_migrated_get() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D get g;
|
|
}
|
|
abstract class D {
|
|
int get g2;
|
|
}
|
|
''';
|
|
// Since `.g` is a non-nullable getter in an already-migrated class, the
|
|
// `?.` can safely be replaced with `.`. We can safely make this change
|
|
// even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.g?.g2;
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.g.g2;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_get_on_migrated_get_null_shorting() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D get g;
|
|
}
|
|
abstract class D {
|
|
int get g2;
|
|
}
|
|
''';
|
|
// Since `.g` is a non-nullable getter in an already-migrated class, the
|
|
// `?.` can safely be replaced with `.`. We can safely make this change
|
|
// even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C/*?*/ c) => c?.g?.g2;
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C? c) => c?.g.g2;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_get_on_migrated_method() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D f();
|
|
}
|
|
abstract class D {
|
|
int get g2;
|
|
}
|
|
''';
|
|
// Since `.f()` is a method with a non-nullable return type in an
|
|
// already-migrated class, the `?.` can safely be replaced with `.`. We can
|
|
// safely make this change even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.f()?.g2;
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.f().g2;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_get_on_migrated_method_null_shorting() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D f();
|
|
}
|
|
abstract class D {
|
|
int get g2;
|
|
}
|
|
''';
|
|
// Since `.f()` is a method with a non-nullable return type in an
|
|
// already-migrated class, the `?.` can safely be replaced with `.`. We can
|
|
// safely make this change even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C/*?*/ c) => c?.f()?.g2;
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C? c) => c?.f().g2;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_get_on_private_param_not_nullable() async {
|
|
// The null-aware access on `i` is *not* considered a strong enough signal
|
|
// that `i` is meant to be nullable, because the migration tool can see all
|
|
// callers of `_f`, so it can tell whether it needs to be nullable or not.
|
|
var content = 'bool _f(int i) => i?.isEven;';
|
|
var expected = 'bool _f(int i) => i.isEven;';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_get_on_public_param_implies_nullable() async {
|
|
// The null-aware access on `i` is considered a strong signal that `i` is
|
|
// meant to be nullable.
|
|
var content = 'bool f(int i) => i?.isEven;';
|
|
var expected = 'bool? f(int? i) => i?.isEven;';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_get_on_public_param_overridable_by_hint() async {
|
|
// The null-aware access on `i` is considered a strong signal that `i` is
|
|
// meant to be nullable, but an explicit `/*!*/` is a stronger signal.
|
|
var content = 'bool f(int/*!*/ i) => i?.isEven;';
|
|
var expected = 'bool f(int i) => i.isEven;';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_null_aware_get_on_public_param_overridable_by_intent() async {
|
|
// The null-aware access on `i` is considered a strong signal that `i` is
|
|
// meant to be nullable, but non-null intent is a stronger signal.
|
|
var content = '''
|
|
bool f(int i) {
|
|
print(i + 1);
|
|
return i?.isEven;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
bool f(int i) {
|
|
print(i + 1);
|
|
return i.isEven;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_getter_invocation() async {
|
|
var content = '''
|
|
bool f(int i) => i?.isEven;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
bool? f(int? i) => i?.isEven;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_method_invocation() async {
|
|
var content = '''
|
|
int f(int i) => i?.abs();
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int? f(int? i) => i?.abs();
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_set_on_closure_param_not_nullable() async {
|
|
// The null-aware access on `c` is *not* considered a strong enough signal
|
|
// that `c` is meant to be nullable, because the migration tool can see all
|
|
// callers of the closure, so it can tell whether it needs to be nullable or
|
|
// not.
|
|
//
|
|
// (Note: this is not strictly true, because the closure could be called
|
|
// from elsewhere. But it's a heuristic that seems to be usually right in
|
|
// the cases we've found so far.)
|
|
var content = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
main() {
|
|
var x = (C c) { c?.i = 0; };
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
main() {
|
|
var x = (C c) { c.i = 0; };
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_set_on_local_param_not_nullable() async {
|
|
// The null-aware access on `c` is *not* considered a strong enough signal
|
|
// that `c` is meant to be nullable, because the migration tool can see all
|
|
// callers of `f`, so it can tell whether it needs to be nullable or not.
|
|
//
|
|
// (Note: this is not strictly true, because the local function could be
|
|
// torn off and called from elsewhere. But it's a heuristic that seems to
|
|
// be usually right in the cases we've found so far.)
|
|
var content = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
main() {
|
|
void f(C c) { c?.i = 0; }
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
main() {
|
|
void f(C c) { c.i = 0; }
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_set_on_migrated_get() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D get g;
|
|
}
|
|
abstract class D {
|
|
set s(int i);
|
|
}
|
|
''';
|
|
// Since `.g` is a non-nullable getter in an already-migrated class, the
|
|
// `?.` can safely be replaced with `.`. We can safely make this change
|
|
// even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.g?.s = 0;
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.g.s = 0;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_set_on_migrated_get_null_shorting() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D get g;
|
|
}
|
|
abstract class D {
|
|
set s(int i);
|
|
}
|
|
''';
|
|
// Since `.g` is a non-nullable getter in an already-migrated class, the
|
|
// `?.` can safely be replaced with `.`. We can safely make this change
|
|
// even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C/*?*/ c) => c?.g?.s = 0;
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C? c) => c?.g.s = 0;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_set_on_migrated_method() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D f();
|
|
}
|
|
abstract class D {
|
|
set s(int i);
|
|
}
|
|
''';
|
|
// Since `.f()` is a method with a non-nullable return type in an
|
|
// already-migrated class, the `?.` can safely be replaced with `.`. We can
|
|
// safely make this change even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.f()?.s = 0;
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C c) => c.f().s = 0;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_set_on_migrated_method_null_shorting() async {
|
|
var migratedInput = '''
|
|
// @dart=2.12
|
|
abstract class C {
|
|
D f();
|
|
}
|
|
abstract class D {
|
|
set s(int i);
|
|
}
|
|
''';
|
|
// Since `.f()` is a method with a non-nullable return type in an
|
|
// already-migrated class, the `?.` can safely be replaced with `.`. We can
|
|
// safely make this change even if we are in "warn on weak code" mode.
|
|
var content = '''
|
|
import 'migrated.dart';
|
|
f(C/*?*/ c) => c?.f()?.s = 0;
|
|
''';
|
|
var expected = '''
|
|
import 'migrated.dart';
|
|
f(C? c) => c?.f().s = 0;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected,
|
|
migratedInput: {'$projectPath/lib/migrated.dart': migratedInput},
|
|
warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_null_aware_set_on_private_param_not_nullable() async {
|
|
// The null-aware access on `c` is *not* considered a strong enough signal
|
|
// that `c` is meant to be nullable, because the migration tool can see all
|
|
// callers of `_f`, so it can tell whether it needs to be nullable or not.
|
|
var content = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
void _f(C c) { c?.i = 0; }
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
void _f(C c) { c.i = 0; }
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_set_on_public_param_implies_nullable() async {
|
|
// The null-aware access on `c` is considered a strong signal that `c` is
|
|
// meant to be nullable.
|
|
var content = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
void f(C c) { c?.i = 0; }
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
void f(C? c) { c?.i = 0; }
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_set_on_public_param_overridable_by_hint() async {
|
|
// The null-aware access on `c` is considered a strong signal that `c` is
|
|
// meant to be nullable, but an explicit `/*!*/` is a stronger signal.
|
|
var content = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
void f(C/*!*/ c) { c?.i = 0; }
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
void f(C c) { c.i = 0; }
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_null_aware_set_on_public_param_overridable_by_intent() async {
|
|
// The null-aware access on `c` is considered a strong signal that `c` is
|
|
// meant to be nullable, but non-null intent is a stronger signal.
|
|
var content = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
void f(C c) {
|
|
print(c.i);
|
|
c?.i = 0;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i = 0;
|
|
}
|
|
void f(C c) {
|
|
print(c.i);
|
|
c.i = 0;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_setter_invocation_null_target() async {
|
|
var content = '''
|
|
class C {
|
|
void set x(int value) {}
|
|
}
|
|
int f(C c) => c?.x = 1;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
void set x(int value) {}
|
|
}
|
|
int? f(C? c) => c?.x = 1;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_aware_setter_invocation_null_value() async {
|
|
var content = '''
|
|
class C {
|
|
void set x(int value) {}
|
|
}
|
|
int f(C c) => c?.x = 1;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
void set x(int value) {}
|
|
}
|
|
int? f(C? c) => c?.x = 1;
|
|
main() {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_check_in_cascade_target() async {
|
|
var content = '''
|
|
class _C {
|
|
f() {}
|
|
}
|
|
_C g(int/*!*/ i) => _C();
|
|
test(int/*?*/ j) {
|
|
g(j)..f();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class _C {
|
|
f() {}
|
|
}
|
|
_C g(int i) => _C();
|
|
test(int? j) {
|
|
g(j!)..f();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_check_type_parameter_type_with_nullable_bound() async {
|
|
var content = '''
|
|
abstract class C<E, T extends Iterable<E>/*?*/> {
|
|
void f(T iter) {
|
|
for(var i in iter) {}
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
abstract class C<E, T extends Iterable<E>?> {
|
|
void f(T iter) {
|
|
for(var i in iter!) {}
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_in_conditional_expression() async {
|
|
var content = '''
|
|
void f() {
|
|
List<int> x = false ? [] : null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f() {
|
|
List<int>? x = false ? [] : null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_null_typed_expression_wiithout_valid_migration() async {
|
|
var content = '''
|
|
void f(int/*!*/ x) {}
|
|
void g() {
|
|
f(h());
|
|
}
|
|
Null h() => null;
|
|
''';
|
|
var expected = '''
|
|
void f(int x) {}
|
|
void g() {
|
|
f(h());
|
|
}
|
|
Null h() => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_nullable_use_of_typedef() async {
|
|
var content = '''
|
|
typedef F<T> = int Function(T);
|
|
F<String> f = null;
|
|
void main() {
|
|
f('foo');
|
|
}
|
|
''';
|
|
var expected = '''
|
|
typedef F<T> = int Function(T);
|
|
F<String>? f = null;
|
|
void main() {
|
|
f!('foo');
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_nullTestOnGenericType_implicitBound() async {
|
|
var content = '''
|
|
void f<T>(T x, T y) {
|
|
if (x == null) return;
|
|
if (y == null) return;
|
|
}
|
|
g() => f(1, null);
|
|
''';
|
|
var expected = '''
|
|
void f<T>(T x, T y) {
|
|
if (x == null) return;
|
|
if (y == null) return;
|
|
}
|
|
g() => f(1, null);
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_nullTestOnGenericType_nonNullableBound() async {
|
|
var content = '''
|
|
void f<T extends Object/*!*/>(T x, T y) {
|
|
if (x == null) return;
|
|
if (y == null) return;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f<T extends Object>(T x, T y) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_nullTestOnGenericType_nullableBound() async {
|
|
var content = '''
|
|
void f<T extends Object>(T x, T y) {
|
|
if (x == null) return;
|
|
if (y == null) return;
|
|
}
|
|
g() => f(1, null);
|
|
''';
|
|
var expected = '''
|
|
void f<T extends Object?>(T x, T y) {
|
|
if (x == null) return;
|
|
if (y == null) return;
|
|
}
|
|
g() => f(1, null);
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_operator_eq_with_inferred_parameter_type() async {
|
|
var content = '''
|
|
class C {
|
|
operator==(Object other) {
|
|
return other is C;
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
operator==(Object other) {
|
|
return other is C;
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_override_object_from_type_parameter() async {
|
|
var content = '''
|
|
class C<T> {
|
|
f(T t) {}
|
|
}
|
|
class D<T> extends C<T> {
|
|
@override
|
|
f(Object t) {}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<T> {
|
|
f(T t) {}
|
|
}
|
|
class D<T> extends C<T> {
|
|
@override
|
|
f(Object? t) {}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_override_parameter_type_non_nullable() async {
|
|
var content = '''
|
|
abstract class Base {
|
|
void f(int i);
|
|
}
|
|
class Derived extends Base {
|
|
void f(int i) {
|
|
i + 1;
|
|
}
|
|
}
|
|
void g(int i, bool b, Base base) {
|
|
if (b) {
|
|
base.f(i);
|
|
}
|
|
}
|
|
void h(Base base) {
|
|
g(null, false, base);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
abstract class Base {
|
|
void f(int? i);
|
|
}
|
|
class Derived extends Base {
|
|
void f(int? i) {
|
|
i! + 1;
|
|
}
|
|
}
|
|
void g(int? i, bool b, Base base) {
|
|
if (b) {
|
|
base.f(i);
|
|
}
|
|
}
|
|
void h(Base base) {
|
|
g(null, false, base);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_override_parameter_type_nullable() async {
|
|
var content = '''
|
|
abstract class Base {
|
|
void f(int i);
|
|
}
|
|
class Derived extends Base {
|
|
void f(int i) {}
|
|
}
|
|
void g(int i, Base base) {
|
|
base.f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
abstract class Base {
|
|
void f(int? i);
|
|
}
|
|
class Derived extends Base {
|
|
void f(int? i) {}
|
|
}
|
|
void g(int i, Base base) {
|
|
base.f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_override_return_type_non_nullable() async {
|
|
var content = '''
|
|
abstract class Base {
|
|
int/*!*/ f();
|
|
}
|
|
class Derived extends Base {
|
|
int f() => g();
|
|
}
|
|
int g() => null;
|
|
''';
|
|
var expected = '''
|
|
abstract class Base {
|
|
int f();
|
|
}
|
|
class Derived extends Base {
|
|
int f() => g()!;
|
|
}
|
|
int? g() => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_override_return_type_nullable() async {
|
|
var content = '''
|
|
abstract class Base {
|
|
int f();
|
|
}
|
|
class Derived extends Base {
|
|
int f() => null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
abstract class Base {
|
|
int? f();
|
|
}
|
|
class Derived extends Base {
|
|
int? f() => null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_override_return_type_nullable_substitution_complex() async {
|
|
var content = '''
|
|
abstract class Base<T> {
|
|
T f();
|
|
}
|
|
class Derived extends Base<List<int>> {
|
|
List<int> f() => <int>[null];
|
|
}
|
|
''';
|
|
var expected = '''
|
|
abstract class Base<T> {
|
|
T f();
|
|
}
|
|
class Derived extends Base<List<int?>> {
|
|
List<int?> f() => <int?>[null];
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_override_return_type_nullable_substitution_simple() async {
|
|
var content = '''
|
|
abstract class Base<T> {
|
|
T f();
|
|
}
|
|
class Derived extends Base<int> {
|
|
int f() => null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
abstract class Base<T> {
|
|
T f();
|
|
}
|
|
class Derived extends Base<int?> {
|
|
int? f() => null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_parameter_genericFunctionType() async {
|
|
var content = '''
|
|
int f(int x, int Function(int i) g) {
|
|
return g(x);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int f(int x, int Function(int i) g) {
|
|
return g(x);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_postdominating_usage_after_cfg_altered() async {
|
|
// By altering the control-flow graph, we can create new postdominators,
|
|
// which are not recognized as such. This is not a problem as we only do
|
|
// hard edges on a best-effort basis, and this case would be a lot of
|
|
// additional complexity.
|
|
var content = '''
|
|
int f(int a, int b, int c) {
|
|
if (a != null) {
|
|
b.toDouble();
|
|
} else {
|
|
return null;
|
|
}
|
|
c.toDouble;
|
|
}
|
|
|
|
void main() {
|
|
f(1, null, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int f(int a, int? b, int? c) {
|
|
/* if (a != null) {
|
|
*/ b!.toDouble(); /*
|
|
} else {
|
|
return null;
|
|
} */
|
|
c!.toDouble;
|
|
}
|
|
|
|
void main() {
|
|
f(1, null, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected, removeViaComments: true);
|
|
}
|
|
|
|
Future<void> test_prefix_minus() async {
|
|
var content = '''
|
|
class C {
|
|
D operator-() => null;
|
|
}
|
|
class D {}
|
|
D test(C c) => -c;
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
D? operator-() => null;
|
|
}
|
|
class D {}
|
|
D? test(C c) => -c;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_prefix_minus_substitute() async {
|
|
var content = '''
|
|
abstract class C<T> {
|
|
D<T> operator-();
|
|
}
|
|
class D<U> {}
|
|
D<int> test(C<int/*?*/> c) => -c;
|
|
''';
|
|
var expected = '''
|
|
abstract class C<T> {
|
|
D<T> operator-();
|
|
}
|
|
class D<U> {}
|
|
D<int?> test(C<int?> c) => -c;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_prefixes() async {
|
|
var root = '$projectPath/lib';
|
|
var path1 = convertPath('$root/file1.dart');
|
|
var file1 = '''
|
|
import 'file2.dart';
|
|
int x;
|
|
int f() => null;
|
|
''';
|
|
var expected1 = '''
|
|
import 'file2.dart';
|
|
int? x;
|
|
int? f() => null;
|
|
''';
|
|
var path2 = convertPath('$root/file2.dart');
|
|
var file2 = '''
|
|
import 'file1.dart' as f1;
|
|
void main() {
|
|
f1.x = f1.f();
|
|
}
|
|
''';
|
|
var expected2 = '''
|
|
import 'file1.dart' as f1;
|
|
void main() {
|
|
f1.x = f1.f();
|
|
}
|
|
''';
|
|
await _checkMultipleFileChanges(
|
|
{path1: file1, path2: file2}, {path1: expected1, path2: expected2});
|
|
}
|
|
|
|
Future<void> test_prefixExpression_bang() async {
|
|
var content = '''
|
|
bool f(bool b) => !b;
|
|
void g(bool b1, bool b2) {
|
|
if (b1) {
|
|
f(b2);
|
|
}
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
bool f(bool b) => !b;
|
|
void g(bool b1, bool? b2) {
|
|
if (b1) {
|
|
f(b2!);
|
|
}
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_promotion_conditional_variableRead() async {
|
|
var content = '''
|
|
f({int i}) {
|
|
i = i == null ? 0 : i;
|
|
g(i);
|
|
}
|
|
|
|
g(int j) {}
|
|
''';
|
|
var expected = '''
|
|
f({int? i}) {
|
|
i = i == null ? 0 : i;
|
|
g(i);
|
|
}
|
|
|
|
g(int j) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_promotion_ifNull_variableRead() async {
|
|
var content = '''
|
|
f({int i}) {
|
|
i ??= 3;
|
|
g(i);
|
|
}
|
|
|
|
g(int j) {}
|
|
''';
|
|
var expected = '''
|
|
f({int? i}) {
|
|
i ??= 3;
|
|
g(i);
|
|
}
|
|
|
|
g(int j) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_promotion_ifNull_variableRead_alreadyPromoted() async {
|
|
var content = '''
|
|
f({num i}) {
|
|
if (i is int /*?*/) {
|
|
i ??= 3;
|
|
g(i);
|
|
}
|
|
}
|
|
|
|
g(int j) {}
|
|
''';
|
|
var expected = '''
|
|
f({num? i}) {
|
|
if (i is int?) {
|
|
i ??= 3;
|
|
g(i);
|
|
}
|
|
}
|
|
|
|
g(int j) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_promotion_ifNull_variableRead_subType() async {
|
|
var content = '''
|
|
f({num i}) {
|
|
i ??= 3;
|
|
g(i);
|
|
}
|
|
|
|
g(int j) {}
|
|
''';
|
|
var expected = '''
|
|
f({num? i}) {
|
|
i ??= 3;
|
|
g(i as int);
|
|
}
|
|
|
|
g(int j) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_promotion_preserves_complex_types() async {
|
|
var content = '''
|
|
int/*!*/ f(List<int/*?*/>/*?*/ x) {
|
|
x ??= [0];
|
|
return x[0];
|
|
}
|
|
''';
|
|
// `x ??= [0]` promotes x from List<int?>? to List<int?>. Since there is
|
|
// still a `?` on the `int`, `x[0]` must be null checked.
|
|
var expected = '''
|
|
int f(List<int?>? x) {
|
|
x ??= [0];
|
|
return x[0]!;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_propagate_non_null_intent_into_function_literal() async {
|
|
var content = '''
|
|
void f(int/*!*/ Function(int) callback) {
|
|
callback(null);
|
|
}
|
|
void test() {
|
|
f((int x) => x);
|
|
}
|
|
''';
|
|
// Since the function literal `(int x) => x` is created right here at the
|
|
// point where it's passed to `f`'s `callback` parameter, non-null intent is
|
|
// allowed to propagate backward from the return type of `callback` to the
|
|
// return type of the function literal. As a result, the reference to `x`
|
|
// in the function literal is null checked.
|
|
var expected = '''
|
|
void f(int Function(int?) callback) {
|
|
callback(null);
|
|
}
|
|
void test() {
|
|
f((int? x) => x!);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_property_access_on_cascade_result() async {
|
|
var content = '''
|
|
int f(List<int> l) {
|
|
l..first.isEven
|
|
..firstWhere((_) => true).isEven
|
|
..[0].isEven;
|
|
}
|
|
|
|
void g() {
|
|
f([null]);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int f(List<int?> l) {
|
|
l..first!.isEven
|
|
..firstWhere((_) => true)!.isEven
|
|
..[0]!.isEven;
|
|
}
|
|
|
|
void g() {
|
|
f([null]);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_quiver_checkNotNull_field_formal_initializer() async {
|
|
addQuiverPackage();
|
|
var content = '''
|
|
import 'package:quiver/check.dart';
|
|
class C {
|
|
final int i;
|
|
C(this.i) {
|
|
checkNotNull(i);
|
|
}
|
|
}
|
|
void f(bool b, int i) {
|
|
if (b) new C(i);
|
|
}
|
|
main() {
|
|
f(false, null);
|
|
}
|
|
''';
|
|
// Note: since the reference to `i` in `checkNotNull(i)` refers to the field
|
|
// rather than the formal parameter, this isn't considered sufficient to
|
|
// mark the field as non-nullable (even though that's the clear intention
|
|
// in this case). Changing the behavior to match user intent would require
|
|
// more development work; for now we just want to make sure we provide a
|
|
// fairly reasonable migration without crashing.
|
|
var expected = '''
|
|
import 'package:quiver/check.dart';
|
|
class C {
|
|
final int? i;
|
|
C(this.i) {
|
|
checkNotNull(i);
|
|
}
|
|
}
|
|
void f(bool b, int? i) {
|
|
if (b) new C(i);
|
|
}
|
|
main() {
|
|
f(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_quiver_checkNotNull_implies_non_null_intent() async {
|
|
addQuiverPackage();
|
|
var content = '''
|
|
import 'package:quiver/check.dart';
|
|
void f(int i) {
|
|
checkNotNull(i);
|
|
}
|
|
void g(bool b, int i) {
|
|
if (b) f(i);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'package:quiver/check.dart';
|
|
void f(int i) {
|
|
checkNotNull(i);
|
|
}
|
|
void g(bool b, int? i) {
|
|
if (b) f(i!);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_redirecting_constructor_factory() async {
|
|
var content = '''
|
|
class C {
|
|
factory C(int i, int j) = D;
|
|
}
|
|
class D implements C {
|
|
D(int i, int j);
|
|
}
|
|
main() {
|
|
C(null, 1);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
factory C(int? i, int j) = D;
|
|
}
|
|
class D implements C {
|
|
D(int? i, int j);
|
|
}
|
|
main() {
|
|
C(null, 1);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_redirecting_constructor_ordinary() async {
|
|
var content = '''
|
|
class C {
|
|
C(int i, int j) : this.named(j, i);
|
|
C.named(int j, int i);
|
|
}
|
|
main() {
|
|
C(null, 1);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
C(int? i, int j) : this.named(j, i);
|
|
C.named(int j, int? i);
|
|
}
|
|
main() {
|
|
C(null, 1);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_redirecting_constructor_ordinary_to_unnamed() async {
|
|
var content = '''
|
|
class C {
|
|
C.named(int i, int j) : this(j, i);
|
|
C(int j, int i);
|
|
}
|
|
main() {
|
|
C.named(null, 1);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
C.named(int? i, int j) : this(j, i);
|
|
C(int j, int? i);
|
|
}
|
|
main() {
|
|
C.named(null, 1);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_reference_to_mixin_getter() async {
|
|
var content = '''
|
|
mixin M {
|
|
Object f() => this.x;
|
|
|
|
Object get x => null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
mixin M {
|
|
Object? f() => this.x;
|
|
|
|
Object? get x => null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_regression_40551() async {
|
|
var content = '''
|
|
class B<T extends Object> { // bound should not be made nullable
|
|
void f(T t) { // parameter should not be made nullable
|
|
// Create an edge from the bound to some type
|
|
List<dynamic> x = [t];
|
|
// and make that type exact nullable
|
|
x[0] = null;
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class B<T extends Object> { // bound should not be made nullable
|
|
void f(T t) { // parameter should not be made nullable
|
|
// Create an edge from the bound to some type
|
|
List<dynamic> x = [t];
|
|
// and make that type exact nullable
|
|
x[0] = null;
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_regression_40552() async {
|
|
var content = '''
|
|
void f(Object o) { // parameter should not be made nullable
|
|
// Create an edge from the bound to some type
|
|
List<dynamic> x = [o];
|
|
// and make that type exact nullable
|
|
x[0] = null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(Object o) { // parameter should not be made nullable
|
|
// Create an edge from the bound to some type
|
|
List<dynamic> x = [o];
|
|
// and make that type exact nullable
|
|
x[0] = null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_regression_42374() async {
|
|
var content = '''
|
|
class C<R> {
|
|
R m(dynamic x) {
|
|
assert(x is R);
|
|
return x as R;
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
C<int/*!*/>().m(null/*!*/);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<R> {
|
|
R m(dynamic x) {
|
|
assert(x is R);
|
|
return x as R;
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
C<int>().m(null!);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_remove_question_from_question_dot() async {
|
|
var content = '_f(int/*!*/ i) => i?.isEven;';
|
|
var expected = '_f(int i) => i.isEven;';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_remove_question_from_question_dot_and_add_bang() async {
|
|
var content = '''
|
|
class C {
|
|
int/*?*/ i;
|
|
}
|
|
int/*!*/ f(C/*!*/ c) => c?.i;
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int? i;
|
|
}
|
|
int f(C c) => c.i!;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_remove_question_from_question_dot_method() async {
|
|
var content = '_f(int/*!*/ i) => i?.abs();';
|
|
var expected = '_f(int i) => i.abs();';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_remove_question_from_question_dot_shortcut() async {
|
|
var content = '''
|
|
class C {
|
|
int/*!*/ i;
|
|
}
|
|
bool/*?*/ f(C/*?*/ c) => c?.i?.isEven;
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int i;
|
|
}
|
|
bool? f(C? c) => c?.i.isEven;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_removed_if_element_doesnt_introduce_nullability() async {
|
|
// Failing because we don't yet remove the dead list element
|
|
// `if (x == null) recover()`.
|
|
var content = '''
|
|
f(int x) {
|
|
<int>[if (x == null) recover(), 0];
|
|
}
|
|
int recover() {
|
|
assert(false);
|
|
return null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
f(int x) {
|
|
<int>[0];
|
|
}
|
|
int? recover() {
|
|
assert(false);
|
|
return null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_requiredness_does_not_propagate_between_field_formal_params() async {
|
|
addMetaPackage();
|
|
var content = '''
|
|
import 'package:meta/meta.dart';
|
|
class C {
|
|
final bool x;
|
|
C.one({this.x});
|
|
C.two({@required this.x}) : assert(x != null);
|
|
}
|
|
test() => C.one();
|
|
''';
|
|
var expected = '''
|
|
import 'package:meta/meta.dart';
|
|
class C {
|
|
final bool? x;
|
|
C.one({this.x});
|
|
C.two({required bool this.x}) : assert(x != null);
|
|
}
|
|
test() => C.one();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_return_future_or_null_from_async_method() async {
|
|
var content = '''
|
|
import 'dart:async';
|
|
Future<Null> f() async => g();
|
|
FutureOr<Null> g() => null;
|
|
''';
|
|
var expected = '''
|
|
import 'dart:async';
|
|
Future<Null> f() async => g();
|
|
FutureOr<Null> g() => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_setter_overrides_implicit_setter() async {
|
|
var content = '''
|
|
class A {
|
|
String s = "x";
|
|
}
|
|
class C implements A {
|
|
String get s => "x";
|
|
void set s(String value) {}
|
|
}
|
|
f() => A().s = null;
|
|
''';
|
|
var expected = '''
|
|
class A {
|
|
String? s = "x";
|
|
}
|
|
class C implements A {
|
|
String get s => "x";
|
|
void set s(String? value) {}
|
|
}
|
|
f() => A().s = null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_single_file_multiple_changes() async {
|
|
var content = '''
|
|
int f() => null;
|
|
int g() => null;
|
|
''';
|
|
var expected = '''
|
|
int? f() => null;
|
|
int? g() => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_single_file_single_change() async {
|
|
var content = '''
|
|
int f() => null;
|
|
''';
|
|
var expected = '''
|
|
int? f() => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_singleWhere_non_nullable() async {
|
|
var content = '''
|
|
int singleEven(Iterable<int> x)
|
|
=> x.singleWhere((x) => x.isEven, orElse: () => null);
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableExtension;
|
|
|
|
int? singleEven(Iterable<int> x)
|
|
=> x.singleWhereOrNull((x) => x.isEven);
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_singleWhere_nullable() async {
|
|
var content = '''
|
|
int singleEven(Iterable<int> x)
|
|
=> x.singleWhere((x) => x.isEven, orElse: () => null);
|
|
f() => singleEven([null]);
|
|
''';
|
|
var expected = '''
|
|
int? singleEven(Iterable<int?> x)
|
|
=> x.singleWhere((x) => x!.isEven, orElse: () => null);
|
|
f() => singleEven([null]);
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/40728')
|
|
Future<void> test_soft_edge_for_assigned_variable() async {
|
|
var content = '''
|
|
void f(int i) {
|
|
print(i + 1);
|
|
i = null;
|
|
print(i);
|
|
}
|
|
main() {
|
|
f(0);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(int? i) {
|
|
print(i! + 1);
|
|
i = null;
|
|
print(i);
|
|
}
|
|
main() {
|
|
f(0);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_tearoff_parameter_matching_named() async {
|
|
var content = '''
|
|
void f(int x, void Function({int x}) callback) {
|
|
callback(x: x);
|
|
}
|
|
void g({int x}) {
|
|
assert(x != null);
|
|
}
|
|
void h() {
|
|
f(null, g);
|
|
}
|
|
''';
|
|
// Normally the assertion in g would cause g's `x` argument to be
|
|
// non-nullable (and thus required). However, since g is torn off and
|
|
// passed to f, which requires a callback that accepts null, g's `x`
|
|
// argument is nullable (and thus not required).
|
|
var expected = '''
|
|
void f(int? x, void Function({int? x}) callback) {
|
|
callback(x: x);
|
|
}
|
|
void g({int? x}) {
|
|
assert(x != null);
|
|
}
|
|
void h() {
|
|
f(null, g);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_testVariable_assignedNullableValue() async {
|
|
addTestCorePackage();
|
|
var content = '''
|
|
import 'package:test/test.dart';
|
|
void main() {
|
|
int i;
|
|
setUp(() {
|
|
i = null;
|
|
});
|
|
test('a', () {
|
|
i.isEven;
|
|
});
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'package:test/test.dart';
|
|
void main() {
|
|
int? i;
|
|
setUp(() {
|
|
i = null;
|
|
});
|
|
test('a', () {
|
|
i!.isEven;
|
|
});
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_testVariable_downstreamAllNonNull() async {
|
|
addTestCorePackage();
|
|
var content = '''
|
|
import 'package:test/test.dart';
|
|
void main() {
|
|
int i;
|
|
setUp(() {
|
|
i = 1;
|
|
});
|
|
test('a', () {
|
|
i.isEven;
|
|
});
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'package:test/test.dart';
|
|
void main() {
|
|
late int i;
|
|
setUp(() {
|
|
i = 1;
|
|
});
|
|
test('a', () {
|
|
i.isEven;
|
|
});
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_testVariable_hasInitializer() async {
|
|
addTestCorePackage();
|
|
var content = '''
|
|
import 'package:test/test.dart';
|
|
void main() {
|
|
int i = 1;
|
|
setUp(() {
|
|
i = 1;
|
|
});
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'package:test/test.dart';
|
|
void main() {
|
|
int i = 1;
|
|
setUp(() {
|
|
i = 1;
|
|
});
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_testVariable_usedAsNullable() async {
|
|
addTestCorePackage();
|
|
var content = '''
|
|
import 'package:test/test.dart';
|
|
void main() {
|
|
int i;
|
|
setUp(() {
|
|
i = 1;
|
|
});
|
|
f(int /*?*/ i) {}
|
|
test('a', () {
|
|
f(i);
|
|
});
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'package:test/test.dart';
|
|
void main() {
|
|
late int i;
|
|
setUp(() {
|
|
i = 1;
|
|
});
|
|
f(int? i) {}
|
|
test('a', () {
|
|
f(i);
|
|
});
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_this_inside_extension() async {
|
|
var content = '''
|
|
class C<T> {
|
|
T field;
|
|
C(this.field);
|
|
}
|
|
extension on C<int> {
|
|
f() {
|
|
this.field = null;
|
|
}
|
|
}
|
|
extension on C<List<int>> {
|
|
f() {
|
|
this.field = [null];
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<T> {
|
|
T field;
|
|
C(this.field);
|
|
}
|
|
extension on C<int?> {
|
|
f() {
|
|
this.field = null;
|
|
}
|
|
}
|
|
extension on C<List<int?>> {
|
|
f() {
|
|
this.field = [null];
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected, warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_topLevelFunction_parameterType_implicit_dynamic() async {
|
|
var content = '''
|
|
Object f(x) => x;
|
|
''';
|
|
// Note: even though the type `dynamic` permits `null`, the migration engine
|
|
// sees that there is no code path that passes a null value to `f`, so it
|
|
// leaves its return type as `Object`, and there is an implicit downcast.
|
|
var expected = '''
|
|
Object f(x) => x;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/39369')
|
|
Future<void> test_topLevelFunction_returnType_implicit_dynamic() async {
|
|
var content = '''
|
|
f() {}
|
|
Object g() => f();
|
|
''';
|
|
var expected = '''
|
|
f() {}
|
|
Object? g() => f();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_topLevelVariable_type_inferred() async {
|
|
var content = '''
|
|
int f() => null;
|
|
var x = 1;
|
|
void main() {
|
|
x = f();
|
|
}
|
|
''';
|
|
// The type of x is inferred as non-nullable from its initializer, but we
|
|
// try to assign a nullable value to it. So an explicit type must be added.
|
|
var expected = '''
|
|
int? f() => null;
|
|
int? x = 1;
|
|
void main() {
|
|
x = f();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_topLevelVariable_uninitialized_used() async {
|
|
var content = '''
|
|
String s;
|
|
f() {
|
|
g(s);
|
|
}
|
|
g(String /*!*/ s) {}
|
|
''';
|
|
var expected = '''
|
|
late String s;
|
|
f() {
|
|
g(s);
|
|
}
|
|
g(String s) {}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_two_files() async {
|
|
var root = '$projectPath/lib';
|
|
var path1 = convertPath('$root/file1.dart');
|
|
var file1 = '''
|
|
import 'file2.dart';
|
|
int f() => null;
|
|
int h() => g();
|
|
''';
|
|
var expected1 = '''
|
|
import 'file2.dart';
|
|
int? f() => null;
|
|
int? h() => g();
|
|
''';
|
|
var path2 = convertPath('$root/file2.dart');
|
|
var file2 = '''
|
|
import 'file1.dart';
|
|
int g() => f();
|
|
''';
|
|
var expected2 = '''
|
|
import 'file1.dart';
|
|
int? g() => f();
|
|
''';
|
|
await _checkMultipleFileChanges(
|
|
{path1: file1, path2: file2}, {path1: expected1, path2: expected2});
|
|
}
|
|
|
|
Future<void> test_type_argument_flows_to_bound() async {
|
|
// The inference of C<int?> forces class C to be declared as
|
|
// C<T extends Object?>.
|
|
var content = '''
|
|
abstract class C<T extends Object> {
|
|
void m(T t);
|
|
}
|
|
abstract class D<T extends Object> {
|
|
void m(T t);
|
|
}
|
|
f(C<int> c, D<int> d) {
|
|
c.m(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
abstract class C<T extends Object?> {
|
|
void m(T t);
|
|
}
|
|
abstract class D<T extends Object> {
|
|
void m(T t);
|
|
}
|
|
f(C<int?> c, D<int> d) {
|
|
c.m(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_complex() async {
|
|
var content = '''
|
|
typedef F<R> = Function(R);
|
|
|
|
class C<T> {
|
|
F<T> _f;
|
|
|
|
C(this._f) {
|
|
f(null);
|
|
}
|
|
|
|
f(Object o) {
|
|
_f(o as T);
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
typedef F<R> = Function(R);
|
|
|
|
class C<T> {
|
|
F<T?> _f;
|
|
|
|
C(this._f) {
|
|
f(null);
|
|
}
|
|
|
|
f(Object? o) {
|
|
_f(o as T?);
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_migrated_lhs_parameters() async {
|
|
var content = '''
|
|
import 'migrated_typedef.dart';
|
|
void main(F<int> f) {
|
|
f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'migrated_typedef.dart';
|
|
void main(F<int?> f) {
|
|
f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected, migratedInput: {
|
|
'$projectPath/lib/migrated_typedef.dart': '''
|
|
// @dart=2.12
|
|
typedef F<R> = Function(R);
|
|
'''
|
|
});
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_migrated_lhs_rhs_parameters() async {
|
|
var content = '''
|
|
import 'migrated_typedef.dart';
|
|
void f1(F<int> f) {
|
|
f<int>(null, null);
|
|
}
|
|
void f2(F<int> f) {
|
|
f<int>(0, null);
|
|
}
|
|
void f3(F<int> f) {
|
|
f<int>(null, 1);
|
|
}
|
|
void f4(F<int> f) {
|
|
f<int>(0, 1);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'migrated_typedef.dart';
|
|
void f1(F<int?> f) {
|
|
f<int?>(null, null);
|
|
}
|
|
void f2(F<int> f) {
|
|
f<int?>(0, null);
|
|
}
|
|
void f3(F<int?> f) {
|
|
f<int>(null, 1);
|
|
}
|
|
void f4(F<int> f) {
|
|
f<int>(0, 1);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected, migratedInput: {
|
|
'$projectPath/lib/migrated_typedef.dart': '''
|
|
// @dart=2.12
|
|
typedef F<T> = Function<R>(T, R);
|
|
'''
|
|
});
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_migrated_rhs_parameters() async {
|
|
var content = '''
|
|
import 'migrated_typedef.dart';
|
|
void main(F f) {
|
|
f<int>(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
import 'migrated_typedef.dart';
|
|
void main(F f) {
|
|
f<int?>(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected, migratedInput: {
|
|
'$projectPath/lib/migrated_typedef.dart': '''
|
|
// @dart=2.12
|
|
typedef F = Function<R>(R);
|
|
'''
|
|
});
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_parameter() async {
|
|
var content = '''
|
|
typedef F = Function(int);
|
|
|
|
F/*!*/ _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
typedef F = Function(int?);
|
|
|
|
late F _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_return() async {
|
|
var content = '''
|
|
typedef F = int Function();
|
|
|
|
F _f = () => null;
|
|
''';
|
|
var expected = '''
|
|
typedef F = int? Function();
|
|
|
|
F _f = () => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/40388')
|
|
Future<void> test_typedef_assign_null_return_type_formal() async {
|
|
var content = '''
|
|
typedef F = T Function<T>();
|
|
|
|
F _f = <T>() => null;
|
|
''';
|
|
var expected = '''
|
|
typedef F = T? Function<T>();
|
|
|
|
F _f = <T>() => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_return_type_parameter() async {
|
|
var content = '''
|
|
typedef F<T> = T Function();
|
|
|
|
F<int> _f = () => null;
|
|
''';
|
|
var expected = '''
|
|
typedef F<T> = T Function();
|
|
|
|
F<int?> _f = () => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_type_formal() async {
|
|
var content = '''
|
|
typedef F = Function<T>(T);
|
|
|
|
F/*!*/ _f;
|
|
|
|
f() {
|
|
_f<int>(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
typedef F = Function<T>(T);
|
|
|
|
late F _f;
|
|
|
|
f() {
|
|
_f<int?>(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_type_formal_with_parameter() async {
|
|
var content = '''
|
|
typedef F<R> = Function<T>(T);
|
|
|
|
F<Object>/*!*/ _f;
|
|
|
|
f() {
|
|
_f<int>(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
typedef F<R> = Function<T>(T);
|
|
|
|
late F<Object> _f;
|
|
|
|
f() {
|
|
_f<int?>(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_type_parameter() async {
|
|
var content = '''
|
|
typedef F<T> = Function(T);
|
|
|
|
F<int>/*!*/ _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
typedef F<T> = Function(T);
|
|
|
|
late F<int?> _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_type_parameter_non_null() async {
|
|
var content = '''
|
|
typedef F<T> = Function(T);
|
|
|
|
F<int>/*!*/ _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
typedef F<T> = Function(T);
|
|
|
|
late F<int?> _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_assign_null_type_return_value_nested() async {
|
|
var content = '''
|
|
typedef F<T> = T Function();
|
|
|
|
F<F<int>> f = () => () => null;
|
|
''';
|
|
var expected = '''
|
|
typedef F<T> = T Function();
|
|
|
|
F<F<int?>> f = () => () => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_old_assign_null_parameter() async {
|
|
var content = '''
|
|
typedef F(int x);
|
|
|
|
F/*!*/ _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
typedef F(int? x);
|
|
|
|
late F _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_old_assign_null_return() async {
|
|
var content = '''
|
|
typedef int F();
|
|
|
|
F _f = () => null;
|
|
''';
|
|
var expected = '''
|
|
typedef int? F();
|
|
|
|
F _f = () => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_old_assign_null_return_type_parameter() async {
|
|
var content = '''
|
|
typedef T F<T>();
|
|
|
|
F<int> _f = () => null;
|
|
''';
|
|
var expected = '''
|
|
typedef T F<T>();
|
|
|
|
F<int?> _f = () => null;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_old_assign_null_type_parameter() async {
|
|
var content = '''
|
|
typedef F<T>(T t);
|
|
|
|
F<int>/*!*/ _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
typedef F<T>(T t);
|
|
|
|
late F<int?> _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_typedef_old_assign_null_type_parameter_non_null() async {
|
|
var content = '''
|
|
typedef F<T>(T t);
|
|
|
|
F<int>/*!*/ _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
typedef F<T>(T t);
|
|
|
|
late F<int?> _f;
|
|
|
|
f() {
|
|
_f(null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_assert_is_statement_implies_non_null_intent() async {
|
|
var content = '''
|
|
void f(Object i) {
|
|
assert(i is int);
|
|
}
|
|
void g(bool b, int i) {
|
|
if (b) f(i);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(Object i) {
|
|
assert(i is int);
|
|
}
|
|
void g(bool b, int? i) {
|
|
if (b) f(i!);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_assert_statement_implies_non_null_intent() async {
|
|
var content = '''
|
|
void f(int i) {
|
|
assert(i != null);
|
|
}
|
|
void g(bool b, int i) {
|
|
if (b) f(i);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(int i) {
|
|
assert(i != null);
|
|
}
|
|
void g(bool b, int? i) {
|
|
if (b) f(i!);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_binary_expression_implies_non_null_intent() async {
|
|
var content = '''
|
|
void f(int i) {
|
|
i + 1;
|
|
}
|
|
void g(bool b, int i) {
|
|
if (b) f(i);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(int i) {
|
|
i + 1;
|
|
}
|
|
void g(bool b, int? i) {
|
|
if (b) f(i!);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_cascaded_indexed_set_implies_non_null_intent() async {
|
|
var content = '''
|
|
class C {
|
|
operator[]=(int i, int j) {}
|
|
}
|
|
void f(C c) {
|
|
c..[1] = 2;
|
|
}
|
|
void g(bool b, C c) {
|
|
if (b) f(c);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
operator[]=(int i, int j) {}
|
|
}
|
|
void f(C c) {
|
|
c..[1] = 2;
|
|
}
|
|
void g(bool b, C? c) {
|
|
if (b) f(c!);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_cascaded_method_call_implies_non_null_intent() async {
|
|
var content = '''
|
|
void f(int i) {
|
|
i..abs();
|
|
}
|
|
void g(bool b, int i) {
|
|
if (b) f(i);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(int i) {
|
|
i..abs();
|
|
}
|
|
void g(bool b, int? i) {
|
|
if (b) f(i!);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_cascaded_property_set_implies_non_null_intent() async {
|
|
var content = '''
|
|
class C {
|
|
int x = 0;
|
|
}
|
|
void f(C c) {
|
|
c..x = 1;
|
|
}
|
|
void g(bool b, C c) {
|
|
if (b) f(c);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int x = 0;
|
|
}
|
|
void f(C c) {
|
|
c..x = 1;
|
|
}
|
|
void g(bool b, C? c) {
|
|
if (b) f(c!);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_unconditional_method_call_implies_non_null_intent() async {
|
|
var content = '''
|
|
void f(int i) {
|
|
i.abs();
|
|
}
|
|
void g(bool b, int i) {
|
|
if (b) f(i);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(int i) {
|
|
i.abs();
|
|
}
|
|
void g(bool b, int? i) {
|
|
if (b) f(i!);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_method_call_implies_non_null_intent_after_conditions() async {
|
|
var content = '''
|
|
void g(bool b, int i1, int i2) {
|
|
int i3 = i1;
|
|
if (b) {
|
|
b;
|
|
}
|
|
i3.toDouble();
|
|
int i4 = i2;
|
|
if (b) {
|
|
b;
|
|
return;
|
|
}
|
|
i4.toDouble();
|
|
}
|
|
test(int/*?*/ n) {
|
|
g(false, n, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void g(bool b, int i1, int? i2) {
|
|
int i3 = i1;
|
|
if (b) {
|
|
b;
|
|
}
|
|
i3.toDouble();
|
|
int? i4 = i2;
|
|
if (b) {
|
|
b;
|
|
return;
|
|
}
|
|
i4!.toDouble();
|
|
}
|
|
test(int? n) {
|
|
g(false, n!, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_method_call_implies_non_null_intent_in_condition() async {
|
|
var content = '''
|
|
void g(bool b, int _i) {
|
|
if (b) {
|
|
int i = _i;
|
|
i.toDouble();
|
|
}
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void g(bool b, int? _i) {
|
|
if (b) {
|
|
int i = _i!;
|
|
i.toDouble();
|
|
}
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_non_null_usage_implies_non_null_intent() async {
|
|
var content = '''
|
|
void f(int i, int j) {
|
|
i.gcd(j);
|
|
}
|
|
void g(bool b, int i, int j) {
|
|
if (b) f(i, j);
|
|
}
|
|
main() {
|
|
g(false, 0, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(int i, int j) {
|
|
i.gcd(j);
|
|
}
|
|
void g(bool b, int i, int? j) {
|
|
if (b) f(i, j!);
|
|
}
|
|
main() {
|
|
g(false, 0, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_property_access_implies_non_null_intent() async {
|
|
var content = '''
|
|
void f(int i) {
|
|
i.isEven;
|
|
}
|
|
void g(bool b, int i) {
|
|
if (b) f(i);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(int i) {
|
|
i.isEven;
|
|
}
|
|
void g(bool b, int? i) {
|
|
if (b) f(i!);
|
|
}
|
|
main() {
|
|
g(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_unconditional_usage_propagates_non_null_intent() async {
|
|
var content = '''
|
|
void f(int i) {
|
|
assert(i != null);
|
|
}
|
|
void g(int i) {
|
|
f(i);
|
|
}
|
|
void h(bool b, int i) {
|
|
if (b) g(i);
|
|
}
|
|
main() {
|
|
h(false, null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f(int i) {
|
|
assert(i != null);
|
|
}
|
|
void g(int i) {
|
|
f(i);
|
|
}
|
|
void h(bool b, int? i) {
|
|
if (b) g(i!);
|
|
}
|
|
main() {
|
|
h(false, null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_use_of_field_formal_param_does_not_create_hard_edge() async {
|
|
var content = '''
|
|
class C {
|
|
int i;
|
|
int j;
|
|
C.one(this.i) : j = i + 1;
|
|
C.two() : i = null, j = 0;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int? i;
|
|
int j;
|
|
C.one(int this.i) : j = i + 1;
|
|
C.two() : i = null, j = 0;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void>
|
|
test_unconditional_use_of_field_formal_param_does_not_create_hard_edge_generic() async {
|
|
var content = '''
|
|
class C {
|
|
List<int/*?*/> i;
|
|
int j;
|
|
C.one(this.i) : j = i.length;
|
|
C.two() : i = null, j = 0;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
List<int?>? i;
|
|
int j;
|
|
C.one(List<int?> this.i) : j = i.length;
|
|
C.two() : i = null, j = 0;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_uninitialized_instance_field_is_nullable() async {
|
|
var content = '''
|
|
class C {
|
|
int i;
|
|
f() {
|
|
print(i == null);
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
int? i;
|
|
f() {
|
|
print(i == null);
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_uninitialized_static_field_is_nullable() async {
|
|
var content = '''
|
|
class C {
|
|
static int i;
|
|
f() {
|
|
print(i == null);
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C {
|
|
static int? i;
|
|
f() {
|
|
print(i == null);
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_uninitialized_toplevel_var_is_nullable() async {
|
|
var content = '''
|
|
int i;
|
|
f() {
|
|
print(i == null);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int? i;
|
|
f() {
|
|
print(i == null);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_unnecessary_cast_remove() async {
|
|
var content = '''
|
|
_f(Object x) {
|
|
if (x is! int) return;
|
|
print((x as int) + 1);
|
|
}
|
|
''';
|
|
var expected = '''
|
|
_f(Object x) {
|
|
if (x is! int) return;
|
|
print(x + 1);
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/44012')
|
|
Future<void> test_use_import_prefix_when_adding_re_exported_type() async {
|
|
addPackageFile('http', 'http.dart', '''
|
|
export 'src/base_client.dart';
|
|
export 'src/client.dart';
|
|
''');
|
|
addPackageFile('http', 'src/base_client.dart', '''
|
|
import 'client.dart';
|
|
abstract class BaseClient implements Client {}
|
|
''');
|
|
addPackageFile('http', 'src/client.dart', '''
|
|
abstract class Client {}
|
|
''');
|
|
var content = '''
|
|
import 'package:http/http.dart' as http;
|
|
http.BaseClient downcast(http.Client x) => x;
|
|
''';
|
|
var expected = '''
|
|
import 'package:http/http.dart' as http;
|
|
http.BaseClient downcast(http.Client x) => x as http.BaseClient;
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_var_with_different_types() async {
|
|
// Based on https://github.com/dart-lang/sdk/issues/47669
|
|
var content = '''
|
|
class C<T> {
|
|
T m() => throw 'foo';
|
|
}
|
|
f(bool b, List<C<int>> cs) {
|
|
var x = !b,
|
|
y = cs.first,
|
|
z = y.m();
|
|
}
|
|
''';
|
|
var expected = '''
|
|
class C<T> {
|
|
T m() => throw 'foo';
|
|
}
|
|
f(bool b, List<C<int>> cs) {
|
|
var x = !b,
|
|
y = cs.first,
|
|
z = y.m();
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_var_with_different_types_becoming_explicit() async {
|
|
// When types need to be added to some variables in a declaration but not
|
|
// others, we handle it by introducing `as` casts.
|
|
var content = '''
|
|
f(int i, String s) {
|
|
var x = i, y = s;
|
|
x = null;
|
|
}
|
|
''';
|
|
var expected = '''
|
|
f(int i, String s) {
|
|
var x = i as int?, y = s;
|
|
x = null;
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_weak_if_visit_weak_subexpression() async {
|
|
var content = '''
|
|
int f(int x, int/*?*/ y) {
|
|
if (x == null) {
|
|
print(y.toDouble());
|
|
} else {
|
|
print(y.toDouble());
|
|
}
|
|
}
|
|
''';
|
|
var expected = '''
|
|
int f(int x, int? y) {
|
|
if (x == null) {
|
|
print(y!.toDouble());
|
|
} else {
|
|
print(y!.toDouble());
|
|
}
|
|
}
|
|
''';
|
|
await _checkSingleFileChanges(content, expected, warnOnWeakCode: true);
|
|
}
|
|
|
|
Future<void> test_whereNotNull() async {
|
|
var content = '''
|
|
Iterable<String> f(Iterable<String/*?*/> it) => it.where((s) => s != null);
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableNullableExtension;
|
|
|
|
Iterable<String> f(Iterable<String?> it) => it.whereNotNull();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_whereNotNull_and_firstWhereOrNull() async {
|
|
var content = '''
|
|
Iterable<String> f(Iterable<String/*?*/> it) => it.where((s) => s != null);
|
|
int g(Iterable<int> it) => it.firstWhere((i) => i != 0, orElse: () => null);
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableExtension, IterableNullableExtension;
|
|
|
|
Iterable<String> f(Iterable<String?> it) => it.whereNotNull();
|
|
int? g(Iterable<int> it) => it.firstWhereOrNull((i) => i != 0);
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_whereNotNull_complexType() async {
|
|
var content = '''
|
|
Iterable<Map<String, int>> f(Iterable<Map<String/*?*/, int>/*?*/> it)
|
|
=> it.where((m) => m != null);
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableNullableExtension;
|
|
|
|
Iterable<Map<String?, int>> f(Iterable<Map<String?, int>?> it)
|
|
=> it.whereNotNull();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_whereNotNull_iterable_dynamic() async {
|
|
var content = '''
|
|
f(Iterable<dynamic> it) => it.where((s) => s != null);
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableNullableExtension;
|
|
|
|
f(Iterable<dynamic> it) => it.whereNotNull();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/49103')
|
|
Future<void> test_whereNotNull_iterable_U() async {
|
|
var content = '''
|
|
f<U>(Iterable<U> it) => it.where((s) => s != null);
|
|
''';
|
|
// whereNotNull cannot be used in this case, because its signature is:
|
|
//
|
|
// extension IterableNullableExtension<T extends Object> on Iterable<T?> {
|
|
// Iterable<T> whereNotNull() => ...;
|
|
// }
|
|
//
|
|
// When the type system tries to solve for a substitution T=... that makes
|
|
// the extension apply, it gets T=U, but that doesn't work because U is not
|
|
// a subtype of Object.
|
|
//
|
|
// So the migration tool shouldn't change the `where` to `whereNotNull`.
|
|
var expected = '''
|
|
f<U>(Iterable<U> it) => it.where((s) => s != null);
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_whereNotNull_iterable_U_extends_object() async {
|
|
var content = '''
|
|
f<U extends Object>(Iterable<U> it) => it.where((s) => s != null);
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableNullableExtension;
|
|
|
|
f<U extends Object>(Iterable<U> it) => it.whereNotNull();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
|
|
Future<void> test_whereNotNull_noContext() async {
|
|
var content = '''
|
|
f(Iterable<String/*?*/> it) => it.where((s) => s != null);
|
|
''';
|
|
var expected = '''
|
|
import 'package:collection/collection.dart' show IterableNullableExtension;
|
|
|
|
f(Iterable<String?> it) => it.whereNotNull();
|
|
''';
|
|
await _checkSingleFileChanges(content, expected);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class _ProvisionalApiTestPermissive extends _ProvisionalApiTestBase
|
|
with _ProvisionalApiTestCases {
|
|
@override
|
|
bool get _usePermissiveMode => true;
|
|
}
|
|
|
|
/// Tests of the provisional API, where the driver is reset between calls to
|
|
/// `prepareInput` and `processInput`, ensuring that the migration algorithm
|
|
/// sees different AST and element objects during different phases.
|
|
@reflectiveTest
|
|
class _ProvisionalApiTestWithReset extends _ProvisionalApiTestBase
|
|
with _ProvisionalApiTestCases {
|
|
@override
|
|
bool get _usePermissiveMode => false;
|
|
|
|
@override
|
|
void _betweenStages() {
|
|
driver!.clearLibraryContext();
|
|
}
|
|
}
|