blob: a9026b2ef66c61204bcf5a9c5a648feb7b62c643 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/error/hint_codes.dart';
import 'package:analyzer/src/generated/element_type_provider.dart';
import 'package:nnbd_migration/fix_reason_target.dart';
import 'package:nnbd_migration/nnbd_migration.dart';
import 'package:nnbd_migration/src/edit_plan.dart';
import 'package:nnbd_migration/src/fix_aggregator.dart';
import 'package:nnbd_migration/src/fix_builder.dart';
import 'package:nnbd_migration/src/nullability_node.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'migration_visitor_test_base.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(FixBuilderTest);
});
}
/// Information about the target of an assignment expression analyzed by
/// [FixBuilder].
class AssignmentTargetInfo {
/// The type that the assignment target has when read. This is only relevant
/// for compound assignments (since they both read and write the assignment
/// target)
final DartType? readType;
/// The type that the assignment target has when written to.
final DartType? writeType;
AssignmentTargetInfo(this.readType, this.writeType);
}
@reflectiveTest
class FixBuilderTest extends EdgeBuilderTestBase {
static final isAddRequiredKeyword =
TypeMatcher<NodeChangeForDefaultFormalParameter>()
.having((c) => c.addRequiredKeyword, 'addRequiredKeyword', true);
static final isMakeNullable = TypeMatcher<NodeChangeForTypeAnnotation>()
.having((c) => c.makeNullable, 'makeNullable', true)
.having((c) => c.nullabilityHint, 'nullabilityHint', isNull);
static final isMakeNullableDueToHint =
TypeMatcher<NodeChangeForTypeAnnotation>()
.having((c) => c.makeNullable, 'makeNullable', true)
.having((c) => c.nullabilityHint, 'nullabilityHint', isNotNull);
static const isEdge = TypeMatcher<NullabilityEdge>();
static final isExplainNonNullable = TypeMatcher<NodeChangeForTypeAnnotation>()
.having((c) => c.makeNullable, 'makeNullable', false);
static final isBadCombinedType = TypeMatcher<NodeChangeForAssignmentLike>()
.having((c) => c.hasBadCombinedType, 'hasBadCombinedType', true);
static final isNullableSource = TypeMatcher<NodeChangeForAssignmentLike>()
.having((c) => c.hasNullableSource, 'hasNullableSource', true);
static final isNodeChangeForExpression =
TypeMatcher<NodeChangeForExpression>();
static final isNoValidMigration =
isNodeChangeForExpression.havingNoValidMigrationWithInfo(anything);
static final isNullCheck =
isNodeChangeForExpression.havingNullCheckWithInfo(anything);
static final isRemoveLanguageVersion =
TypeMatcher<NodeChangeForCompilationUnit>().having(
(c) => c.removeLanguageVersionComment,
'removeLanguageVersionComment',
true);
static final isAddImportOfIterableExtension =
TypeMatcher<NodeChangeForCompilationUnit>()
.having((c) => c.addImports, 'addImports', {
'package:collection/collection.dart': {'IterableExtension'}
});
static final isAddShowOfIterableExtension =
TypeMatcher<NodeChangeForShowCombinator>().having((c) => c.addNames,
'addNames', unorderedEquals(['IterableExtension']));
static final isRemoveNullAwareness =
TypeMatcher<NodeChangeForPropertyAccess>()
.having((c) => c.removeNullAwareness, 'removeNullAwareness', true);
static final isRemoveAs = TypeMatcher<NodeChangeForAsExpression>()
.having((c) => c.removeAs, 'removeAs', true);
static final isRequiredAnnotationToRequiredKeyword =
TypeMatcher<NodeChangeForAnnotation>().having(
(c) => c.changeToRequiredKeyword, 'changeToRequiredKeyword', true);
static final isWeakNullAwareAssignment =
TypeMatcher<NodeChangeForAssignment>()
.having((c) => c.isWeakNullAware, 'isWeakNullAware', true);
DartType get dynamicType => postMigrationTypeProvider.dynamicType;
DartType get objectType => postMigrationTypeProvider.objectType;
TypeProvider get postMigrationTypeProvider =>
(typeProvider as TypeProviderImpl).asNonNullableByDefault;
@override
Future<CompilationUnit> analyze(String code) async {
var unit = await super.analyze(code);
graph.propagate();
return unit;
}
TypeMatcher<NodeChangeForArgumentList> isDropArgument(
dynamic argumentsToDrop) =>
TypeMatcher<NodeChangeForArgumentList>()
.having((c) => c.argumentsToDrop, 'argumentsToDrop', argumentsToDrop);
TypeMatcher<AtomicEditInfo> isInfo(description, fixReasons) =>
TypeMatcher<AtomicEditInfo>()
.having((i) => i.description, 'description', description)
.having((i) => i.fixReasons, 'fixReasons', fixReasons);
TypeMatcher<NodeChangeForMethodName> isMethodNameChange(
dynamic replacement) =>
TypeMatcher<NodeChangeForMethodName>()
.having((c) => c.replacement, 'replacement', replacement);
Map<AstNode, NodeChange> scopedChanges(
FixBuilder fixBuilder, AstNode? scope) =>
{
for (var entry in fixBuilder.changes.entries)
if (_isInScope(entry.key, scope) && !entry.value.isInformative)
entry.key: entry.value
};
Map<AstNode, NodeChange> scopedInformative(
FixBuilder fixBuilder, AstNode scope) =>
{
for (var entry in fixBuilder.changes.entries)
if (_isInScope(entry.key, scope) && entry.value.isInformative)
entry.key: entry.value
};
Map<AstNode, Set<Problem>> scopedProblems(
FixBuilder fixBuilder, AstNode? scope) =>
{
for (var entry in fixBuilder.problems.entries)
if (_isInScope(entry.key, scope)) entry.key: entry.value
};
Future<void> test_asExpression_keep() async {
await analyze('''
_f(Object x) {
print((x as int) + 1);
}
''');
var asExpression = findNode.simple('x as').parent as Expression;
visitSubexpression(asExpression, 'int');
}
Future<void> test_asExpression_keep_previously_unnecessary() async {
verifyNoTestUnitErrors = false;
await analyze('''
f(int i) {
print((i as int) + 1);
}
''');
expect(
testAnalysisResult.errors.single.errorCode, HintCode.UNNECESSARY_CAST);
var asExpression = findNode.simple('i as').parent as Expression;
visitSubexpression(asExpression, 'int');
}
Future<void> test_asExpression_remove() async {
await analyze('''
_f(Object x) {
if (x is! int) return;
print((x as int) + 1);
}
''');
var asExpression = findNode.simple('x as').parent as Expression;
visitSubexpression(asExpression, 'int',
changes: {asExpression: isRemoveAs});
}
Future<void>
test_assignmentExpression_compound_combined_nullable_noProblem() async {
await analyze('''
abstract class _C {
_D/*?*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
abstract class _E {
_C/*!*/ get x;
void set x(_C/*?*/ value);
f(int/*!*/ y) => x += y;
}
''');
visitSubexpression(findNode.assignment('+='), '_D?');
}
Future<void>
test_assignmentExpression_compound_combined_nullable_noProblem_dynamic() async {
await analyze('''
abstract class _E {
dynamic get x;
void set x(Object/*!*/ value);
f(int/*!*/ y) => x += y;
}
''');
var assignment = findNode.assignment('+=');
visitSubexpression(assignment, 'dynamic');
}
@FailingTest(reason: 'TODO(paulberry)')
Future<void>
test_assignmentExpression_compound_combined_nullable_problem() async {
await analyze('''
abstract class _C {
_D/*?*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
abstract class _E {
_C/*!*/ get x;
void set x(_C/*!*/ value);
f(int/*!*/ y) => x += y;
}
''');
var assignment = findNode.assignment('+=');
visitSubexpression(assignment, '_D', problems: {
assignment: {const CompoundAssignmentCombinedNullable()}
});
}
Future<void> test_assignmentExpression_compound_dynamic() async {
// To confirm that the RHS is visited, we check that a null check was
// properly inserted into a subexpression of the RHS.
await analyze('''
_f(dynamic x, int/*?*/ y) => x += y + 1;
''');
visitSubexpression(findNode.assignment('+='), 'dynamic',
changes: {findNode.simple('y +'): isNullCheck});
}
Future<void> test_assignmentExpression_compound_intRules() async {
await analyze('''
_f(int x, int y) => x += y;
''');
visitSubexpression(findNode.assignment('+='), 'int');
}
@FailingTest(reason: 'TODO(paulberry)')
Future<void> test_assignmentExpression_compound_lhs_nullable_problem() async {
await analyze('''
abstract class _C {
_D/*!*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
abstract class _E {
_C/*?*/ get x;
void set x(_C/*?*/ value);
f(int/*!*/ y) => x += y;
}
''');
var assignment = findNode.assignment('+=');
visitSubexpression(assignment, '_D', problems: {
assignment: {const CompoundAssignmentReadNullable()}
});
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/39641')
Future<void> test_assignmentExpression_compound_promoted() async {
await analyze('''
f(bool/*?*/ x, bool/*?*/ y) => x != null && (x = y);
''');
// It is ok to assign a nullable value to `x` even though it is promoted to
// non-nullable, so `y` should not be null-checked. However, the whole
// assignment `x = y` should be null checked because the RHS of `&&` cannot
// be nullable.
visitSubexpression(findNode.binary('&&'), 'bool',
changes: {findNode.parenthesized('x = y'): isNullCheck});
}
Future<void> test_assignmentExpression_compound_rhs_nonNullable() async {
await analyze('''
abstract class _C {
_D/*!*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
_f(_C/*!*/ x, int/*!*/ y) => x += y;
''');
visitSubexpression(findNode.assignment('+='), '_D');
}
Future<void> test_assignmentExpression_compound_rhs_nullable_check() async {
await analyze('''
abstract class _C {
_D/*!*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
_f(_C/*!*/ x, int/*?*/ y) => x += y;
''');
visitSubexpression(findNode.assignment('+='), '_D',
changes: {findNode.simple('y;'): isNullCheck});
}
Future<void> test_assignmentExpression_compound_rhs_nullable_noCheck() async {
await analyze('''
abstract class _C {
_D/*!*/ operator+(int/*?*/ value);
}
abstract class _D extends _C {}
_f(_C/*!*/ x, int/*?*/ y) => x += y;
''');
visitSubexpression(findNode.assignment('+='), '_D');
}
Future<void>
test_assignmentExpression_null_aware_rhs_does_not_promote() async {
await analyze('''
_f(bool/*?*/ b, int/*?*/ i) {
b ??= i.isEven; // 1
b = i.isEven; // 2
b = i.isEven; // 3
}
''');
// The null check inserted at 1 fails to promote i because it's inside the
// `??=`, so a null check is inserted at 2. This does promote i, so no null
// check is inserted at 3.
visitStatement(findNode.block('{'), changes: {
findNode.simple('i.isEven; // 1'): isNullCheck,
findNode.simple('i.isEven; // 2'): isNullCheck
});
}
Future<void> test_assignmentExpression_null_aware_rhs_nonNullable() async {
await analyze('''
abstract class _B {}
abstract class _C extends _B {}
abstract class _D extends _C {}
abstract class _E extends _C {}
abstract class _F {
_D/*?*/ get x;
void set x(_B/*?*/ value);
f(_E/*!*/ y) => x ??= y;
}
''');
visitSubexpression(findNode.assignment('??='), '_C');
}
Future<void> test_assignmentExpression_null_aware_rhs_nullable() async {
await analyze('''
abstract class _B {}
abstract class _C extends _B {}
abstract class _D extends _C {}
abstract class _E extends _C {}
abstract class _F {
_D/*?*/ get x;
void set x(_B/*?*/ value);
f(_E/*?*/ y) => x ??= y;
}
''');
visitSubexpression(findNode.assignment('??='), '_C?');
}
Future<void> test_assignmentExpression_null_aware_simple_promoted() async {
await analyze('''
_f(bool/*?*/ x, bool/*?*/ y) => x != null && (x ??= y) != null;
''');
// On the RHS of the `&&`, `x` is promoted to non-nullable, but it is still
// considered to be a nullable assignment target, so no null check is
// generated for `y`.
visitSubexpression(findNode.binary('&&'), 'bool',
changes: {findNode.assignment('??='): isWeakNullAwareAssignment});
}
Future<void>
test_assignmentExpression_simple_nonNullable_to_nonNullable() async {
await analyze('''
_f(int/*!*/ x, int/*!*/ y) => x = y;
''');
visitSubexpression(findNode.assignment('= '), 'int');
}
Future<void>
test_assignmentExpression_simple_nonNullable_to_nullable() async {
await analyze('''
_f(int/*?*/ x, int/*!*/ y) => x = y;
''');
visitSubexpression(findNode.assignment('= '), 'int');
}
Future<void>
test_assignmentExpression_simple_nullable_to_nonNullable() async {
await analyze('''
_f(int/*!*/ x, int/*?*/ y) => x = y;
''');
visitSubexpression(findNode.assignment('= '), 'int',
changes: {findNode.simple('y;'): isNullCheck});
}
Future<void> test_assignmentExpression_simple_nullable_to_nullable() async {
await analyze('''
_f(int/*?*/ x, int/*?*/ y) => x = y;
''');
visitSubexpression(findNode.assignment('= '), 'int?');
}
Future<void> test_assignmentExpression_simple_promoted() async {
await analyze('''
_f(bool/*?*/ x, bool/*?*/ y) => x != null && (x = y) != null;
''');
// On the RHS of the `&&`, `x` is promoted to non-nullable, but it is still
// considered to be a nullable assignment target, so no null check is
// generated for `y`.
visitSubexpression(findNode.binary('&&'), 'bool');
}
Future<void> test_assignmentTarget_indexExpression_compound_dynamic() async {
await analyze('''
_f(dynamic d, int/*?*/ i) => d[i] += 0;
''');
visitAssignmentTarget(findNode.index('d[i]'), 'dynamic', 'dynamic');
}
Future<void> test_assignmentTarget_indexExpression_compound_simple() async {
await analyze('''
class _C {
int operator[](String s) => 1;
void operator[]=(String s, num n) {}
}
_f(_C c) => c['foo'] += 0;
''');
visitAssignmentTarget(findNode.index('c['), 'int', 'num');
}
Future<void>
test_assignmentTarget_indexExpression_compound_simple_check_lhs() async {
await analyze('''
class _C {
int operator[](String s) => 1;
void operator[]=(String s, num n) {}
}
_f(_C/*?*/ c) => c['foo'] += 0;
''');
visitAssignmentTarget(findNode.index('c['), 'int', 'num',
changes: {findNode.simple('c['): isNullCheck});
}
@FailingTest(reason: 'TODO(paulberry): decide if this is worth caring about')
Future<void>
test_assignmentTarget_indexExpression_compound_simple_check_rhs() async {
await analyze('''
class _C {
int operator[](String/*!*/ s) => 1;
void operator[]=(String/*?*/ s, num n) {}
}
_f(_C c, String/*?*/ s) => c[s] += 0;
''');
visitAssignmentTarget(findNode.index('c['), 'int', 'num',
changes: {findNode.simple('s]'): isNullCheck});
}
Future<void>
test_assignmentTarget_indexExpression_compound_substituted() async {
await analyze('''
class _C<T, U> {
T operator[](U u) => throw 'foo';
void operator[]=(U u, T t) {}
}
_f(_C<int, String> c) => c['foo'] += 1;
''');
visitAssignmentTarget(findNode.index('c['), 'int', 'int');
}
@FailingTest(reason: 'TODO(paulberry): decide if this is worth caring about')
Future<void>
test_assignmentTarget_indexExpression_compound_substituted_check_rhs() async {
await analyze('''
class _C<T, U> {
T operator[](U u) => throw 'foo';
void operator[]=(U/*?*/ u, T t) {}
}
_f(_C<int, String/*!*/> c, String/*?*/ s) => c[s] += 1;
''');
visitAssignmentTarget(findNode.index('c['), 'int', 'int',
changes: {findNode.simple('s]'): isNullCheck});
}
Future<void>
test_assignmentTarget_indexExpression_compound_substituted_no_check_rhs() async {
await analyze('''
class _C<T, U> {
T operator[](U u) => throw 'foo';
void operator[]=(U u, T t) {}
}
_f(_C<int, String/*?*/> c, String/*?*/ s) => c[s] += 0;
''');
visitAssignmentTarget(findNode.index('c['), 'int', 'int');
}
Future<void> test_assignmentTarget_indexExpression_dynamic() async {
await analyze('''
_f(dynamic d, int/*?*/ i) => d[i] = 0;
''');
visitAssignmentTarget(findNode.index('d[i]'), null, 'dynamic');
}
Future<void> test_assignmentTarget_indexExpression_simple() async {
await analyze('''
class _C {
int operator[](String s) => 1;
void operator[]=(String s, num n) {}
}
_f(_C c) => c['foo'] = 0;
''');
visitAssignmentTarget(findNode.index('c['), null, 'num');
}
Future<void> test_assignmentTarget_indexExpression_simple_check_lhs() async {
await analyze('''
class _C {
int operator[](String s) => 1;
void operator[]=(String s, num n) {}
}
_f(_C/*?*/ c) => c['foo'] = 0;
''');
visitAssignmentTarget(findNode.index('c['), null, 'num',
changes: {findNode.simple('c['): isNullCheck});
}
Future<void> test_assignmentTarget_indexExpression_simple_check_rhs() async {
await analyze('''
class _C {
int operator[](String/*?*/ s) => 1;
void operator[]=(String/*!*/ s, num n) {}
}
_f(_C c, String/*?*/ s) => c[s] = 0;
''');
visitAssignmentTarget(findNode.index('c['), null, 'num',
changes: {findNode.simple('s]'): isNullCheck});
}
Future<void> test_assignmentTarget_indexExpression_substituted() async {
await analyze('''
class _C<T, U> {
T operator[](U u) => throw 'foo';
void operator[]=(U u, T t) {}
}
_f(_C<int, String> c) => c['foo'] = 1;
''');
visitAssignmentTarget(findNode.index('c['), null, 'int');
}
Future<void>
test_assignmentTarget_indexExpression_substituted_check_rhs() async {
await analyze('''
class _C<T, U> {
T operator[](U u) => throw 'foo';
void operator[]=(U/*!*/ u, T t) {}
}
_f(_C<int, String/*!*/> c, String/*?*/ s) => c[s] = 1;
''');
visitAssignmentTarget(findNode.index('c['), null, 'int',
changes: {findNode.simple('s]'): isNullCheck});
}
Future<void>
test_assignmentTarget_indexExpression_substituted_no_check_rhs() async {
await analyze('''
class _C<T, U> {
T operator[](U u) => throw 'foo';
void operator[]=(U u, T t) {}
}
_f(_C<int, String/*?*/> c, String/*?*/ s) => c[s] = 0;
''');
visitAssignmentTarget(findNode.index('c['), null, 'int');
}
Future<void> test_assignmentTarget_prefixedIdentifier_dynamic() async {
await analyze('''
Object/*!*/ _f(dynamic d) => d.x += 1;
''');
visitAssignmentTarget(findNode.prefixed('d.x'), 'dynamic', 'dynamic');
}
Future<void> test_assignmentTarget_propertyAccess_dynamic() async {
await analyze('''
_f(dynamic d) => (d).x += 1;
''');
visitAssignmentTarget(
findNode.propertyAccess('(d).x'), 'dynamic', 'dynamic');
}
Future<void>
test_assignmentTarget_propertyAccess_dynamic_notCompound() async {
await analyze('''
_f(dynamic d) => (d).x = 1;
''');
visitAssignmentTarget(findNode.propertyAccess('(d).x'), null, 'dynamic');
}
Future<void> test_assignmentTarget_propertyAccess_field_nonNullable() async {
await analyze('''
class _C {
int/*!*/ x = 0;
}
_f(_C c) => (c).x += 1;
''');
visitAssignmentTarget(findNode.propertyAccess('(c).x'), 'int', 'int');
}
Future<void>
test_assignmentTarget_propertyAccess_field_nonNullable_notCompound() async {
await analyze('''
class _C {
int/*!*/ x = 0;
}
_f(_C c) => (c).x = 1;
''');
visitAssignmentTarget(findNode.propertyAccess('(c).x'), null, 'int');
}
Future<void> test_assignmentTarget_propertyAccess_field_nullable() async {
await analyze('''
class _C {
int/*?*/ x = 0;
}
_f(_C c) => (c).x += 1;
''');
visitAssignmentTarget(findNode.propertyAccess('(c).x'), 'int?', 'int?');
}
Future<void> test_assignmentTarget_propertyAccess_getter_nullable() async {
await analyze('''
abstract class _C {
int/*?*/ get x;
void set x(num/*?*/ value);
}
_f(_C c) => (c).x += 1;
''');
visitAssignmentTarget(findNode.propertyAccess('(c).x'), 'int?', 'num?');
}
Future<void>
test_assignmentTarget_propertyAccess_getter_setter_check_lhs() async {
await analyze('''
abstract class _C {
int get x;
void set x(num value);
}
_f(_C/*?*/ c) => (c).x += 1;
''');
visitAssignmentTarget(findNode.propertyAccess('(c).x'), 'int', 'num',
changes: {findNode.parenthesized('(c).x'): isNullCheck});
}
Future<void>
test_assignmentTarget_propertyAccess_getter_setter_nonNullable() async {
await analyze('''
abstract class _C {
int/*!*/ get x;
void set x(num/*!*/ value);
}
_f(_C c) => (c).x += 1;
''');
visitAssignmentTarget(findNode.propertyAccess('(c).x'), 'int', 'num');
}
Future<void> test_assignmentTarget_propertyAccess_nullAware_dynamic() async {
await analyze('''
_f(dynamic d) => d?.x += 1;
''');
visitAssignmentTarget(
findNode.propertyAccess('d?.x'), 'dynamic', 'dynamic');
}
Future<void>
test_assignmentTarget_propertyAccess_nullAware_field_nonNullable() async {
await analyze('''
class _C {
int/*!*/ x = 0;
}
_f(_C/*?*/ c) => c?.x += 1;
''');
visitAssignmentTarget(findNode.propertyAccess('c?.x'), 'int', 'int');
}
Future<void>
test_assignmentTarget_propertyAccess_nullAware_field_nullable() async {
await analyze('''
class _C {
int/*?*/ x = 0;
}
_f(_C/*?*/ c) => c?.x += 1;
''');
visitAssignmentTarget(findNode.propertyAccess('c?.x'), 'int?', 'int?');
}
Future<void>
test_assignmentTarget_propertyAccess_nullAware_getter_setter_nonNullable() async {
await analyze('''
abstract class _C {
int/*!*/ get x;
void set x(num/*!*/ value);
}
_f(_C/*?*/ c) => c?.x += 1;
''');
visitAssignmentTarget(findNode.propertyAccess('c?.x'), 'int', 'num');
}
Future<void>
test_assignmentTarget_propertyAccess_nullAware_getter_setter_nullable() async {
await analyze('''
abstract class _C {
int/*?*/ get x;
void set x(num/*?*/ value);
}
_f(_C/*?*/ c) => c?.x += 1;
''');
visitAssignmentTarget(findNode.propertyAccess('c?.x'), 'int?', 'num?');
}
Future<void>
test_assignmentTarget_propertyAccess_nullAware_substituted() async {
await analyze('''
abstract class _C<T> {
_E<T> get x;
void set x(_D<T> value);
}
class _D<T> implements Iterable<T> {
noSuchMethod(invocation) => super.noSuchMethod(invocation);
_D<T> operator+(int i) => this;
}
class _E<T> extends _D<T> {}
_f(_C<int>/*?*/ c) => c?.x += 1;
''');
visitAssignmentTarget(
findNode.propertyAccess('c?.x'), '_E<int>', '_D<int>');
}
Future<void> test_assignmentTarget_propertyAccess_substituted() async {
await analyze('''
abstract class _C<T> {
_E<T> get x;
void set x(_D<T> value);
}
class _D<T> implements Iterable<T> {
noSuchMethod(invocation) => super.noSuchMethod(invocation);
_D<T> operator+(int i) => this;
}
class _E<T> extends _D<T> {}
_f(_C<int> c) => (c).x += 1;
''');
visitAssignmentTarget(
findNode.propertyAccess('(c).x'), '_E<int>', '_D<int>');
}
Future<void> test_assignmentTarget_simpleIdentifier_field_generic() async {
await analyze('''
abstract class _C<T> {
_C<T> operator+(int i);
}
class _D<T> {
_D(this.x);
_C<T/*!*/>/*!*/ x;
_f() => x += 0;
}
''');
visitAssignmentTarget(findNode.simple('x +='), '_C<T>', '_C<T>');
}
Future<void>
test_assignmentTarget_simpleIdentifier_field_nonNullable() async {
await analyze('''
class _C {
int/*!*/ x;
_f() => x += 0;
}
''');
visitAssignmentTarget(findNode.simple('x '), 'int', 'int');
}
Future<void> test_assignmentTarget_simpleIdentifier_field_nullable() async {
await analyze('''
class _C {
int/*?*/ x;
_f() => x += 0;
}
''');
visitAssignmentTarget(findNode.simple('x '), 'int?', 'int?');
}
Future<void> test_assignmentTarget_simpleIdentifier_getset_generic() async {
await analyze('''
abstract class _C<T> {
_C<T> operator+(int i);
}
abstract class _D<T> extends _C<T> {}
abstract class _E<T> {
_D<T/*!*/>/*!*/ get x;
void set x(_C<T/*!*/>/*!*/ value);
_f() => x += 0;
}
''');
visitAssignmentTarget(findNode.simple('x +='), '_D<T>', '_C<T>');
}
Future<void>
test_assignmentTarget_simpleIdentifier_getset_getterNullable() async {
await analyze('''
class _C {
int/*?*/ get x => 1;
void set x(int/*!*/ value) {}
_f() => x += 0;
}
''');
visitAssignmentTarget(findNode.simple('x +='), 'int?', 'int');
}
Future<void>
test_assignmentTarget_simpleIdentifier_getset_setterNullable() async {
await analyze('''
class _C {
int/*!*/ get x => 1;
void set x(int/*?*/ value) {}
_f() => x += 0;
}
''');
visitAssignmentTarget(findNode.simple('x +='), 'int', 'int?');
}
Future<void>
test_assignmentTarget_simpleIdentifier_localVariable_nonNullable() async {
await analyze('''
_f(int/*!*/ x) => x += 0;
''');
visitAssignmentTarget(findNode.simple('x '), 'int', 'int');
}
Future<void>
test_assignmentTarget_simpleIdentifier_localVariable_nullable() async {
await analyze('''
_f(int/*?*/ x) => x += 0;
''');
visitAssignmentTarget(findNode.simple('x '), 'int?', 'int?');
}
Future<void>
test_assignmentTarget_simpleIdentifier_setter_nonNullable() async {
await analyze('''
class _C {
void set x(int/*!*/ value) {}
_f() => x = 0;
}
''');
visitAssignmentTarget(findNode.simple('x '), null, 'int');
}
Future<void> test_assignmentTarget_simpleIdentifier_setter_nullable() async {
await analyze('''
class _C {
void set x(int/*?*/ value) {}
_f() => x = 0;
}
''');
visitAssignmentTarget(findNode.simple('x '), null, 'int?');
}
Future<void> test_binaryExpression_ampersand_ampersand() async {
await analyze('''
_f(bool x, bool y) => x && y;
''');
visitSubexpression(findNode.binary('&&'), 'bool');
}
Future<void> test_binaryExpression_ampersand_ampersand_flow() async {
await analyze('''
_f(bool/*?*/ x) => x != null && x;
''');
visitSubexpression(findNode.binary('&&'), 'bool');
}
Future<void> test_binaryExpression_ampersand_ampersand_nullChecked() async {
await analyze('''
_f(bool/*?*/ x, bool/*?*/ y) => x && y;
''');
var xRef = findNode.simple('x &&');
var yRef = findNode.simple('y;');
visitSubexpression(findNode.binary('&&'), 'bool',
changes: {xRef: isNullCheck, yRef: isNullCheck});
}
Future<void> test_binaryExpression_bang_eq() async {
await analyze('''
_f(Object/*?*/ x, Object/*?*/ y) => x != y;
''');
visitSubexpression(findNode.binary('!='), 'bool');
}
Future<void> test_binaryExpression_bar_bar() async {
await analyze('''
_f(bool x, bool y) {
return x || y;
}
''');
visitSubexpression(findNode.binary('||'), 'bool');
}
Future<void> test_binaryExpression_bar_bar_flow() async {
await analyze('''
_f(bool/*?*/ x) {
return x == null || x;
}
''');
visitSubexpression(findNode.binary('||'), 'bool');
}
Future<void> test_binaryExpression_bar_bar_nullChecked() async {
await analyze('''
_f(bool/*?*/ x, bool/*?*/ y) {
return x || y;
}
''');
var xRef = findNode.simple('x ||');
var yRef = findNode.simple('y;');
visitSubexpression(findNode.binary('||'), 'bool',
changes: {xRef: isNullCheck, yRef: isNullCheck});
}
Future<void> test_binaryExpression_eq_eq() async {
await analyze('''
_f(Object/*?*/ x, Object/*?*/ y) {
return x == y;
}
''');
visitSubexpression(findNode.binary('=='), 'bool');
}
Future<void> test_binaryExpression_extensionMember_allowsNull() async {
await analyze('''
class C {}
extension E on C/*?*/ {
void operator+(C/*!*/ other) {}
}
f(C/*?*/ c) => c + c;
''');
var binaryExpression = findNode.binary('c + c');
visitSubexpression(binaryExpression, 'void',
changes: {binaryExpression.rightOperand: isNullCheck});
}
Future<void>
test_binaryExpression_extensionMember_allowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*?*/ {
void operator+(C/*!*/ other) {}
}
f(C/*?*/ c) => E(c) + c;
''');
var binaryExpression = findNode.binary('E(c) + c');
visitSubexpression(binaryExpression, 'void',
changes: {binaryExpression.rightOperand: isNullCheck});
}
Future<void> test_binaryExpression_extensionMember_disallowsNull() async {
await analyze('''
class C {}
extension E on C/*!*/ {
void operator+(C/*!*/ other) {}
}
f(C/*?*/ c) => c + c;
''');
var binaryExpression = findNode.binary('c + c');
visitSubexpression(binaryExpression, 'void',
changes: {binaryExpression.leftOperand: isNullCheck});
}
Future<void>
test_binaryExpression_extensionMember_disallowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*!*/ {
void operator+(C/*!*/ other) {}
}
f(C/*?*/ c) => E(c) + c;
''');
var binaryExpression = findNode.binary('E(c) + c');
visitSubexpression(binaryExpression, 'void',
changes: {findNode.simple('c) +'): isNullCheck});
}
Future<void> test_binaryExpression_question_question() async {
await analyze('''
_f(int/*?*/ x, double/*?*/ y) {
return x ?? y;
}
''');
visitSubexpression(findNode.binary('??'), 'num?');
}
Future<void> test_binaryExpression_question_question_flow() async {
await analyze('''
_f(int/*?*/ x, int/*?*/ y) =>
<dynamic>[x ?? (y != null ? 1 : throw 'foo'), y + 1];
''');
// The null check on the RHS of the `??` doesn't promote, because it is not
// guaranteed to execute.
visitSubexpression(findNode.listLiteral('['), 'List<dynamic>',
changes: {findNode.simple('y +'): isNullCheck});
}
Future<void> test_binaryExpression_question_question_nullChecked() async {
await analyze('''
Object/*!*/ _f(int/*?*/ x, double/*?*/ y) {
return x ?? y;
}
''');
var yRef = findNode.simple('y;');
visitSubexpression(findNode.binary('??'), 'num',
changes: {yRef: isNullCheck});
}
Future<void> test_binaryExpression_userDefinable_dynamic() async {
await analyze('''
Object/*!*/ _f(dynamic d, int/*?*/ i) => d + i;
''');
visitSubexpression(findNode.binary('+'), 'dynamic');
}
Future<void> test_binaryExpression_userDefinable_intRules() async {
await analyze('''
_f(int i, int j) => i + j;
''');
visitSubexpression(findNode.binary('+'), 'int');
}
Future<void> test_binaryExpression_userDefinable_simple() async {
await analyze('''
class _C {
int operator+(String s) => 1;
}
_f(_C c) => c + 'foo';
''');
visitSubexpression(findNode.binary('c +'), 'int');
}
Future<void> test_binaryExpression_userDefinable_simple_check_lhs() async {
await analyze('''
class _C {
int operator+(String s) => 1;
}
_f(_C/*?*/ c) => c + 'foo';
''');
visitSubexpression(findNode.binary('c +'), 'int',
changes: {findNode.simple('c +'): isNullCheck});
}
Future<void> test_binaryExpression_userDefinable_simple_check_rhs() async {
await analyze('''
class _C {
int operator+(String/*!*/ s) => 1;
}
_f(_C c, String/*?*/ s) => c + s;
''');
visitSubexpression(findNode.binary('c +'), 'int',
changes: {findNode.simple('s;'): isNullCheck});
}
Future<void> test_binaryExpression_userDefinable_substituted() async {
await analyze('''
class _C<T, U> {
T operator+(U u) => throw 'foo';
}
_f(_C<int, String> c) => c + 'foo';
''');
visitSubexpression(findNode.binary('c +'), 'int');
}
Future<void>
test_binaryExpression_userDefinable_substituted_check_rhs() async {
await analyze('''
class _C<T, U> {
T operator+(U/*!*/ u) => throw 'foo';
}
_f(_C<int, String/*!*/> c, String/*?*/ s) => c + s;
''');
visitSubexpression(findNode.binary('c +'), 'int',
changes: {findNode.simple('s;'): isNullCheck});
}
Future<void>
test_binaryExpression_userDefinable_substituted_no_check_rhs() async {
await analyze('''
class _C<T, U> {
T operator+(U u) => throw 'foo';
}
_f(_C<int, String/*?*/> c, String/*?*/ s) => c + s;
''');
visitSubexpression(findNode.binary('c +'), 'int');
}
Future<void> test_block() async {
await analyze('''
_f(int/*?*/ x, int/*?*/ y) {
{ // block
x + 1;
y + 1;
}
}
''');
visitStatement(findNode.statement('{ // block'), changes: {
findNode.simple('x + 1'): isNullCheck,
findNode.simple('y + 1'): isNullCheck
});
}
Future<void> test_booleanLiteral() async {
await analyze('''
f() => true;
''');
visitSubexpression(findNode.booleanLiteral('true'), 'bool');
}
Future<void> test_compound_assignment_null_shorted_ok() async {
await analyze('''
class C {
int/*!*/ x;
}
_f(C/*?*/ c) {
c?.x += 1;
}
''');
// Even though c?.x is nullable, it should not be a problem to use it as the
// LHS of a compound assignment, because null shorting will ensure that the
// assignment only happens if c is non-null.
var assignment = findNode.assignment('+=');
visitSubexpression(assignment, 'int?');
}
Future<void> test_compound_assignment_nullable_result_bad() async {
await analyze('''
abstract class C {
C/*?*/ operator+(int i);
}
f(C c) {
c += 1;
}
''');
var assignment = findNode.assignment('+=');
visitSubexpression(assignment, 'C?',
changes: {assignment: isBadCombinedType});
}
Future<void> test_compound_assignment_nullable_result_ok() async {
await analyze('''
abstract class C {
C/*?*/ operator+(int i);
}
abstract class D {
void set x(C/*?*/ value);
C/*!*/ get x;
f() {
x += 1;
}
}
''');
var assignment = findNode.assignment('+=');
visitSubexpression(assignment, 'C?');
}
Future<void> test_compound_assignment_nullable_source() async {
await analyze('''
_f(int/*?*/ x) {
x += 1;
}
''');
var assignment = findNode.assignment('+=');
visitSubexpression(assignment, 'int',
changes: {assignment: isNullableSource});
}
Future<void> test_compound_assignment_potentially_nullable_source() async {
await analyze('''
class C<T extends num/*?*/> {
_f(T/*!*/ x) {
x += 1;
}
}
''');
var assignment = findNode.assignment('+=');
visitSubexpression(assignment, 'num',
changes: {assignment: isNullableSource});
}
Future<void> test_compound_assignment_promoted_ok() async {
await analyze('''
abstract class C {
C/*?*/ operator+(int i);
}
f(C/*?*/ x) {
if (x != null) {
x += 1;
}
}
''');
// The compound assignment is ok, because:
// - prior to the assignment, x's value is promoted to non-nullable
// - the nullable return value of operator+ is ok to assign to x, because it
// un-does the promotion.
visitSubexpression(findNode.assignment('+='), 'C?');
}
Future<void> test_conditionalExpression_dead_else_remove() async {
await analyze('_f(int x, int/*?*/ y) => x != null ? x + 1 : y + 1.0;');
var expression = findNode.conditionalExpression('x != null');
visitSubexpression(expression, 'int',
changes: {expression: isConditionalWithKnownValue(true)});
}
Future<void> test_conditionalExpression_dead_else_warn() async {
await analyze('_f(int x, int/*?*/ y) => x != null ? x + 1 : y + 1.0;');
var expression = findNode.conditionalExpression('x != null');
visitSubexpression(expression, 'num', warnOnWeakCode: true, changes: {
expression: isConditionalWithKnownValue(true),
findNode.simple('y +'): isNullCheck
});
}
Future<void> test_conditionalExpression_dead_then_remove() async {
await analyze('_f(int x, int/*?*/ y) => x == null ? y + 1.0 : x + 1;');
var expression = findNode.conditionalExpression('x == null');
visitSubexpression(expression, 'int',
changes: {expression: isConditionalWithKnownValue(false)});
}
Future<void> test_conditionalExpression_dead_then_warn() async {
await analyze('_f(int x, int/*?*/ y) => x == null ? y + 1.0 : x + 1;');
var expression = findNode.conditionalExpression('x == null');
visitSubexpression(expression, 'num', warnOnWeakCode: true, changes: {
expression: isConditionalWithKnownValue(false),
findNode.simple('y +'): isNullCheck
});
}
Future<void> test_conditionalExpression_flow_as_condition() async {
await analyze('''
_f(bool x, int/*?*/ y) => (x ? y != null : y != null) ? y + 1 : 0;
''');
// No explicit check needs to be added to `y + 1`, because both arms of the
// conditional can only be true if `y != null`.
visitSubexpression(findNode.conditionalExpression('y + 1'), 'int');
}
Future<void> test_conditionalExpression_flow_condition() async {
await analyze('''
_f(bool/*?*/ x) => x ? (x && true) : (x && true);
''');
// No explicit check needs to be added to either `x && true`, because there
// is already an explicit null check inserted for the condition.
visitSubexpression(findNode.conditionalExpression('x ?'), 'bool',
changes: {findNode.simple('x ?'): isNullCheck});
}
Future<void> test_conditionalExpression_flow_then_else() async {
await analyze('''
_f(bool x, bool/*?*/ y) => (x ? (y && true) : (y && true)) && y;
''');
// No explicit check needs to be added to the final reference to `y`,
// because null checks are added to the "then" and "else" branches promoting
// y.
visitSubexpression(findNode.binary('&& y'), 'bool', changes: {
findNode.simple('y && true) '): isNullCheck,
findNode.simple('y && true))'): isNullCheck
});
}
Future<void> test_conditionalExpression_lub() async {
await analyze('''
_f(bool b) => b ? 1 : 1.0;
''');
visitSubexpression(findNode.conditionalExpression('1.0'), 'num');
}
Future<void> test_conditionalExpression_throw_promotes() async {
await analyze('''
_f(int/*?*/ x) =>
<dynamic>[(x != null ? 1 : throw 'foo'), x + 1];
''');
// No null check needs to be added to `x + 1`, because there is already an
// explicit null check.
visitSubexpression(findNode.listLiteral('['), 'List<dynamic>');
}
Future<void>
test_defaultFormalParameter_add_required_ignore_decoy_annotation() async {
await analyze('''
const foo = Object();
int _f({@foo int x}) => x + 1;
''');
visitAll(
changes: {findNode.defaultParameter('int x'): isAddRequiredKeyword});
}
Future<void>
test_defaultFormalParameter_add_required_no_because_default() async {
await analyze('''
int _f({int x = 0}) => x + 1;
''');
visitAll();
}
Future<void>
test_defaultFormalParameter_add_required_no_because_nullable() async {
await analyze('''
int _f({int/*?*/ x}) => 1;
''');
visitAll(
changes: {findNode.typeName('int/*?*/ x'): isMakeNullableDueToHint});
}
Future<void>
test_defaultFormalParameter_add_required_no_because_positional() async {
await analyze('''
int _f([int/*!*/ x]) => x + 1;
''');
visitAll(problems: {
findNode.defaultParameter('int/*!*/ x'): {
const NonNullableUnnamedOptionalParameter()
}
});
}
Future<void>
test_defaultFormalParameter_add_required_replace_annotation() async {
// TODO(paulberry): it would be nice to remove the import of `meta` if it's
// no longer needed after the change.
addMetaPackage();
await analyze('''
import 'package:meta/meta.dart';
int _f({@required int x}) => x + 1;
''');
visitAll(changes: {
findNode.annotation('required'): isRequiredAnnotationToRequiredKeyword
});
}
Future<void>
test_defaultFormalParameter_add_required_replace_annotation_nullable() async {
// TODO(paulberry): it would be nice to remove the import of `meta` if it's
// no longer needed after the change.
addMetaPackage();
await analyze('''
import 'package:meta/meta.dart';
void _f({@required int/*?*/ x}) {}
''');
visitAll(changes: {
findNode.annotation('required'): isRequiredAnnotationToRequiredKeyword,
findNode.typeName('int'): isMakeNullableDueToHint,
});
}
Future<void> test_defaultFormalParameter_add_required_yes() async {
await analyze('''
int _f({int x}) => x + 1;
''');
visitAll(
changes: {findNode.defaultParameter('int x'): isAddRequiredKeyword});
}
Future<void> test_doubleLiteral() async {
await analyze('''
f() => 1.0;
''');
visitSubexpression(findNode.doubleLiteral('1.0'), 'double');
}
Future<void> test_enum_ref_index() async {
await analyze('''
enum E { V }
_f(E e) => e.index;
''');
visitSubexpression(findNode.prefixed('e.index'), 'int');
}
Future<void> test_enum_ref_value() async {
await analyze('''
enum E { V }
_f() => E.V;
''');
visitSubexpression(findNode.prefixed('E.V'), 'E');
}
Future<void> test_enum_ref_values() async {
await analyze('''
enum E { V }
_f() => E.values;
''');
visitSubexpression(findNode.prefixed('E.values'), 'List<E>');
}
Future<void> test_expressionStatement() async {
await analyze('''
_f(int/*!*/ x, int/*?*/ y) {
x = y;
}
''');
visitStatement(findNode.statement('x = y'),
changes: {findNode.simple('y;'): isNullCheck});
}
Future<void> test_firstWhere_transform() async {
await analyze('''
_f(Iterable<int> x) => x.firstWhere((n) => n.isEven, orElse: () => null);
''');
var methodInvocation = findNode.methodInvocation('firstWhere');
var functionExpression = findNode.functionExpression('() => null');
var fixBuilder = visitSubexpression(methodInvocation, 'int?', changes: {
methodInvocation.methodName: isMethodNameChange('firstWhereOrNull'),
methodInvocation.argumentList:
isDropArgument({functionExpression.parent: anything}),
// Behavior of the null literal doesn't matter because it's being dropped.
findNode.nullLiteral('null'): anything
});
expect(fixBuilder.needsIterableExtension, true);
}
Future<void> test_functionExpressionInvocation_dynamic() async {
await analyze('''
_f(dynamic d) => d();
''');
visitSubexpression(findNode.functionExpressionInvocation('d('), 'dynamic');
}
Future<void>
test_functionExpressionInvocation_extensionMember_allowsNull() async {
await analyze('''
class C {}
extension E on C/*?*/ {
void call() {}
}
f(C/*?*/ c) => c();
''');
var functoinExpressionInvocation =
findNode.functionExpressionInvocation('c()');
visitSubexpression(functoinExpressionInvocation, 'void');
}
Future<void>
test_functionExpressionInvocation_extensionMember_allowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*?*/ {
void call() {}
}
f(C/*?*/ c) => E(c)();
''');
var functoinExpressionInvocation =
findNode.functionExpressionInvocation('E(c)()');
visitSubexpression(functoinExpressionInvocation, 'void');
}
Future<void>
test_functionExpressionInvocation_extensionMember_disallowsNull() async {
await analyze('''
class C {}
extension E on C/*!*/ {
void call() {}
}
f(C/*?*/ c) => c();
''');
var functionExpressionInvocation =
findNode.functionExpressionInvocation('c()');
visitSubexpression(functionExpressionInvocation, 'void',
changes: {functionExpressionInvocation.function: isNullCheck});
}
Future<void>
test_functionExpressionInvocation_extensionMember_disallowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*!*/ {
void call() {}
}
f(C/*?*/ c) => E(c)();
''');
var functionExpressionInvocation =
findNode.functionExpressionInvocation('E(c)()');
visitSubexpression(functionExpressionInvocation, 'void',
changes: {findNode.simple('c)()'): isNullCheck});
}
Future<void> test_functionExpressionInvocation_function_checked() async {
await analyze('''
_f(Function/*?*/ func) => func();
''');
visitSubexpression(
findNode.functionExpressionInvocation('func('), 'dynamic',
changes: {findNode.simple('func()'): isNullCheck});
}
Future<void> test_functionExpressionInvocation_getter() async {
await analyze('''
abstract class _C {
int Function() get f;
}
_f(_C c) => (c.f)();
''');
visitSubexpression(findNode.functionExpressionInvocation('c.f'), 'int');
}
Future<void>
test_functionExpressionInvocation_getter_looksLikeMethodCall() async {
await analyze('''
abstract class _C {
int Function() get f;
}
_f(_C c) => c.f();
''');
visitSubexpression(findNode.functionExpressionInvocation('c.f'), 'int');
}
Future<void> test_functionExpressionInvocation_getter_nullChecked() async {
await analyze('''
abstract class _C {
int Function()/*?*/ get f;
}
_f(_C c) => (c.f)();
''');
visitSubexpression(findNode.functionExpressionInvocation('c.f'), 'int',
changes: {findNode.parenthesized('c.f'): isNullCheck});
}
Future<void>
test_functionExpressionInvocation_getter_nullChecked_looksLikeMethodCall() async {
await analyze('''
abstract class _C {
int Function()/*?*/ get f;
}
_f(_C c) => c.f();
''');
visitSubexpression(findNode.functionExpressionInvocation('c.f'), 'int',
changes: {findNode.propertyAccess('c.f'): isNullCheck});
}
Future<void> test_genericFunctionType_nonNullable() async {
await analyze('''
void _f() {
void Function() x = _f;
}
''');
var genericFunctionType = findNode.genericFunctionType('Function');
visitTypeAnnotation(genericFunctionType, 'void Function()',
informative: {genericFunctionType: isExplainNonNullable});
}
Future<void> test_genericFunctionType_nonNullable_by_context() async {
await analyze('''
typedef F = void Function();
''');
var genericFunctionType = findNode.genericFunctionType('Function');
visitTypeAnnotation(genericFunctionType, 'void Function()',
informative: isEmpty);
}
Future<void> test_genericFunctionType_nullable() async {
await analyze('''
void _f() {
void Function() x = null;
}
''');
var genericFunctionTypeAnnotation =
findNode.genericFunctionType('Function');
visitTypeAnnotation(genericFunctionTypeAnnotation, 'void Function()?',
changes: {genericFunctionTypeAnnotation: isMakeNullable});
}
Future<void> test_genericFunctionType_nullable_arg() async {
await analyze('''
void Function(int/*?*/) _f() {
void Function(int) x = _g;
return x;
}
void _g(int/*?*/ x) {}
''');
var intTypeAnnotation = findNode.typeName('int)');
var genericFunctionTypeAnnotation =
findNode.genericFunctionType('Function(int)');
visitTypeAnnotation(genericFunctionTypeAnnotation, 'void Function(int?)',
changes: {intTypeAnnotation: isMakeNullable});
}
Future<void> test_genericFunctionType_nullable_return() async {
await analyze('''
void _f() {
int Function() x = _g;
}
int/*?*/ _g() => null;
''');
var intTypeAnnotation = findNode.typeName('int Function');
var genericFunctionTypeAnnotation =
findNode.genericFunctionType('Function');
visitTypeAnnotation(genericFunctionTypeAnnotation, 'int? Function()',
changes: {intTypeAnnotation: isMakeNullable});
}
Future<void> test_ifStatement_dead_else() async {
await analyze('''
_f(int x, int/*?*/ y) {
if (x != null) {
print(x + 1);
} else {
print(y + 1);
}
}
''');
var ifStatement = findNode.statement('if');
visitStatement(ifStatement,
changes: {ifStatement: isConditionalWithKnownValue(true)});
}
Future<void> test_ifStatement_dead_then() async {
await analyze('''
_f(int x, int/*?*/ y) {
if (x == null) {
print(y + 1);
} else {
print(x + 1);
}
}
''');
var ifStatement = findNode.statement('if');
visitStatement(ifStatement,
changes: {ifStatement: isConditionalWithKnownValue(false)});
}
Future<void> test_ifStatement_flow_promote_in_else() async {
await analyze('''
_f(int/*?*/ x) {
if (x == null) {
x + 1;
} else {
x + 2;
}
}
''');
visitStatement(findNode.statement('if'),
changes: {findNode.simple('x + 1'): isNullCheck});
}
Future<void> test_ifStatement_flow_promote_in_then() async {
await analyze('''
_f(int/*?*/ x) {
if (x != null) {
x + 1;
} else {
x + 2;
}
}
''');
visitStatement(findNode.statement('if'),
changes: {findNode.simple('x + 2'): isNullCheck});
}
Future<void> test_ifStatement_flow_promote_in_then_no_else() async {
await analyze('''
_f(int/*?*/ x) {
if (x != null) {
x + 1;
}
}
''');
visitStatement(findNode.statement('if'));
}
Future<void> test_implicit_downcast() async {
await analyze('int f(num x) => x;');
var xRef = findNode.simple('x;');
visitSubexpression(xRef, 'int', changes: {
xRef: isNodeChangeForExpression.havingIndroduceAsWithInfo(
'int',
isInfo(NullabilityFixDescription.downcastExpression,
{FixReasonTarget.root: isEdge}))
});
}
Future<void> test_import_IterableExtension_already_imported_add_show() async {
addPackageFile('collection', 'collection.dart', 'class PriorityQueue {}');
await analyze('''
import 'package:collection/collection.dart' show PriorityQueue;
main() {}
''');
visitAll(injectNeedsIterableExtension: true, changes: {
findNode.import('package:collection').combinators[0]:
isAddShowOfIterableExtension
});
}
Future<void> test_import_IterableExtension_already_imported_all() async {
addPackageFile('collection', 'collection.dart', '');
await analyze('''
import 'package:collection/collection.dart';
main() {}
''');
visitAll(injectNeedsIterableExtension: true, changes: {});
}
Future<void>
test_import_IterableExtension_already_imported_and_shown() async {
addPackageFile('collection', 'collection.dart',
'extension IterableExtension<T> on Iterable<T> {}');
await analyze('''
import 'package:collection/collection.dart' show IterableExtension;
main() {}
''');
visitAll(injectNeedsIterableExtension: true, changes: {});
}
Future<void> test_import_IterableExtension_already_imported_prefixed() async {
addPackageFile('collection', 'collection.dart', '');
await analyze('''
import 'package:collection/collection.dart' as c;
main() {}
''');
visitAll(
injectNeedsIterableExtension: true,
changes: {findNode.unit: isAddImportOfIterableExtension});
}
Future<void> test_import_IterableExtension_other_import() async {
addPackageFile(
'foo', 'foo.dart', 'extension IterableExtension<T> on Iterable<T> {}');
await analyze('''
import 'package:foo/foo.dart' show IterableExtension;
main() {}
''');
visitAll(
injectNeedsIterableExtension: true,
changes: {findNode.unit: isAddImportOfIterableExtension});
}
Future<void> test_import_IterableExtension_simple() async {
await analyze('''
main() {}
''');
visitAll(
injectNeedsIterableExtension: true,
changes: {findNode.unit: isAddImportOfIterableExtension});
}
Future<void> test_indexExpression_dynamic() async {
await analyze('''
Object/*!*/ _f(dynamic d, int/*?*/ i) => d[i];
''');
visitSubexpression(findNode.index('d[i]'), 'dynamic');
}
Future<void> test_indexExpression_extensionMember_allowsNull() async {
await analyze('''
class C {}
extension E on C/*?*/ {
int operator[](int index) => 0;
}
f(C/*?*/ c) => c[0];
''');
var indexExpression = findNode.index('c[0]');
visitSubexpression(indexExpression, 'int');
}
Future<void>
test_indexExpression_extensionMember_allowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*?*/ {
int operator[](int index) => 0;
}
f(C/*?*/ c) => E(c)[0];
''');
var indexExpression = findNode.index('E(c)[0]');
visitSubexpression(indexExpression, 'int');
}
Future<void> test_indexExpression_extensionMember_disallowsNull() async {
await analyze('''
class C {}
extension E on C/*!*/ {
int operator[](int index) => 0;
}
f(C/*?*/ c) => c[0];
''');
var indexExpression = findNode.index('c[0]');
visitSubexpression(indexExpression, 'int',
changes: {indexExpression.target: isNullCheck});
}
Future<void>
test_indexExpression_extensionMember_disallowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*!*/ {
int operator[](int index) => 0;
}
f(C/*?*/ c) => E(c)[0];
''');
var indexExpression = findNode.index('E(c)[0]');
visitSubexpression(indexExpression, 'int',
changes: {findNode.simple('c)[0]'): isNullCheck});
}
Future<void> test_indexExpression_simple() async {
await analyze('''
class _C {
int operator[](String s) => 1;
}
_f(_C c) => c['foo'];
''');
visitSubexpression(findNode.index('c['), 'int');
}
Future<void> test_indexExpression_simple_check_lhs() async {
await analyze('''
class _C {
int operator[](String s) => 1;
}
_f(_C/*?*/ c) => c['foo'];
''');
visitSubexpression(findNode.index('c['), 'int',
changes: {findNode.simple('c['): isNullCheck});
}
Future<void> test_indexExpression_simple_check_rhs() async {
await analyze('''
class _C {
int operator[](String/*!*/ s) => 1;
}
_f(_C c, String/*?*/ s) => c[s];
''');
visitSubexpression(findNode.index('c['), 'int',
changes: {findNode.simple('s]'): isNullCheck});
}
Future<void> test_indexExpression_substituted() async {
await analyze('''
class _C<T, U> {
T operator[](U u) => throw 'foo';
}
_f(_C<int, String> c) => c['foo'];
''');
visitSubexpression(findNode.index('c['), 'int');
}
Future<void> test_indexExpression_substituted_check_rhs() async {
await analyze('''
class _C<T, U> {
T operator[](U/*!*/ u) => throw 'foo';
}
_f(_C<int, String/*!*/> c, String/*?*/ s) => c[s];
''');
visitSubexpression(findNode.index('c['), 'int',
changes: {findNode.simple('s]'): isNullCheck});
}
Future<void> test_indexExpression_substituted_no_check_rhs() async {
await analyze('''
class _C<T, U> {
T operator[](U u) => throw 'foo';
}
_f(_C<int, String/*?*/> c, String/*?*/ s) => c[s];
''');
visitSubexpression(findNode.index('c['), 'int');
}
Future<void> test_integerLiteral() async {
await analyze('''
f() => 1;
''');
visitSubexpression(findNode.integerLiteral('1'), 'int');
}
Future<void> test_list_ifElement_alive() async {
await analyze('''
_f(int x, bool b, int/*?*/ y) => [if (b) h(y) else g(y)];
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.listLiteral('['), 'List<num>', changes: {
findNode.simple('y) else'): isNullCheck,
findNode.simple('y)]'): isNullCheck
});
}
Future<void> test_list_ifElement_alive_with_null_check() async {
await analyze('''
_f(int x, bool/*?*/ b, int/*?*/ y) => [if (b == null) h(y) else g(y)];
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.listLiteral('['), 'List<num>', changes: {
findNode.simple('y) else'): isNullCheck,
findNode.simple('y)]'): isNullCheck
});
}
Future<void> test_list_ifElement_dead_else() async {
await analyze('''
_f(int x, int/*?*/ y) => [if (x != null) g(y) else h(y)];
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.listLiteral('['), 'List<int>', changes: {
findNode.ifElement('null'): isConditionalWithKnownValue(true),
findNode.simple('y) else'): isNullCheck
});
}
Future<void> test_list_ifElement_dead_else_no_else() async {
await analyze('''
_f(int x, int/*?*/ y) => [if (x != null) g(y)];
int/*!*/ g(int/*!*/ y) => y;
''');
visitSubexpression(findNode.listLiteral('['), 'List<int>', changes: {
findNode.ifElement('null'): isConditionalWithKnownValue(true),
findNode.simple('y)]'): isNullCheck
});
}
Future<void> test_list_ifElement_dead_then() async {
await analyze('''
_f(int x, int/*?*/ y) => [if (x == null) h(y) else g(y)];
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.listLiteral('['), 'List<int>', changes: {
findNode.ifElement('null'): isConditionalWithKnownValue(false),
findNode.simple('y)]'): isNullCheck
});
}
Future<void> test_list_ifElement_dead_then_no_else() async {
// TODO(paulberry): rather than infer the type to be List<dynamic>,
// FixBuilder should add an explicit type argument to ensure that it is
// still List<int>.
await analyze('''
_f(int x, int/*?*/ y) => [if (x == null) h(y)];
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.listLiteral('['), 'List<dynamic>', changes: {
findNode.ifElement('null'): isConditionalWithKnownValue(false)
});
}
Future<void> test_list_make_explicit_type_nullable() async {
await analyze('_f() => <int>[null];');
// The `null` should be analyzed with a context type of `int?`, so it should
// not be null-checked.
visitSubexpression(findNode.listLiteral('['), 'List<int?>',
changes: {findNode.typeAnnotation('int'): isMakeNullable});
}
Future<void> test_list_unchanged() async {
await analyze('_f(int x) => [x];');
visitSubexpression(findNode.listLiteral('['), 'List<int>');
}
Future<void> test_listLiteral_typed() async {
await analyze('''
_f() => <int>[];
''');
visitSubexpression(findNode.listLiteral('['), 'List<int>');
}
Future<void> test_listLiteral_typed_visit_contents() async {
await analyze('''
_f(int/*?*/ x) => <int/*!*/>[x];
''');
visitSubexpression(findNode.listLiteral('['), 'List<int>',
changes: {findNode.simple('x]'): isNullCheck});
}
Future<void> test_map_ifElement_alive() async {
await analyze('''
_f(int x, bool b, int/*?*/ y) => {if (b) 0: h(y) else 0: g(y)};
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.setOrMapLiteral('{'), 'Map<int, num>',
changes: {
findNode.simple('y) else'): isNullCheck,
findNode.simple('y)}'): isNullCheck
});
}
Future<void> test_map_ifElement_alive_with_null_check() async {
await analyze('''
_f(int x, bool/*?*/ b, int/*?*/ y) => {if (b == null) 0: h(y) else 0: g(y)};
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.setOrMapLiteral('{'), 'Map<int, num>',
changes: {
findNode.simple('y) else'): isNullCheck,
findNode.simple('y)}'): isNullCheck
});
}
Future<void> test_map_ifElement_dead_else() async {
await analyze('''
_f(int x, int/*?*/ y) => {if (x != null) 0: g(y) else 0: h(y)};
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.setOrMapLiteral('{'), 'Map<int, int>',
changes: {
findNode.ifElement('null'): isConditionalWithKnownValue(true),
findNode.simple('y) else'): isNullCheck
});
}
Future<void> test_map_ifElement_dead_else_no_else() async {
await analyze('''
_f(int x, int/*?*/ y) => {if (x != null) 0: g(y)};
int/*!*/ g(int/*!*/ y) => y;
''');
visitSubexpression(findNode.setOrMapLiteral('{'), 'Map<int, int>',
changes: {
findNode.ifElement('null'): isConditionalWithKnownValue(true),
findNode.simple('y)}'): isNullCheck
});
}
Future<void> test_map_ifElement_dead_then() async {
await analyze('''
_f(int x, int/*?*/ y) => {if (x == null) 0: h(y) else 0: g(y)};
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.setOrMapLiteral('{'), 'Map<int, int>',
changes: {
findNode.ifElement('null'): isConditionalWithKnownValue(false),
findNode.simple('y)}'): isNullCheck
});
}
Future<void> test_map_ifElement_dead_then_no_else() async {
// TODO(paulberry): rather than infer the type to be Map<dynamic, dynamic>,
// FixBuilder should add an explicit type argument to ensure that it is
// still Map<int, int>.
await analyze('''
_f(int x, int/*?*/ y) => {if (x == null) 0: h(y)};
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.setOrMapLiteral('{'), 'Map<dynamic, dynamic>',
changes: {
findNode.ifElement('null'): isConditionalWithKnownValue(false)
});
}
Future<void> test_map_make_explicit_key_type_nullable() async {
await analyze('_f() => <int, double>{null: 0.0};');
// The `null` should be analyzed with a context type of `int?`, so it should
// not be null-checked.
visitSubexpression(findNode.setOrMapLiteral('{'), 'Map<int?, double>',
changes: {findNode.typeAnnotation('int'): isMakeNullable});
}
Future<void> test_map_make_explicit_value_type_nullable() async {
await analyze('_f() => <double, int>{0.0: null};');
// The `null` should be analyzed with a context type of `int?`, so it should
// not be null-checked.
visitSubexpression(findNode.setOrMapLiteral('{'), 'Map<double, int?>',
changes: {findNode.typeAnnotation('int'): isMakeNullable});
}
Future<void> test_methodInvocation_dynamic() async {
await analyze('''
Object/*!*/ _f(dynamic d) => d.f();
''');
visitSubexpression(findNode.methodInvocation('d.f'), 'dynamic');
}
Future<void> test_methodInvocation_extensionMember_allowsNull() async {
await analyze('''
class C {}
extension E on C/*?*/ {
void foo() {}
}
f(C/*?*/ c) => c.foo();
''');
var methodInvocation = findNode.methodInvocation('c.foo');
visitSubexpression(methodInvocation, 'void');
}
Future<void>
test_methodInvocation_extensionMember_allowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*?*/ {
void foo() {}
}
f(C/*?*/ c) => E(c).foo();
''');
var methodInvocation = findNode.methodInvocation('E(c).foo');
visitSubexpression(methodInvocation, 'void');
}
Future<void> test_methodInvocation_extensionMember_disallowsNull() async {
await analyze('''
class C {}
extension E on C/*!*/ {
void foo() {}
}
f(C/*?*/ c) => c.foo();
''');
var methodInvocation = findNode.methodInvocation('c.foo');
visitSubexpression(methodInvocation, 'void',
changes: {methodInvocation.target: isNullCheck});
}
Future<void>
test_methodInvocation_extensionMember_disallowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*!*/ {
void foo() {}
}
f(C/*?*/ c) => E(c).foo();
''');
var methodInvocation = findNode.methodInvocation('E(c).foo');
visitSubexpression(methodInvocation, 'void',
changes: {findNode.simple('c).foo'): isNullCheck});
}
Future<void> test_methodInvocation_function_call_nullCheck() async {
await analyze('''
f(void Function()/*?*/ x) => x.call();
''');
var methodInvocation = findNode.methodInvocation('x.call');
visitSubexpression(methodInvocation, 'void',
changes: {methodInvocation.target: isNullCheck});
}
Future<void> test_methodInvocation_namedParameter() async {
await analyze('''
abstract class _C {
int f({int/*!*/ x});
}
_f(_C c, int/*?*/ y) => c.f(x: y);
''');
visitSubexpression(findNode.methodInvocation('c.f'), 'int',
changes: {findNode.simple('y);'): isNullCheck});
}
Future<void> test_methodInvocation_ordinaryParameter() async {
await analyze('''
abstract class _C {
int f(int/*!*/ x);
}
_f(_C c, int/*?*/ y) => c.f(y);
''');
visitSubexpression(findNode.methodInvocation('c.f'), 'int',
changes: {findNode.simple('y);'): isNullCheck});
}
Future<void> test_methodInvocation_return_nonNullable() async {
await analyze('''
abstract class _C {
int f();
}
_f(_C c) => c.f();
''');
visitSubexpression(findNode.methodInvocation('c.f'), 'int');
}
Future<void> test_methodInvocation_return_nonNullable_check_target() async {
await analyze('''
abstract class _C {
int f();
}
_f(_C/*?*/ c) => c.f();
''');
visitSubexpression(findNode.methodInvocation('c.f'), 'int',
changes: {findNode.simple('c.f'): isNullCheck});
}
Future<void> test_methodInvocation_return_nonNullable_nullAware() async {
await analyze('''
abstract class _C {
int f();
}
_f(_C/*?*/ c) => c?.f();
''');
visitSubexpression(findNode.methodInvocation('c?.f'), 'int?');
}
Future<void> test_methodInvocation_return_nullable() async {
await analyze('''
abstract class _C {
int/*?*/ f();
}
_f(_C c) => c.f();
''');
visitSubexpression(findNode.methodInvocation('c.f'), 'int?');
}
Future<void> test_methodInvocation_static() async {
await analyze('''
_f() => _C.g();
class _C {
static int g() => 1;
}
''');
visitSubexpression(findNode.methodInvocation('_C.g();'), 'int');
}
Future<void> test_methodInvocation_topLevel() async {
await analyze('''
_f() => _g();
int _g() => 1;
''');
visitSubexpression(findNode.methodInvocation('_g();'), 'int');
}
Future<void> test_methodInvocation_toString() async {
await analyze('''
abstract class _C {}
_f(_C/*?*/ c) => c.toString();
''');
visitSubexpression(findNode.methodInvocation('c.toString'), 'String');
}
Future<void> test_null_aware_assignment_non_nullable_source() async {
await analyze('''
abstract class C {
int/*!*/ f();
g(int/*!*/ x) {
x ??= f();
}
}
''');
var assignment = findNode.assignment('??=');
visitSubexpression(assignment, 'int',
changes: {assignment: isWeakNullAwareAssignment});
}
Future<void> test_null_aware_assignment_nullable_rhs_needs_check() async {
await analyze('''
abstract class C {
void set x(int/*!*/ value);
int/*?*/ get x;
int/*?*/ f();
g() {
x ??= f();
}
}
''');
var assignment = findNode.assignment('??=');
visitSubexpression(assignment, 'int',
changes: {assignment.rightHandSide: isNullCheck});
}
Future<void> test_null_aware_assignment_nullable_rhs_ok() async {
await analyze('''
abstract class C {
int/*?*/ f();
g(int/*?*/ x) {
x ??= f();
}
}
''');
var assignment = findNode.assignment('??=');
visitSubexpression(assignment, 'int?');
}
Future<void> test_nullable_value_in_null_context() async {
await analyze('int/*!*/ f(int/*?*/ i) => i;');
var iRef = findNode.simple('i;');
visitSubexpression(iRef, 'int', changes: {
iRef: isNodeChangeForExpression.havingNullCheckWithInfo(isInfo(
NullabilityFixDescription.checkExpression,
{FixReasonTarget.root: TypeMatcher<NullabilityEdge>()}))
});
}
Future<void> test_nullAssertion_promotes() async {
await analyze('''
_f(bool/*?*/ x) => x && x;
''');
// Only the first `x` is null-checked because thereafter, the type of `x` is
// promoted to `bool`.
visitSubexpression(findNode.binary('&&'), 'bool',
changes: {findNode.simple('x &&'): isNullCheck});
}
Future<void> test_nullLiteral() async {
await analyze('''
f() => null;
''');
visitSubexpression(findNode.nullLiteral('null'), 'Null');
}
Future<void> test_nullLiteral_hinted() async {
await analyze('''
int/*!*/ f() => null/*!*/;
''');
var literal = findNode.nullLiteral('null');
// Normally we would leave the null literal alone and add an informative
// comment saying there's no valid migration for it. But since the user
// specifically hinted that `!` should be added, we respect that.
visitSubexpression(literal, 'Never', changes: {
literal: isNodeChangeForExpression.havingNullCheckWithInfo(isInfo(
NullabilityFixDescription.checkExpressionDueToHint,
{FixReasonTarget.root: TypeMatcher<FixReason_NullCheckHint>()}))
});
}
Future<void> test_nullLiteral_noValidMigration() async {
await analyze('''
int/*!*/ f() => null;
''');
var literal = findNode.nullLiteral('null');
// Note: in spite of the fact that we leave the literal as `null`, we
// analyze it as though it has type `Never`, because it's in a context where
// `null` doesn't work.
visitSubexpression(literal, 'Never', changes: {
literal: isNodeChangeForExpression.havingNoValidMigrationWithInfo(isInfo(
NullabilityFixDescription.noValidMigrationForNull,
{FixReasonTarget.root: TypeMatcher<NullabilityEdge>()}))
});
}
Future<void> test_parenthesizedExpression() async {
await analyze('''
f() => (1);
''');
visitSubexpression(findNode.integerLiteral('1'), 'int');
}
Future<void> test_parenthesizedExpression_flow() async {
await analyze('''
_f(bool/*?*/ x) => ((x) != (null)) && x;
''');
visitSubexpression(findNode.binary('&&'), 'bool');
}
Future<void> test_post_decrement_int_behavior() async {
await analyze('''
_f(int x) => x--;
''');
// It's not a problem that int.operator- returns `num` (which is not
// assignable to `int`) because the value implicitly passed to operator- has
// type `int`, so the static type of the result is `int`.
visitSubexpression(findNode.postfix('--'), 'int');
}
Future<void> test_post_increment_int_behavior() async {
await analyze('''
_f(int x) => x++;
''');
// It's not a problem that int.operator+ returns `num` (which is not
// assignable to `int`) because the value implicitly passed to operator- has
// type `int`, so the static type of the result is `int`.
visitSubexpression(findNode.postfix('++'), 'int');
}
Future<void> test_post_increment_null_shorted_ok() async {
await analyze('''
class C {
int/*!*/ x;
}
_f(C/*?*/ c) {
c?.x++;
}
''');
// Even though c?.x is nullable, it should not be a problem to use it as the
// target of a post-increment, because null shorting will ensure that the
// increment only happens if c is non-null.
var increment = findNode.postfix('++');
visitSubexpression(increment, 'int?');
}
Future<void> test_post_increment_nullable_result_bad() async {
await analyze('''
abstract class C {
C/*?*/ operator+(int i);
}
f(C c) {
c++;
}
''');
var increment = findNode.postfix('++');
visitSubexpression(increment, 'C', changes: {increment: isBadCombinedType});
}
Future<void> test_post_increment_nullable_result_ok() async {
await analyze('''
abstract class C {
C/*?*/ operator+(int i);
}
abstract class D {
void set x(C/*?*/ value);
C/*!*/ get x;
f() {
x++;
}
}
''');
var increment = findNode.postfix('++');
visitSubexpression(increment, 'C');
}
Future<void> test_post_increment_nullable_source() async {
await analyze('''
_f(int/*?*/ x) {
x++;
}
''');
var increment = findNode.postfix('++');
visitSubexpression(increment, 'int?',
changes: {increment: isNullableSource});
}
Future<void> test_post_increment_potentially_nullable_source() async {
await analyze('''
class C<T extends num/*?*/> {
_f(T/*!*/ x) {
x++;
}
}
''');
var increment = findNode.postfix('++');
visitSubexpression(increment, 'T', changes: {increment: isNullableSource});
}
Future<void> test_post_increment_promoted_ok() async {
await analyze('''
abstract class C {
C/*?*/ operator+(int i);
}
f(C/*?*/ x) {
if (x != null) {
x++;
}
}
''');
// The increment is ok, because:
// - prior to the increment, x's value is promoted to non-nullable
// - the nullable return value of operator+ is ok to assign to x, because it
// un-does the promotion.
visitSubexpression(findNode.postfix('++'), 'C');
}
Future<void> test_postfixExpression_combined_nullable_noProblem() async {
await analyze('''
abstract class _C {
_D/*?*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
abstract class _E {
_C/*!*/ get x;
void set x(_C/*?*/ value);
f() => x++;
}
''');
visitSubexpression(findNode.postfix('++'), '_C');
}
Future<void>
test_postfixExpression_combined_nullable_noProblem_dynamic() async {
await analyze('''
abstract class _E {
dynamic get x;
void set x(Object/*!*/ value);
f() => x++;
}
''');
visitSubexpression(findNode.postfix('++'), 'dynamic');
}
@FailingTest(reason: 'TODO(paulberry)')
Future<void> test_postfixExpression_combined_nullable_problem() async {
await analyze('''
abstract class _C {
_D/*?*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
abstract class _E {
_C/*!*/ get x;
void set x(_C/*!*/ value);
f() => x++;
}
''');
var postfix = findNode.postfix('++');
visitSubexpression(postfix, '_C', problems: {
postfix: {const CompoundAssignmentCombinedNullable()}
});
}
Future<void> test_postfixExpression_decrement_undoes_promotion() async {
await analyze('''
abstract class _C {
_C/*?*/ operator-(int value);
}
_f(_C/*?*/ c) { // method
if (c != null) {
c--;
_g(c);
}
}
_g(_C/*!*/ c) {}
''');
visitStatement(findNode.block('{ // method'),
changes: {findNode.simple('c);'): isNullCheck});
}
Future<void> test_postfixExpression_dynamic() async {
await analyze('''
_f(dynamic x) => x++;
''');
visitSubexpression(findNode.postfix('++'), 'dynamic');
}
Future<void> test_postfixExpression_increment_undoes_promotion() async {
await analyze('''
abstract class _C {
_C/*?*/ operator+(int value);
}
_f(_C/*?*/ c) { // method
if (c != null) {
c++;
_g(c);
}
}
_g(_C/*!*/ c) {}
''');
visitStatement(findNode.block('{ // method'),
changes: {findNode.simple('c);'): isNullCheck});
}
@FailingTest(reason: 'TODO(paulberry)')
Future<void> test_postfixExpression_lhs_nullable_problem() async {
await analyze('''
abstract class _C {
_D/*!*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
abstract class _E {
_C/*?*/ get x;
void set x(_C/*?*/ value);
f() => x++;
}
''');
var postfix = findNode.postfix('++');
visitSubexpression(postfix, '_C?', problems: {
postfix: {const CompoundAssignmentReadNullable()}
});
}
Future<void> test_postfixExpression_rhs_nonNullable() async {
await analyze('''
abstract class _C {
_D/*!*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
_f(_C/*!*/ x) => x++;
''');
visitSubexpression(findNode.postfix('++'), '_C');
}
Future<void> test_pre_decrement_int_behavior() async {
await analyze('''
_f(int x) => --x;
''');
// It's not a problem that int.operator- returns `num` (which is not
// assignable to `int`) because the value implicitly passed to operator- has
// type `int`, so the static type of the result is `int`.
visitSubexpression(findNode.prefix('--'), 'int');
}
Future<void> test_pre_increment_int_behavior() async {
await analyze('''
_f(int x) => ++x;
''');
// It's not a problem that int.operator+ returns `num` (which is not
// assignable to `int`) because the value implicitly passed to operator- has
// type `int`, so the static type of the result is `int`.
visitSubexpression(findNode.prefix('++'), 'int');
}
Future<void> test_pre_increment_null_shorted_ok() async {
await analyze('''
class C {
int/*!*/ x;
}
_f(C/*?*/ c) {
++c?.x;
}
''');
// Even though c?.x is nullable, it should not be a problem to use it as the
// target of a pre-increment, because null shorting will ensure that the
// increment only happens if c is non-null.
var increment = findNode.prefix('++');
visitSubexpression(increment, 'int?');
}
Future<void> test_pre_increment_nullable_result_bad() async {
await analyze('''
abstract class C {
C/*?*/ operator+(int i);
}
f(C c) {
++c;
}
''');
var increment = findNode.prefix('++');
visitSubexpression(increment, 'C?',
changes: {increment: isBadCombinedType});
}
Future<void> test_pre_increment_nullable_result_ok() async {
await analyze('''
abstract class C {
C/*?*/ operator+(int i);
}
abstract class D {
void set x(C/*?*/ value);
C/*!*/ get x;
f() {
++x;
}
}
''');
var increment = findNode.prefix('++');
visitSubexpression(increment, 'C?');
}
Future<void> test_pre_increment_nullable_source() async {
await analyze('''
_f(int/*?*/ x) {
++x;
}
''');
var increment = findNode.prefix('++');
visitSubexpression(increment, 'int',
changes: {increment: isNullableSource});
}
Future<void> test_pre_increment_potentially_nullable_source() async {
await analyze('''
class C<T extends num/*?*/> {
_f(T/*!*/ x) {
++x;
}
}
''');
var increment = findNode.prefix('++');
visitSubexpression(increment, 'num',
changes: {increment: isNullableSource});
}
Future<void> test_pre_increment_promoted_ok() async {
await analyze('''
abstract class C {
C/*?*/ operator+(int i);
}
f(C/*?*/ x) {
if (x != null) {
++x;
}
}
''');
// The increment is ok, because:
// - prior to the increment, x's value is promoted to non-nullable
// - the nullable return value of operator+ is ok to assign to x, because it
// un-does the promotion.
visitSubexpression(findNode.prefix('++'), 'C?');
}
Future<void> test_prefixedIdentifier_dynamic() async {
await analyze('''
Object/*!*/ _f(dynamic d) => d.x;
''');
visitSubexpression(findNode.prefixed('d.x'), 'dynamic');
}
Future<void> test_prefixedIdentifier_extensionMember_allowsNull() async {
await analyze('''
class C {}
extension E on C/*?*/ {
int get foo => 0;
}
f(C/*?*/ c) => c.foo;
''');
var prefixedIdentifier = findNode.prefixed('c.foo');
visitSubexpression(prefixedIdentifier, 'int');
}
Future<void> test_prefixedIdentifier_extensionMember_disallowsNull() async {
await analyze('''
class C {}
extension E on C/*!*/ {
int get foo => 0;
}
f(C/*?*/ c) => c.foo;
''');
var prefixedIdentifier = findNode.prefixed('c.foo');
visitSubexpression(prefixedIdentifier, 'int',
changes: {prefixedIdentifier.prefix: isNullCheck});
}
Future<void> test_prefixedIdentifier_field_nonNullable() async {
await analyze('''
class _C {
int/*!*/ x = 0;
}
_f(_C c) => c.x;
''');
visitSubexpression(findNode.prefixed('c.x'), 'int');
}
Future<void> test_prefixedIdentifier_field_nullable() async {
await analyze('''
class _C {
int/*?*/ x = 0;
}
_f(_C c) => c.x;
''');
visitSubexpression(findNode.prefixed('c.x'), 'int?');
}
Future<void> test_prefixedIdentifier_getter_check_lhs() async {
await analyze('''
abstract class _C {
int get x;
}
_f(_C/*?*/ c) => c.x;
''');
visitSubexpression(findNode.prefixed('c.x'), 'int',
changes: {findNode.simple('c.x'): isNullCheck});
}
Future<void> test_prefixedIdentifier_getter_nonNullable() async {
await analyze('''
abstract class _C {
int/*!*/ get x;
}
_f(_C c) => c.x;
''');
visitSubexpression(findNode.prefixed('c.x'), 'int');
}
Future<void> test_prefixedIdentifier_getter_nullable() async {
await analyze('''
abstract class _C {
int/*?*/ get x;
}
_f(_C c) => c.x;
''');
visitSubexpression(findNode.prefixed('c.x'), 'int?');
}
Future<void> test_prefixedIdentifier_object_getter() async {
await analyze('''
class _C {}
_f(_C/*?*/ c) => c.hashCode;
''');
visitSubexpression(findNode.prefixed('c.hashCode'), 'int');
}
Future<void> test_prefixedIdentifier_object_tearoff() async {
await analyze('''
class _C {}
_f(_C/*?*/ c) => c.toString;
''');
visitSubexpression(findNode.prefixed('c.toString'), 'String Function()');
}
Future<void> test_prefixedIdentifier_substituted() async {
await analyze('''
abstract class _C<T> {
List<T> get x;
}
_f(_C<int> c) => c.x;
''');
visitSubexpression(findNode.prefixed('c.x'), 'List<int>');
}
Future<void> test_prefixExpression_bang_flow() async {
await analyze('''
_f(int/*?*/ x) {
if (!(x == null)) {
x + 1;
}
}
''');
// No null check should be needed on `x + 1` because `!(x == null)` promotes
// x's type to `int`.
visitStatement(findNode.statement('if'));
}
Future<void> test_prefixExpression_bang_nonNullable() async {
await analyze('''
_f(bool/*!*/ x) => !x;
''');
visitSubexpression(findNode.prefix('!x'), 'bool');
}
Future<void> test_prefixExpression_bang_nullable() async {
await analyze('''
_f(bool/*?*/ x) => !x;
''');
visitSubexpression(findNode.prefix('!x'), 'bool',
changes: {findNode.simple('x;'): isNullCheck});
}
Future<void> test_prefixExpression_combined_nullable_noProblem() async {
await analyze('''
abstract class _C {
_D/*?*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
abstract class _E {
_C/*!*/ get x;
void set x(_C/*?*/ value);
f() => ++x;
}
''');
visitSubexpression(findNode.prefix('++'), '_D?');
}
Future<void>
test_prefixExpression_combined_nullable_noProblem_dynamic() async {
await analyze('''
abstract class _E {
dynamic get x;
void set x(Object/*!*/ value);
f() => ++x;
}
''');
var prefix = findNode.prefix('++');
visitSubexpression(prefix, 'dynamic');
}
@FailingTest(reason: 'TODO(paulberry)')
Future<void> test_prefixExpression_combined_nullable_problem() async {
await analyze('''
abstract class _C {
_D/*?*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
abstract class _E {
_C/*!*/ get x;
void set x(_C/*!*/ value);
f() => ++x;
}
''');
var prefix = findNode.prefix('++');
visitSubexpression(prefix, '_D', problems: {
prefix: {const CompoundAssignmentCombinedNullable()}
});
}
Future<void> test_prefixExpression_decrement_undoes_promotion() async {
await analyze('''
abstract class _C {
_C/*?*/ operator-(int value);
}
_f(_C/*?*/ c) { // method
if (c != null) {
--c;
_g(c);
}
}
_g(_C/*!*/ c) {}
''');
visitStatement(findNode.block('{ // method'),
changes: {findNode.simple('c);'): isNullCheck});
}
Future<void> test_prefixExpression_extensionMember_allowsNull() async {
await analyze('''
class C {}
extension E on C/*?*/ {
C operator-() => C();
}
f(C/*?*/ c) => -c;
''');
var prefixExpression = findNode.prefix('-c');
visitSubexpression(prefixExpression, 'C');
}
Future<void>
test_prefixExpression_extensionMember_allowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*?*/ {
C operator-() => C();
}
f(C/*?*/ c) => -E(c);
''');
var prefixExpression = findNode.prefix('-E(c)');
visitSubexpression(prefixExpression, 'C');
}
Future<void> test_prefixExpression_extensionMember_disallowsNull() async {
await analyze('''
class C {}
extension E on C/*!*/ {
C operator-() => C();
}
f(C/*?*/ c) => -c;
''');
var prefixExpression = findNode.prefix('-c');
visitSubexpression(prefixExpression, 'C',
changes: {prefixExpression.operand: isNullCheck});
}
Future<void>
test_prefixExpression_extensionMember_disallowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*!*/ {
C operator-() => C();
}
f(C/*?*/ c) => -E(c);
''');
var prefixExpression = findNode.prefix('-E(c)');
visitSubexpression(prefixExpression, 'C',
changes: {findNode.simple('c);'): isNullCheck});
}
Future<void> test_prefixExpression_increment_undoes_promotion() async {
await analyze('''
abstract class _C {
_C/*?*/ operator+(int value);
}
_f(_C/*?*/ c) { // method
if (c != null) {
++c;
_g(c);
}
}
_g(_C/*!*/ c) {}
''');
visitStatement(findNode.block('{ // method'),
changes: {findNode.simple('c);'): isNullCheck});
}
Future<void> test_prefixExpression_intRules() async {
await analyze('''
_f(int x) => ++x;
''');
visitSubexpression(findNode.prefix('++'), 'int');
}
@FailingTest(reason: 'TODO(paulberry)')
Future<void> test_prefixExpression_lhs_nullable_problem() async {
await analyze('''
abstract class _C {
_D/*!*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
abstract class _E {
_C/*?*/ get x;
void set x(_C/*?*/ value);
f() => ++x;
}
''');
var prefix = findNode.prefix('++');
visitSubexpression(prefix, '_D', problems: {
prefix: {const CompoundAssignmentReadNullable()}
});
}
Future<void> test_prefixExpression_minus_dynamic() async {
await analyze('''
_f(dynamic x) => -x;
''');
visitSubexpression(findNode.prefix('-x'), 'dynamic');
}
Future<void> test_prefixExpression_minus_nonNullable() async {
await analyze('''
_f(int/*!*/ x) => -x;
''');
visitSubexpression(findNode.prefix('-x'), 'int');
}
Future<void> test_prefixExpression_minus_nullable() async {
await analyze('''
_f(int/*?*/ x) => -x;
''');
visitSubexpression(findNode.prefix('-x'), 'int',
changes: {findNode.simple('x;'): isNullCheck});
}
Future<void> test_prefixExpression_minus_substitution() async {
await analyze('''
abstract class _C<T> {
List<T> operator-();
}
_f(_C<int> x) => -x;
''');
visitSubexpression(findNode.prefix('-x'), 'List<int>');
}
Future<void> test_prefixExpression_rhs_nonNullable() async {
await analyze('''
abstract class _C {
_D/*!*/ operator+(int/*!*/ value);
}
abstract class _D extends _C {}
_f(_C/*!*/ x) => ++x;
''');
visitSubexpression(findNode.prefix('++'), '_D');
}
Future<void> test_prefixExpression_tilde_dynamic() async {
await analyze('''
_f(dynamic x) => ~x;
''');
visitSubexpression(findNode.prefix('~x'), 'dynamic');
}
Future<void> test_prefixExpression_tilde_nonNullable() async {
await analyze('''
_f(int/*!*/ x) => ~x;
''');
visitSubexpression(findNode.prefix('~x'), 'int');
}
Future<void> test_prefixExpression_tilde_nullable() async {
await analyze('''
_f(int/*?*/ x) => ~x;
''');
visitSubexpression(findNode.prefix('~x'), 'int',
changes: {findNode.simple('x;'): isNullCheck});
}
Future<void> test_prefixExpression_tilde_substitution() async {
await analyze('''
abstract class _C<T> {
List<T> operator~();
}
_f(_C<int> x) => ~x;
''');
visitSubexpression(findNode.prefix('~x'), 'List<int>');
}
Future<void> test_propertyAccess_dynamic() async {
await analyze('''
Object/*!*/ _f(dynamic d) => (d).x;
''');
visitSubexpression(findNode.propertyAccess('(d).x'), 'dynamic');
}
Future<void> test_propertyAccess_extensionMember_allowsNull() async {
await analyze('''
class C {}
extension E on C/*?*/ {
int get foo => 0;
}
f(C/*?*/ Function() g) => g().foo;
''');
var propertyAccess = findNode.propertyAccess('g().foo');
visitSubexpression(propertyAccess, 'int');
}
Future<void> test_propertyAccess_extensionMember_allowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*?*/ {
int get foo => 0;
}
f(C/*?*/ Function() g) => E(g()).foo;
''');
var propertyAccess = findNode.propertyAccess('E(g()).foo');
visitSubexpression(propertyAccess, 'int');
}
Future<void> test_propertyAccess_extensionMember_disallowsNull() async {
await analyze('''
class C {}
extension E on C/*!*/ {
int get foo => 0;
}
f(C/*?*/ Function() g) => g().foo;
''');
var propertyAccess = findNode.propertyAccess('g().foo');
visitSubexpression(propertyAccess, 'int',
changes: {propertyAccess.target: isNullCheck});
}
Future<void>
test_propertyAccess_extensionMember_disallowsNull_explicit() async {
await analyze('''
class C {}
extension E on C/*!*/ {
int get foo => 0;
}
f(C/*?*/ Function() g) => E(g()).foo;
''');
var propertyAccess = findNode.propertyAccess('E(g()).foo');
visitSubexpression(propertyAccess, 'int', changes: {
findNode.functionExpressionInvocation('g()).foo'): isNullCheck
});
}
Future<void> test_propertyAccess_field_nonNullable() async {
await analyze('''
class _C {
int/*!*/ x = 0;
}
_f(_C c) => (c).x;
''');
visitSubexpression(findNode.propertyAccess('(c).x'), 'int');
}
Future<void> test_propertyAccess_field_nullable() async {
await analyze('''
class _C {
int/*?*/ x = 0;
}
_f(_C c) => (c).x;
''');
visitSubexpression(findNode.propertyAccess('(c).x'), 'int?');
}
Future<void> test_propertyAccess_getter_check_lhs() async {
await analyze('''
abstract class _C {
int get x;
}
_f(_C/*?*/ c) => (c).x;
''');
visitSubexpression(findNode.propertyAccess('(c).x'), 'int',
changes: {findNode.parenthesized('(c).x'): isNullCheck});
}
Future<void> test_propertyAccess_getter_nonNullable() async {
await analyze('''
abstract class _C {
int/*!*/ get x;
}
_f(_C c) => (c).x;
''');
visitSubexpression(findNode.propertyAccess('(c).x'), 'int');
}
Future<void> test_propertyAccess_getter_nullable() async {
await analyze('''
abstract class _C {
int/*?*/ get x;
}
_f(_C c) => (c).x;
''');
visitSubexpression(findNode.propertyAccess('(c).x'), 'int?');
}
Future<void> test_propertyAccess_nullAware_dynamic() async {
await analyze('''
Object/*!*/ _f(dynamic d) => d?.x;
''');
visitSubexpression(findNode.propertyAccess('d?.x'), 'dynamic');
}
Future<void> test_propertyAccess_nullAware_field_nonNullable() async {
await analyze('''
class _C {
int/*!*/ x = 0;
}
_f(_C/*?*/ c) => c?.x;
''');
visitSubexpression(findNode.propertyAccess('c?.x'), 'int?');
}
Future<void> test_propertyAccess_nullAware_field_nullable() async {
await analyze('''
class _C {
int/*?*/ x = 0;
}
_f(_C/*?*/ c) => c?.x;
''');
visitSubexpression(findNode.propertyAccess('c?.x'), 'int?');
}
Future<void> test_propertyAccess_nullAware_getter_nonNullable() async {
await analyze('''
abstract class _C {
int/*!*/ get x;
}
_f(_C/*?*/ c) => c?.x;
''');
visitSubexpression(findNode.propertyAccess('c?.x'), 'int?');
}
Future<void> test_propertyAccess_nullAware_getter_nullable() async {
await analyze('''
abstract class _C {
int/*?*/ get x;
}
_f(_C/*?*/ c) => c?.x;
''');
visitSubexpression(findNode.propertyAccess('c?.x'), 'int?');
}
Future<void> test_propertyAccess_nullAware_object_getter() async {
await analyze('''
class _C {}
_f(_C/*?*/ c) => c?.hashCode;
''');
visitSubexpression(findNode.propertyAccess('c?.hashCode'), 'int?');
}
Future<void> test_propertyAccess_nullAware_object_tearoff() async {
await analyze('''
class _C {}
_f(_C/*?*/ c) => c?.toString;
''');
visitSubexpression(
findNode.propertyAccess('c?.toString'), 'String Function()?');
}
Future<void> test_propertyAccess_nullAware_potentiallyNullable() async {
// In the code example below, the `?.` is not changed to `.` because `T`
// might be instantiated to `int?`, in which case the null check is still
// needed.
await analyze('''
class C<T extends int/*?*/> {
f(T t) => t?.isEven;
}
''');
visitSubexpression(findNode.propertyAccess('?.'), 'bool?');
}
Future<void> test_propertyAccess_nullAware_removeNullAwareness() async {
await analyze('_f(int/*!*/ i) => i?.isEven;');
var propertyAccess = findNode.propertyAccess('?.');
visitSubexpression(propertyAccess, 'bool',
changes: {propertyAccess: isRemoveNullAwareness});
}
Future<void>
test_propertyAccess_nullAware_removeNullAwareness_nullCheck() async {
await analyze('''
class C {
int/*?*/ i;
}
int/*!*/ f(C/*!*/ c) => c?.i;
''');
var propertyAccess = findNode.propertyAccess('?.');
visitSubexpression(propertyAccess, 'int', changes: {
propertyAccess: TypeMatcher<NodeChangeForPropertyAccess>()
.havingNullCheckWithInfo(isNotNull)
.having((c) => c.removeNullAwareness, 'removeNullAwareness', true)
});
}
Future<void> test_propertyAccess_nullAware_substituted() async {
await analyze('''
abstract class _C<T> {
List<T> get x;
}
_f(_C<int>/*?*/ c) => c?.x;
''');
visitSubexpression(findNode.propertyAccess('c?.x'), 'List<int>?');
}
Future<void> test_propertyAccess_object_getter() async {
await analyze('''
class _C {}
_f(_C/*?*/ c) => (c).hashCode;
''');
visitSubexpression(findNode.propertyAccess('(c).hashCode'), 'int');
}
Future<void> test_propertyAccess_object_tearoff() async {
await analyze('''
class _C {}
_f(_C/*?*/ c) => (c).toString;
''');
visitSubexpression(
findNode.propertyAccess('(c).toString'), 'String Function()');
}
Future<void> test_propertyAccess_substituted() async {
await analyze('''
abstract class _C<T> {
List<T> get x;
}
_f(_C<int> c) => (c).x;
''');
visitSubexpression(findNode.propertyAccess('(c).x'), 'List<int>');
}
Future<void> test_removeLanguageVersionComment() async {
await analyze('''
// @dart = 2.6
void main() {}
''');
visitAll(changes: {findNode.unit: isRemoveLanguageVersion});
}
Future<void> test_removeLanguageVersionComment_withCopyright() async {
await analyze('''
// Some copyright notice here...
// @dart = 2.6
void main() {}
''');
visitAll(changes: {findNode.unit: isRemoveLanguageVersion});
}
Future<void> test_set_ifElement_alive() async {
await analyze('''
_f(int x, bool b, int/*?*/ y) => {if (b) h(y) else g(y)};
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.setOrMapLiteral('{'), 'Set<num>', changes: {
findNode.simple('y) else'): isNullCheck,
findNode.simple('y)}'): isNullCheck
});
}
Future<void> test_set_ifElement_alive_with_null_check() async {
await analyze('''
_f(int x, bool/*?*/ b, int/*?*/ y) => {if (b == null) h(y) else g(y)};
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.setOrMapLiteral('{'), 'Set<num>', changes: {
findNode.simple('y) else'): isNullCheck,
findNode.simple('y)}'): isNullCheck
});
}
Future<void> test_set_ifElement_dead_else() async {
await analyze('''
_f(int x, int/*?*/ y) => {if (x != null) g(y) else h(y)};
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.setOrMapLiteral('{'), 'Set<int>', changes: {
findNode.ifElement('null'): isConditionalWithKnownValue(true),
findNode.simple('y) else'): isNullCheck
});
}
Future<void> test_set_ifElement_dead_else_no_else() async {
await analyze('''
_f(int x, int/*?*/ y) => {if (x != null) g(y)};
int/*!*/ g(int/*!*/ y) => y;
''');
visitSubexpression(findNode.setOrMapLiteral('{'), 'Set<int>', changes: {
findNode.ifElement('null'): isConditionalWithKnownValue(true),
findNode.simple('y)}'): isNullCheck
});
}
Future<void> test_set_ifElement_dead_then() async {
await analyze('''
_f(int x, int/*?*/ y) => {if (x == null) h(y) else g(y)};
int/*!*/ g(int/*!*/ y) => y;
double/*!*/ h(int/*!*/ y) => y.toDouble();
''');
visitSubexpression(findNode.setOrMapLiteral('{'), 'Set<int>', changes: {
findNode.ifElement('null'): isConditionalWithKnownValue(false),
findNode.simple('y)}'): isNullCheck
});