// 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() .having((c) => c.addRequiredKeyword, 'addRequiredKeyword', true); static final isMakeNullable = TypeMatcher() .having((c) => c.makeNullable, 'makeNullable', true) .having((c) => c.nullabilityHint, 'nullabilityHint', isNull); static final isMakeNullableDueToHint = TypeMatcher() .having((c) => c.makeNullable, 'makeNullable', true) .having((c) => c.nullabilityHint, 'nullabilityHint', isNotNull); static const isEdge = TypeMatcher(); static final isExplainNonNullable = TypeMatcher() .having((c) => c.makeNullable, 'makeNullable', false); static final isBadCombinedType = TypeMatcher() .having((c) => c.hasBadCombinedType, 'hasBadCombinedType', true); static final isNullableSource = TypeMatcher() .having((c) => c.hasNullableSource, 'hasNullableSource', true); static final isNodeChangeForExpression = TypeMatcher(); static final isNoValidMigration = isNodeChangeForExpression.havingNoValidMigrationWithInfo(anything); static final isNullCheck = isNodeChangeForExpression.havingNullCheckWithInfo(anything); static final isRemoveLanguageVersion = TypeMatcher().having( (c) => c.removeLanguageVersionComment, 'removeLanguageVersionComment', true); static final isAddImportOfIterableExtension = TypeMatcher() .having((c) => c.addImports, 'addImports', { 'package:collection/collection.dart': {'IterableExtension'} }); static final isAddShowOfIterableExtension = TypeMatcher().having((c) => c.addNames, 'addNames', unorderedEquals(['IterableExtension'])); static final isRemoveNullAwareness = TypeMatcher() .having((c) => c.removeNullAwareness, 'removeNullAwareness', true); static final isRemoveAs = TypeMatcher() .having((c) => c.removeAs, 'removeAs', true); static final isRequiredAnnotationToRequiredKeyword = TypeMatcher().having( (c) => c.changeToRequiredKeyword, 'changeToRequiredKeyword', true); static final isWeakNullAwareAssignment = TypeMatcher() .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 analyze(String code) async { var unit = await super.analyze(code); graph.propagate(null); return unit; } TypeMatcher isDropArgument( dynamic argumentsToDrop) => TypeMatcher() .having((c) => c.argumentsToDrop, 'argumentsToDrop', argumentsToDrop); TypeMatcher isInfo(description, fixReasons) => TypeMatcher() .having((i) => i.description, 'description', description) .having((i) => i.fixReasons, 'fixReasons', fixReasons); TypeMatcher isMethodNameChange( dynamic replacement) => TypeMatcher() .having((c) => c.replacement, 'replacement', replacement); Map 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 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> scopedProblems( FixBuilder fixBuilder, AstNode scope) => { for (var entry in fixBuilder.problems.entries) if (_isInScope(entry.key, scope)) entry.key: entry.value }; Future 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 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 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 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 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 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 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 test_assignmentExpression_compound_intRules() async { await analyze(''' _f(int x, int y) => x += y; '''); visitSubexpression(findNode.assignment('+='), 'int'); } @FailingTest(reason: 'TODO(paulberry)') Future 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 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 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 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 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 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 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 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 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 test_assignmentExpression_simple_nonNullable_to_nonNullable() async { await analyze(''' _f(int/*!*/ x, int/*!*/ y) => x = y; '''); visitSubexpression(findNode.assignment('= '), 'int'); } Future test_assignmentExpression_simple_nonNullable_to_nullable() async { await analyze(''' _f(int/*?*/ x, int/*!*/ y) => x = y; '''); visitSubexpression(findNode.assignment('= '), 'int'); } Future 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 test_assignmentExpression_simple_nullable_to_nullable() async { await analyze(''' _f(int/*?*/ x, int/*?*/ y) => x = y; '''); visitSubexpression(findNode.assignment('= '), 'int?'); } Future 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 test_assignmentTarget_indexExpression_compound_dynamic() async { await analyze(''' _f(dynamic d, int/*?*/ i) => d[i] += 0; '''); visitAssignmentTarget(findNode.index('d[i]'), 'dynamic', 'dynamic'); } Future 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 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 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 test_assignmentTarget_indexExpression_compound_substituted() async { await analyze(''' class _C { T operator[](U u) => throw 'foo'; void operator[]=(U u, T t) {} } _f(_C c) => c['foo'] += 1; '''); visitAssignmentTarget(findNode.index('c['), 'int', 'int'); } @FailingTest(reason: 'TODO(paulberry): decide if this is worth caring about') Future test_assignmentTarget_indexExpression_compound_substituted_check_rhs() async { await analyze(''' class _C { T operator[](U u) => throw 'foo'; void operator[]=(U/*?*/ u, T t) {} } _f(_C c, String/*?*/ s) => c[s] += 1; '''); visitAssignmentTarget(findNode.index('c['), 'int', 'int', changes: {findNode.simple('s]'): isNullCheck}); } Future test_assignmentTarget_indexExpression_compound_substituted_no_check_rhs() async { await analyze(''' class _C { T operator[](U u) => throw 'foo'; void operator[]=(U u, T t) {} } _f(_C c, String/*?*/ s) => c[s] += 0; '''); visitAssignmentTarget(findNode.index('c['), 'int', 'int'); } Future test_assignmentTarget_indexExpression_dynamic() async { await analyze(''' _f(dynamic d, int/*?*/ i) => d[i] = 0; '''); visitAssignmentTarget(findNode.index('d[i]'), null, 'dynamic'); } Future 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 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 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 test_assignmentTarget_indexExpression_substituted() async { await analyze(''' class _C { T operator[](U u) => throw 'foo'; void operator[]=(U u, T t) {} } _f(_C c) => c['foo'] = 1; '''); visitAssignmentTarget(findNode.index('c['), null, 'int'); } Future test_assignmentTarget_indexExpression_substituted_check_rhs() async { await analyze(''' class _C { T operator[](U u) => throw 'foo'; void operator[]=(U/*!*/ u, T t) {} } _f(_C c, String/*?*/ s) => c[s] = 1; '''); visitAssignmentTarget(findNode.index('c['), null, 'int', changes: {findNode.simple('s]'): isNullCheck}); } Future test_assignmentTarget_indexExpression_substituted_no_check_rhs() async { await analyze(''' class _C { T operator[](U u) => throw 'foo'; void operator[]=(U u, T t) {} } _f(_C c, String/*?*/ s) => c[s] = 0; '''); visitAssignmentTarget(findNode.index('c['), null, 'int'); } Future test_assignmentTarget_prefixedIdentifier_dynamic() async { await analyze(''' Object/*!*/ _f(dynamic d) => d.x += 1; '''); visitAssignmentTarget(findNode.prefixed('d.x'), 'dynamic', 'dynamic'); } Future test_assignmentTarget_propertyAccess_dynamic() async { await analyze(''' _f(dynamic d) => (d).x += 1; '''); visitAssignmentTarget( findNode.propertyAccess('(d).x'), 'dynamic', 'dynamic'); } Future test_assignmentTarget_propertyAccess_dynamic_notCompound() async { await analyze(''' _f(dynamic d) => (d).x = 1; '''); visitAssignmentTarget(findNode.propertyAccess('(d).x'), null, 'dynamic'); } Future 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 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 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 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 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 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 test_assignmentTarget_propertyAccess_nullAware_dynamic() async { await analyze(''' _f(dynamic d) => d?.x += 1; '''); visitAssignmentTarget( findNode.propertyAccess('d?.x'), 'dynamic', 'dynamic'); } Future 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 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 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 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 test_assignmentTarget_propertyAccess_nullAware_substituted() async { await analyze(''' abstract class _C { _E get x; void set x(_D value); } class _D implements Iterable { noSuchMethod(invocation) => super.noSuchMethod(invocation); _D operator+(int i) => this; } class _E extends _D {} _f(_C/*?*/ c) => c?.x += 1; '''); visitAssignmentTarget( findNode.propertyAccess('c?.x'), '_E', '_D'); } Future test_assignmentTarget_propertyAccess_substituted() async { await analyze(''' abstract class _C { _E get x; void set x(_D value); } class _D implements Iterable { noSuchMethod(invocation) => super.noSuchMethod(invocation); _D operator+(int i) => this; } class _E extends _D {} _f(_C c) => (c).x += 1; '''); visitAssignmentTarget( findNode.propertyAccess('(c).x'), '_E', '_D'); } Future test_assignmentTarget_simpleIdentifier_field_generic() async { await analyze(''' abstract class _C { _C operator+(int i); } class _D { _D(this.x); _C/*!*/ x; _f() => x += 0; } '''); visitAssignmentTarget(findNode.simple('x +='), '_C', '_C'); } Future test_assignmentTarget_simpleIdentifier_field_nonNullable() async { await analyze(''' class _C { int/*!*/ x; _f() => x += 0; } '''); visitAssignmentTarget(findNode.simple('x '), 'int', 'int'); } Future test_assignmentTarget_simpleIdentifier_field_nullable() async { await analyze(''' class _C { int/*?*/ x; _f() => x += 0; } '''); visitAssignmentTarget(findNode.simple('x '), 'int?', 'int?'); } Future test_assignmentTarget_simpleIdentifier_getset_generic() async { await analyze(''' abstract class _C { _C operator+(int i); } abstract class _D extends _C {} abstract class _E { _D/*!*/ get x; void set x(_C/*!*/ value); _f() => x += 0; } '''); visitAssignmentTarget(findNode.simple('x +='), '_D', '_C'); } Future 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 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 test_assignmentTarget_simpleIdentifier_localVariable_nonNullable() async { await analyze(''' _f(int/*!*/ x) => x += 0; '''); visitAssignmentTarget(findNode.simple('x '), 'int', 'int'); } Future test_assignmentTarget_simpleIdentifier_localVariable_nullable() async { await analyze(''' _f(int/*?*/ x) => x += 0; '''); visitAssignmentTarget(findNode.simple('x '), 'int?', 'int?'); } Future 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 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 test_binaryExpression_ampersand_ampersand() async { await analyze(''' _f(bool x, bool y) => x && y; '''); visitSubexpression(findNode.binary('&&'), 'bool'); } Future test_binaryExpression_ampersand_ampersand_flow() async { await analyze(''' _f(bool/*?*/ x) => x != null && x; '''); visitSubexpression(findNode.binary('&&'), 'bool'); } Future 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 test_binaryExpression_bang_eq() async { await analyze(''' _f(Object/*?*/ x, Object/*?*/ y) => x != y; '''); visitSubexpression(findNode.binary('!='), 'bool'); } Future test_binaryExpression_bar_bar() async { await analyze(''' _f(bool x, bool y) { return x || y; } '''); visitSubexpression(findNode.binary('||'), 'bool'); } Future test_binaryExpression_bar_bar_flow() async { await analyze(''' _f(bool/*?*/ x) { return x == null || x; } '''); visitSubexpression(findNode.binary('||'), 'bool'); } Future 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 test_binaryExpression_eq_eq() async { await analyze(''' _f(Object/*?*/ x, Object/*?*/ y) { return x == y; } '''); visitSubexpression(findNode.binary('=='), 'bool'); } Future 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 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 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 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 test_binaryExpression_question_question() async { await analyze(''' _f(int/*?*/ x, double/*?*/ y) { return x ?? y; } '''); visitSubexpression(findNode.binary('??'), 'num?'); } Future test_binaryExpression_question_question_flow() async { await analyze(''' _f(int/*?*/ x, int/*?*/ y) => [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', changes: {findNode.simple('y +'): isNullCheck}); } Future 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 test_binaryExpression_userDefinable_dynamic() async { await analyze(''' Object/*!*/ _f(dynamic d, int/*?*/ i) => d + i; '''); visitSubexpression(findNode.binary('+'), 'dynamic'); } Future test_binaryExpression_userDefinable_intRules() async { await analyze(''' _f(int i, int j) => i + j; '''); visitSubexpression(findNode.binary('+'), 'int'); } Future 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 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 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 test_binaryExpression_userDefinable_substituted() async { await analyze(''' class _C { T operator+(U u) => throw 'foo'; } _f(_C c) => c + 'foo'; '''); visitSubexpression(findNode.binary('c +'), 'int'); } Future test_binaryExpression_userDefinable_substituted_check_rhs() async { await analyze(''' class _C { T operator+(U/*!*/ u) => throw 'foo'; } _f(_C c, String/*?*/ s) => c + s; '''); visitSubexpression(findNode.binary('c +'), 'int', changes: {findNode.simple('s;'): isNullCheck}); } Future test_binaryExpression_userDefinable_substituted_no_check_rhs() async { await analyze(''' class _C { T operator+(U u) => throw 'foo'; } _f(_C c, String/*?*/ s) => c + s; '''); visitSubexpression(findNode.binary('c +'), 'int'); } Future 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 test_booleanLiteral() async { await analyze(''' f() => true; '''); visitSubexpression(findNode.booleanLiteral('true'), 'bool'); } Future 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 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 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 test_compound_assignment_nullable_source() async { await analyze(''' _f(int/*?*/ x) { x += 1; } '''); var assignment = findNode.assignment('+='); visitSubexpression(assignment, 'int', changes: {assignment: isNullableSource}); } Future test_compound_assignment_potentially_nullable_source() async { await analyze(''' class C { _f(T/*!*/ x) { x += 1; } } '''); var assignment = findNode.assignment('+='); visitSubexpression(assignment, 'num', changes: {assignment: isNullableSource}); } Future 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 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 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 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 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 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 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 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 test_conditionalExpression_lub() async { await analyze(''' _f(bool b) => b ? 1 : 1.0; '''); visitSubexpression(findNode.conditionalExpression('1.0'), 'num'); } Future test_conditionalExpression_throw_promotes() async { await analyze(''' _f(int/*?*/ x) => [(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'); } Future 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 test_defaultFormalParameter_add_required_no_because_default() async { await analyze(''' int _f({int x = 0}) => x + 1; '''); visitAll(); } Future test_defaultFormalParameter_add_required_no_because_nullable() async { await analyze(''' int _f({int/*?*/ x}) => 1; '''); visitAll( changes: {findNode.typeName('int/*?*/ x'): isMakeNullableDueToHint}); } Future 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 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 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 test_defaultFormalParameter_add_required_yes() async { await analyze(''' int _f({int x}) => x + 1; '''); visitAll( changes: {findNode.defaultParameter('int x'): isAddRequiredKeyword}); } Future test_doubleLiteral() async { await analyze(''' f() => 1.0; '''); visitSubexpression(findNode.doubleLiteral('1.0'), 'double'); } Future test_enum_ref_index() async { await analyze(''' enum E { V } _f(E e) => e.index; '''); visitSubexpression(findNode.prefixed('e.index'), 'int'); } Future test_enum_ref_value() async { await analyze(''' enum E { V } _f() => E.V; '''); visitSubexpression(findNode.prefixed('E.V'), 'E'); } Future test_enum_ref_values() async { await analyze(''' enum E { V } _f() => E.values; '''); visitSubexpression(findNode.prefixed('E.values'), 'List'); } Future test_expressionStatement() async { await analyze(''' _f(int/*!*/ x, int/*?*/ y) { x = y; } '''); visitStatement(findNode.statement('x = y'), changes: {findNode.simple('y;'): isNullCheck}); } Future test_firstWhere_transform() async { await analyze(''' _f(Iterable 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 test_functionExpressionInvocation_dynamic() async { await analyze(''' _f(dynamic d) => d(); '''); visitSubexpression(findNode.functionExpressionInvocation('d('), 'dynamic'); } Future 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 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 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 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 test_functionExpressionInvocation_function_checked() async { await analyze(''' _f(Function/*?*/ func) => func(); '''); visitSubexpression( findNode.functionExpressionInvocation('func('), 'dynamic', changes: {findNode.simple('func()'): isNullCheck}); } Future 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 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 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 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 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 test_genericFunctionType_nonNullable_by_context() async { await analyze(''' typedef F = void Function(); '''); var genericFunctionType = findNode.genericFunctionType('Function'); visitTypeAnnotation(genericFunctionType, 'void Function()', informative: isEmpty); } Future 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 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 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 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 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 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 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 test_ifStatement_flow_promote_in_then_no_else() async { await analyze(''' _f(int/*?*/ x) { if (x != null) { x + 1; } } '''); visitStatement(findNode.statement('if')); } Future 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 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 test_import_IterableExtension_already_imported_all() async { addPackageFile('collection', 'collection.dart', ''); await analyze(''' import 'package:collection/collection.dart'; main() {} '''); visitAll(injectNeedsIterableExtension: true, changes: {}); } Future test_import_IterableExtension_already_imported_and_shown() async { addPackageFile('collection', 'collection.dart', 'extension IterableExtension on Iterable {}'); await analyze(''' import 'package:collection/collection.dart' show IterableExtension; main() {} '''); visitAll(injectNeedsIterableExtension: true, changes: {}); } Future 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 test_import_IterableExtension_other_import() async { addPackageFile( 'foo', 'foo.dart', 'extension IterableExtension on Iterable {}'); await analyze(''' import 'package:foo/foo.dart' show IterableExtension; main() {} '''); visitAll( injectNeedsIterableExtension: true, changes: {findNode.unit: isAddImportOfIterableExtension}); } Future test_import_IterableExtension_simple() async { await analyze(''' main() {} '''); visitAll( injectNeedsIterableExtension: true, changes: {findNode.unit: isAddImportOfIterableExtension}); } Future test_indexExpression_dynamic() async { await analyze(''' Object/*!*/ _f(dynamic d, int/*?*/ i) => d[i]; '''); visitSubexpression(findNode.index('d[i]'), 'dynamic'); } Future 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 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 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 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 test_indexExpression_simple() async { await analyze(''' class _C { int operator[](String s) => 1; } _f(_C c) => c['foo']; '''); visitSubexpression(findNode.index('c['), 'int'); } Future 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 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 test_indexExpression_substituted() async { await analyze(''' class _C { T operator[](U u) => throw 'foo'; } _f(_C c) => c['foo']; '''); visitSubexpression(findNode.index('c['), 'int'); } Future test_indexExpression_substituted_check_rhs() async { await analyze(''' class _C { T operator[](U/*!*/ u) => throw 'foo'; } _f(_C c, String/*?*/ s) => c[s]; '''); visitSubexpression(findNode.index('c['), 'int', changes: {findNode.simple('s]'): isNullCheck}); } Future test_indexExpression_substituted_no_check_rhs() async { await analyze(''' class _C { T operator[](U u) => throw 'foo'; } _f(_C c, String/*?*/ s) => c[s]; '''); visitSubexpression(findNode.index('c['), 'int'); } Future test_integerLiteral() async { await analyze(''' f() => 1; '''); visitSubexpression(findNode.integerLiteral('1'), 'int'); } Future 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', changes: { findNode.simple('y) else'): isNullCheck, findNode.simple('y)]'): isNullCheck }); } Future 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', changes: { findNode.simple('y) else'): isNullCheck, findNode.simple('y)]'): isNullCheck }); } Future 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', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(true), findNode.simple('y) else'): isNullCheck }); } Future 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', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(true), findNode.simple('y)]'): isNullCheck }); } Future 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', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(false), findNode.simple('y)]'): isNullCheck }); } Future test_list_ifElement_dead_then_no_else() async { // TODO(paulberry): rather than infer the type to be List, // FixBuilder should add an explicit type argument to ensure that it is // still List. await analyze(''' _f(int x, int/*?*/ y) => [if (x == null) h(y)]; double/*!*/ h(int/*!*/ y) => y.toDouble(); '''); visitSubexpression(findNode.listLiteral('['), 'List', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(false) }); } Future test_list_make_explicit_type_nullable() async { await analyze('_f() => [null];'); // The `null` should be analyzed with a context type of `int?`, so it should // not be null-checked. visitSubexpression(findNode.listLiteral('['), 'List', changes: {findNode.typeAnnotation('int'): isMakeNullable}); } Future test_list_unchanged() async { await analyze('_f(int x) => [x];'); visitSubexpression(findNode.listLiteral('['), 'List'); } Future test_listLiteral_typed() async { await analyze(''' _f() => []; '''); visitSubexpression(findNode.listLiteral('['), 'List'); } Future test_listLiteral_typed_visit_contents() async { await analyze(''' _f(int/*?*/ x) => [x]; '''); visitSubexpression(findNode.listLiteral('['), 'List', changes: {findNode.simple('x]'): isNullCheck}); } Future 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', changes: { findNode.simple('y) else'): isNullCheck, findNode.simple('y)}'): isNullCheck }); } Future 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', changes: { findNode.simple('y) else'): isNullCheck, findNode.simple('y)}'): isNullCheck }); } Future 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', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(true), findNode.simple('y) else'): isNullCheck }); } Future 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', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(true), findNode.simple('y)}'): isNullCheck }); } Future 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', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(false), findNode.simple('y)}'): isNullCheck }); } Future test_map_ifElement_dead_then_no_else() async { // TODO(paulberry): rather than infer the type to be Map, // FixBuilder should add an explicit type argument to ensure that it is // still Map. await analyze(''' _f(int x, int/*?*/ y) => {if (x == null) 0: h(y)}; double/*!*/ h(int/*!*/ y) => y.toDouble(); '''); visitSubexpression(findNode.setOrMapLiteral('{'), 'Map', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(false) }); } Future test_map_make_explicit_key_type_nullable() async { await analyze('_f() => {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', changes: {findNode.typeAnnotation('int'): isMakeNullable}); } Future test_map_make_explicit_value_type_nullable() async { await analyze('_f() => {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', changes: {findNode.typeAnnotation('int'): isMakeNullable}); } Future test_methodInvocation_dynamic() async { await analyze(''' Object/*!*/ _f(dynamic d) => d.f(); '''); visitSubexpression(findNode.methodInvocation('d.f'), 'dynamic'); } Future 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 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 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 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 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 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 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 test_methodInvocation_return_nonNullable() async { await analyze(''' abstract class _C { int f(); } _f(_C c) => c.f(); '''); visitSubexpression(findNode.methodInvocation('c.f'), 'int'); } Future 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 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 test_methodInvocation_return_nullable() async { await analyze(''' abstract class _C { int/*?*/ f(); } _f(_C c) => c.f(); '''); visitSubexpression(findNode.methodInvocation('c.f'), 'int?'); } Future test_methodInvocation_static() async { await analyze(''' _f() => _C.g(); class _C { static int g() => 1; } '''); visitSubexpression(findNode.methodInvocation('_C.g();'), 'int'); } Future test_methodInvocation_topLevel() async { await analyze(''' _f() => _g(); int _g() => 1; '''); visitSubexpression(findNode.methodInvocation('_g();'), 'int'); } Future test_methodInvocation_toString() async { await analyze(''' abstract class _C {} _f(_C/*?*/ c) => c.toString(); '''); visitSubexpression(findNode.methodInvocation('c.toString'), 'String'); } Future 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 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 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 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()})) }); } Future 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 test_nullLiteral() async { await analyze(''' f() => null; '''); visitSubexpression(findNode.nullLiteral('null'), 'Null'); } Future 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()})) }); } Future 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()})) }); } Future test_parenthesizedExpression() async { await analyze(''' f() => (1); '''); visitSubexpression(findNode.integerLiteral('1'), 'int'); } Future test_parenthesizedExpression_flow() async { await analyze(''' _f(bool/*?*/ x) => ((x) != (null)) && x; '''); visitSubexpression(findNode.binary('&&'), 'bool'); } Future 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 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 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 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 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 test_post_increment_nullable_source() async { await analyze(''' _f(int/*?*/ x) { x++; } '''); var increment = findNode.postfix('++'); visitSubexpression(increment, 'int?', changes: {increment: isNullableSource}); } Future test_post_increment_potentially_nullable_source() async { await analyze(''' class C { _f(T/*!*/ x) { x++; } } '''); var increment = findNode.postfix('++'); visitSubexpression(increment, 'T', changes: {increment: isNullableSource}); } Future 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 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 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 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 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 test_postfixExpression_dynamic() async { await analyze(''' _f(dynamic x) => x++; '''); visitSubexpression(findNode.postfix('++'), 'dynamic'); } Future 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 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 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 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 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 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 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 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 test_pre_increment_nullable_source() async { await analyze(''' _f(int/*?*/ x) { ++x; } '''); var increment = findNode.prefix('++'); visitSubexpression(increment, 'int', changes: {increment: isNullableSource}); } Future test_pre_increment_potentially_nullable_source() async { await analyze(''' class C { _f(T/*!*/ x) { ++x; } } '''); var increment = findNode.prefix('++'); visitSubexpression(increment, 'num', changes: {increment: isNullableSource}); } Future 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 test_prefixedIdentifier_dynamic() async { await analyze(''' Object/*!*/ _f(dynamic d) => d.x; '''); visitSubexpression(findNode.prefixed('d.x'), 'dynamic'); } Future 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 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 test_prefixedIdentifier_field_nonNullable() async { await analyze(''' class _C { int/*!*/ x = 0; } _f(_C c) => c.x; '''); visitSubexpression(findNode.prefixed('c.x'), 'int'); } Future test_prefixedIdentifier_field_nullable() async { await analyze(''' class _C { int/*?*/ x = 0; } _f(_C c) => c.x; '''); visitSubexpression(findNode.prefixed('c.x'), 'int?'); } Future 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 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 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 test_prefixedIdentifier_object_getter() async { await analyze(''' class _C {} _f(_C/*?*/ c) => c.hashCode; '''); visitSubexpression(findNode.prefixed('c.hashCode'), 'int'); } Future test_prefixedIdentifier_object_tearoff() async { await analyze(''' class _C {} _f(_C/*?*/ c) => c.toString; '''); visitSubexpression(findNode.prefixed('c.toString'), 'String Function()'); } Future test_prefixedIdentifier_substituted() async { await analyze(''' abstract class _C { List get x; } _f(_C c) => c.x; '''); visitSubexpression(findNode.prefixed('c.x'), 'List'); } Future 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 test_prefixExpression_bang_nonNullable() async { await analyze(''' _f(bool/*!*/ x) => !x; '''); visitSubexpression(findNode.prefix('!x'), 'bool'); } Future test_prefixExpression_bang_nullable() async { await analyze(''' _f(bool/*?*/ x) => !x; '''); visitSubexpression(findNode.prefix('!x'), 'bool', changes: {findNode.simple('x;'): isNullCheck}); } Future 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 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 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 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 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 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 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 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 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 test_prefixExpression_intRules() async { await analyze(''' _f(int x) => ++x; '''); visitSubexpression(findNode.prefix('++'), 'int'); } @FailingTest(reason: 'TODO(paulberry)') Future 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 test_prefixExpression_minus_dynamic() async { await analyze(''' _f(dynamic x) => -x; '''); visitSubexpression(findNode.prefix('-x'), 'dynamic'); } Future test_prefixExpression_minus_nonNullable() async { await analyze(''' _f(int/*!*/ x) => -x; '''); visitSubexpression(findNode.prefix('-x'), 'int'); } Future test_prefixExpression_minus_nullable() async { await analyze(''' _f(int/*?*/ x) => -x; '''); visitSubexpression(findNode.prefix('-x'), 'int', changes: {findNode.simple('x;'): isNullCheck}); } Future test_prefixExpression_minus_substitution() async { await analyze(''' abstract class _C { List operator-(); } _f(_C x) => -x; '''); visitSubexpression(findNode.prefix('-x'), 'List'); } Future 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 test_prefixExpression_tilde_dynamic() async { await analyze(''' _f(dynamic x) => ~x; '''); visitSubexpression(findNode.prefix('~x'), 'dynamic'); } Future test_prefixExpression_tilde_nonNullable() async { await analyze(''' _f(int/*!*/ x) => ~x; '''); visitSubexpression(findNode.prefix('~x'), 'int'); } Future test_prefixExpression_tilde_nullable() async { await analyze(''' _f(int/*?*/ x) => ~x; '''); visitSubexpression(findNode.prefix('~x'), 'int', changes: {findNode.simple('x;'): isNullCheck}); } Future test_prefixExpression_tilde_substitution() async { await analyze(''' abstract class _C { List operator~(); } _f(_C x) => ~x; '''); visitSubexpression(findNode.prefix('~x'), 'List'); } Future test_propertyAccess_dynamic() async { await analyze(''' Object/*!*/ _f(dynamic d) => (d).x; '''); visitSubexpression(findNode.propertyAccess('(d).x'), 'dynamic'); } Future 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 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 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 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 test_propertyAccess_field_nonNullable() async { await analyze(''' class _C { int/*!*/ x = 0; } _f(_C c) => (c).x; '''); visitSubexpression(findNode.propertyAccess('(c).x'), 'int'); } Future test_propertyAccess_field_nullable() async { await analyze(''' class _C { int/*?*/ x = 0; } _f(_C c) => (c).x; '''); visitSubexpression(findNode.propertyAccess('(c).x'), 'int?'); } Future 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 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 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 test_propertyAccess_nullAware_dynamic() async { await analyze(''' Object/*!*/ _f(dynamic d) => d?.x; '''); visitSubexpression(findNode.propertyAccess('d?.x'), 'dynamic'); } Future 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 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 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 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 test_propertyAccess_nullAware_object_getter() async { await analyze(''' class _C {} _f(_C/*?*/ c) => c?.hashCode; '''); visitSubexpression(findNode.propertyAccess('c?.hashCode'), 'int?'); } Future test_propertyAccess_nullAware_object_tearoff() async { await analyze(''' class _C {} _f(_C/*?*/ c) => c?.toString; '''); visitSubexpression( findNode.propertyAccess('c?.toString'), 'String Function()?'); } Future 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 { f(T t) => t?.isEven; } '''); visitSubexpression(findNode.propertyAccess('?.'), 'bool?'); } Future test_propertyAccess_nullAware_removeNullAwareness() async { await analyze('_f(int/*!*/ i) => i?.isEven;'); var propertyAccess = findNode.propertyAccess('?.'); visitSubexpression(propertyAccess, 'bool', changes: {propertyAccess: isRemoveNullAwareness}); } Future 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() .havingNullCheckWithInfo(isNotNull) .having((c) => c.removeNullAwareness, 'removeNullAwareness', true) }); } Future test_propertyAccess_nullAware_substituted() async { await analyze(''' abstract class _C { List get x; } _f(_C/*?*/ c) => c?.x; '''); visitSubexpression(findNode.propertyAccess('c?.x'), 'List?'); } Future test_propertyAccess_object_getter() async { await analyze(''' class _C {} _f(_C/*?*/ c) => (c).hashCode; '''); visitSubexpression(findNode.propertyAccess('(c).hashCode'), 'int'); } Future test_propertyAccess_object_tearoff() async { await analyze(''' class _C {} _f(_C/*?*/ c) => (c).toString; '''); visitSubexpression( findNode.propertyAccess('(c).toString'), 'String Function()'); } Future test_propertyAccess_substituted() async { await analyze(''' abstract class _C { List get x; } _f(_C c) => (c).x; '''); visitSubexpression(findNode.propertyAccess('(c).x'), 'List'); } Future test_removeLanguageVersionComment() async { await analyze(''' // @dart = 2.6 void main() {} '''); visitAll(changes: {findNode.unit: isRemoveLanguageVersion}); } Future test_removeLanguageVersionComment_withCopyright() async { await analyze(''' // Some copyright notice here... // @dart = 2.6 void main() {} '''); visitAll(changes: {findNode.unit: isRemoveLanguageVersion}); } Future 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', changes: { findNode.simple('y) else'): isNullCheck, findNode.simple('y)}'): isNullCheck }); } Future 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', changes: { findNode.simple('y) else'): isNullCheck, findNode.simple('y)}'): isNullCheck }); } Future 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', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(true), findNode.simple('y) else'): isNullCheck }); } Future 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', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(true), findNode.simple('y)}'): isNullCheck }); } Future 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', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(false), findNode.simple('y)}'): isNullCheck }); } Future test_set_ifElement_dead_then_no_else() async { // TODO(paulberry): rather than infer the type to be Map, // FixBuilder should add an explicit type argument to ensure that it is // still Set. await analyze(''' _f(int x, int/*?*/ y) => {if (x == null) h(y)}; double/*!*/ h(int/*!*/ y) => y.toDouble(); '''); visitSubexpression(findNode.setOrMapLiteral('{'), 'Map', changes: { findNode.ifElement('null'): isConditionalWithKnownValue(false) }); } Future test_set_make_explicit_type_nullable() async { await analyze('_f() => {null};'); // The `null` should be analyzed with a context type of `int?`, so it should // not be null-checked. visitSubexpression(findNode.setOrMapLiteral('{'), 'Set', changes: {findNode.typeAnnotation('int'): isMakeNullable}); } Future test_simpleIdentifier_className() async { await analyze(''' _f() => int; '''); visitSubexpression(findNode.simple('int'), 'Type'); } Future test_simpleIdentifier_field() async { await analyze(''' class _C { int i = 1; f() => i; } '''); visitSubexpression(findNode.simple('i;'), 'int'); } Future test_simpleIdentifier_field_generic() async { await analyze(''' class _C { List x = null; f() => x; } '''); visitSubexpression(findNode.simple('x;'), 'List?'); } Future test_simpleIdentifier_field_nullable() async { await analyze(''' class _C { int/*?*/ i = 1; f() => i; } '''); visitSubexpression(findNode.simple('i;'), 'int?'); } Future test_simpleIdentifier_getter() async { await analyze(''' class _C { int get i => 1; f() => i; } '''); visitSubexpression(findNode.simple('i;'), 'int'); } Future test_simpleIdentifier_getter_nullable() async { await analyze(''' class _C { int/*?*/ get i => 1; f() => i; } '''); visitSubexpression(findNode.simple('i;'), 'int?'); } Future test_simpleIdentifier_localVariable_nonNullable() async { await analyze(''' _f(int x) { return x; } '''); visitSubexpression(findNode.simple('x;'), 'int'); } Future test_simpleIdentifier_localVariable_nullable() async { await analyze(''' _f(int/*?*/ x) { return x; } '''); visitSubexpression(findNode.simple('x;'), 'int?'); } Future test_simpleIdentifier_null_check_hint() async { await analyze('int/*?*/ _f(int/*?*/ x) => x/*!*/;'); var xRef = findNode.simple('x/*!*/'); visitSubexpression(xRef, 'int', changes: { xRef: isNodeChangeForExpression.havingNullCheckWithInfo(isInfo( NullabilityFixDescription.checkExpressionDueToHint, {FixReasonTarget.root: TypeMatcher()})) }); } Future test_stringLiteral() async { await analyze(''' f() => 'foo'; '''); visitSubexpression(findNode.stringLiteral("'foo'"), 'String'); } Future test_suspicious_cast() async { await analyze(''' int f(Object o) { if (o is! String) return 0; return o; } '''); var xRef = findNode.simple('o;'); visitSubexpression(xRef, 'int', changes: { xRef: isNodeChangeForExpression.havingIndroduceAsWithInfo( 'int', isInfo(NullabilityFixDescription.otherCastExpression, {FixReasonTarget.root: isEdge})) }); } Future test_symbolLiteral() async { await analyze(''' f() => #foo; '''); visitSubexpression(findNode.symbolLiteral('#foo'), 'Symbol'); } Future test_throw_flow() async { await analyze(''' _f(int/*?*/ i) { if (i == null) throw 'foo'; i + 1; } '''); visitStatement(findNode.block('{')); } Future test_throw_nullable() async { await analyze(''' _f(int/*?*/ i) => throw i; '''); visitSubexpression(findNode.throw_('throw'), 'Never', changes: {findNode.simple('i;'): isNullCheck}); } Future test_throw_simple() async { await analyze(''' _f() => throw 'foo'; '''); visitSubexpression(findNode.throw_('throw'), 'Never'); } Future test_typeName_dynamic() async { await analyze(''' void _f() { dynamic d = null; } '''); visitTypeAnnotation(findNode.typeAnnotation('dynamic'), 'dynamic'); } Future test_typeName_futureOr_dynamic_nullable() async { await analyze(''' import 'dart:async'; void _f() { FutureOr x = null; } '''); // The type of `x` should be `FutureOr?`, but this is equivalent to // `FutureOr`, so we don't add a `?`. Note: expected type is // still `FutureOr?`; we don't go to extra effort to remove the // redundant `?` from the internal type representation, just from the source // code we generate. visitTypeAnnotation( findNode.typeAnnotation('FutureOr x'), 'FutureOr?', changes: {}); } Future test_typeName_futureOr_inner() async { await analyze(''' import 'dart:async'; void _f(FutureOr x) { FutureOr y = x; } '''); visitTypeAnnotation( findNode.typeAnnotation('FutureOr y'), 'FutureOr', changes: {findNode.typeAnnotation('int> y'): isMakeNullable}); } Future test_typeName_futureOr_null_nullable() async { await analyze(''' import 'dart:async'; void _f() { FutureOr x = null; } '''); // The type of `x` should be `FutureOr?`, but this is equivalent to // `FutureOr`, so we don't add a `?`. Note: expected type is // still `FutureOr?`; we don't go to extra effort to remove the // redundant `?` from the internal type representation, just from the source // code we generate. visitTypeAnnotation( findNode.typeAnnotation('FutureOr x'), 'FutureOr?', changes: {}); } Future test_typeName_futureOr_outer() async { await analyze(''' import 'dart:async'; void _f(FutureOr/*?*/ x) { FutureOr y = x; } '''); var typeAnnotation = findNode.typeAnnotation('FutureOr y'); visitTypeAnnotation(typeAnnotation, 'FutureOr?', changes: {typeAnnotation: isMakeNullable}); } Future test_typeName_futureOr_redundant() async { await analyze(''' import 'dart:async'; void _f(bool b, FutureOr/*?*/ x, FutureOr y) { FutureOr z = b ? x : y; } '''); // The type of `z` should be `FutureOr?`, but this is equivalent to // `FutureOr`, so we only add the first `?`. Note: expected type is // still `FutureOr?`; we don't go to extra effort to remove the // redundant `?` from the internal type representation, just from the source // code we generate. visitTypeAnnotation( findNode.typeAnnotation('FutureOr z'), 'FutureOr?', changes: {findNode.typeAnnotation('int> z'): isMakeNullable}); } Future test_typeName_futureOr_void_nullable() async { await analyze(''' import 'dart:async'; void _f() { FutureOr x = null; } '''); // The type of `x` should be `FutureOr?`, but this is equivalent to // `FutureOr`, so we don't add a `?`. Note: expected type is // still `FutureOr?`; we don't go to extra effort to remove the // redundant `?` from the internal type representation, just from the source // code we generate. visitTypeAnnotation( findNode.typeAnnotation('FutureOr x'), 'FutureOr?', changes: {}); } Future test_typeName_generic_nonNullable() async { await analyze(''' void _f() { List i = [0]; } '''); visitTypeAnnotation(findNode.typeAnnotation('List'), 'List'); } Future test_typeName_generic_nullable() async { await analyze(''' void _f() { List i = null; } '''); var listIntAnnotation = findNode.typeAnnotation('List'); visitTypeAnnotation(listIntAnnotation, 'List?', changes: {listIntAnnotation: isMakeNullable}); } Future test_typeName_generic_nullable_arg() async { await analyze(''' void _f() { List i = [null]; } '''); visitTypeAnnotation(findNode.typeAnnotation('List'), 'List', changes: {findNode.typeAnnotation('int'): isMakeNullable}); } Future test_typeName_generic_nullable_arg_and_outer() async { await analyze(''' void _f(bool b) { List i = b ? [null] : null; } '''); var listInt = findNode.typeAnnotation('List'); visitTypeAnnotation(listInt, 'List?', changes: { findNode.typeAnnotation('int'): isMakeNullable, listInt: isMakeNullable }); } Future test_typeName_simple_nonNullable() async { await analyze(''' void _f() { int i = 0; } '''); var typeAnnotation = findNode.typeAnnotation('int'); visitTypeAnnotation(typeAnnotation, 'int', informative: {typeAnnotation: isExplainNonNullable}); } Future test_typeName_simple_nonNullable_by_context() async { await analyze(''' class C extends Object {} '''); visitTypeAnnotation(findNode.typeAnnotation('Object'), 'Object', informative: isEmpty); } Future test_typeName_simple_nullable() async { await analyze(''' void _f() { int i = null; } '''); var intAnnotation = findNode.typeAnnotation('int'); visitTypeAnnotation((intAnnotation), 'int?', changes: {intAnnotation: isMakeNullable}); } Future test_typeName_void() async { await analyze(''' void _f() { return; } '''); visitTypeAnnotation(findNode.typeAnnotation('void'), 'void'); } Future test_use_of_dynamic() async { // Use of `dynamic` in a context requiring non-null is not explicitly null // checked. await analyze(''' bool _f(dynamic d, bool b) => d && b; '''); visitSubexpression(findNode.binary('&&'), 'bool'); } Future test_variableDeclaration_typed_initialized_nonNullable() async { await analyze(''' void _f() { int x = 0; } '''); visitStatement(findNode.statement('int x')); } Future test_variableDeclaration_typed_initialized_nullable() async { await analyze(''' void _f() { int x = null; } '''); visitStatement(findNode.statement('int x'), changes: {findNode.typeAnnotation('int'): isMakeNullable}); } Future test_variableDeclaration_typed_uninitialized() async { await analyze(''' void _f() { int x; } '''); visitStatement(findNode.statement('int x')); } Future test_variableDeclaration_untyped_initialized() async { await analyze(''' void _f() { var x = 0; } '''); visitStatement(findNode.statement('var x')); } Future test_variableDeclaration_untyped_uninitialized() async { await analyze(''' void _f() { var x; } '''); visitStatement(findNode.statement('var x')); } Future test_variableDeclaration_visit_initializer() async { await analyze(''' void _f(bool/*?*/ x, bool/*?*/ y) { bool z = x && y; } '''); visitStatement(findNode.statement('bool z'), changes: { findNode.simple('x &&'): isNullCheck, findNode.simple('y;'): isNullCheck }); } void visitAll( {Map changes = const {}, Map> problems = const >{}, bool injectNeedsIterableExtension = false}) { var fixBuilder = _createFixBuilder(testUnit); if (injectNeedsIterableExtension) { fixBuilder.needsIterableExtension = true; } fixBuilder.visitAll(); expect(scopedChanges(fixBuilder, testUnit), changes); expect(scopedProblems(fixBuilder, testUnit), problems); } void visitAssignmentTarget( Expression node, String expectedReadType, String expectedWriteType, {Map changes = const {}, Map> problems = const >{}}) { var fixBuilder = _createFixBuilder(node); fixBuilder.visitAll(); var targetInfo = _computeAssignmentTargetInfo(node, fixBuilder); if (expectedReadType == null) { expect(targetInfo.readType, null); } else { expect(targetInfo.readType.getDisplayString(withNullability: true), expectedReadType); } expect(targetInfo.writeType.getDisplayString(withNullability: true), expectedWriteType); expect(scopedChanges(fixBuilder, node), changes); expect(scopedProblems(fixBuilder, node), problems); } void visitStatement(Statement node, {Map changes = const {}, Map> problems = const >{}}) { var fixBuilder = _createFixBuilder(node); fixBuilder.visitAll(); expect(scopedChanges(fixBuilder, node), changes); expect(scopedProblems(fixBuilder, node), problems); } FixBuilder visitSubexpression(Expression node, String expectedType, {Map changes = const {}, Map> problems = const >{}, bool warnOnWeakCode = false}) { var fixBuilder = _createFixBuilder(node, warnOnWeakCode: warnOnWeakCode); fixBuilder.visitAll(); var type = node.staticType; expect(type.getDisplayString(withNullability: true), expectedType); expect(scopedChanges(fixBuilder, node), changes); expect(scopedProblems(fixBuilder, node), problems); return fixBuilder; } void visitTypeAnnotation(TypeAnnotation node, String expectedType, {Map changes = const {}, Map> problems = const >{}, dynamic informative = anything}) { var fixBuilder = _createFixBuilder(node); fixBuilder.visitAll(); var type = node.type; expect(type.getDisplayString(withNullability: true), expectedType); expect(scopedChanges(fixBuilder, node), changes); expect(scopedProblems(fixBuilder, node), problems); expect(scopedInformative(fixBuilder, node), informative); } AssignmentTargetInfo _computeAssignmentTargetInfo( Expression node, FixBuilder fixBuilder) { try { assert( identical(ElementTypeProvider.current, const ElementTypeProvider())); ElementTypeProvider.current = fixBuilder.migrationResolutionHooks; var assignment = node.thisOrAncestorOfType(); var readType = assignment.readType; var writeType = assignment.writeType; return AssignmentTargetInfo(readType, writeType); } finally { ElementTypeProvider.current = const ElementTypeProvider(); } } FixBuilder _createFixBuilder(AstNode scope, {bool warnOnWeakCode = false}) { var unit = scope.thisOrAncestorOfType(); var definingLibrary = unit.declaredElement.library; return FixBuilder( testSource, decoratedClassHierarchy, typeProvider, typeSystem, variables, definingLibrary, null, scope.thisOrAncestorOfType(), warnOnWeakCode, graph, {}); } bool _isInScope(AstNode node, AstNode scope) { return node .thisOrAncestorMatching((ancestor) => identical(ancestor, scope)) != null; } static Matcher isConditionalWithKnownValue(bool knownValue) => TypeMatcher() .having((c) => c.conditionValue, 'conditionValue', knownValue); } extension _NodeChangeForExpressionExtension on TypeMatcher { TypeMatcher havingExpressionChange( dynamic changeMatcher, dynamic infoMatcher) => having((c) => c.expressionChanges.single, 'expressionChanges.single', changeMatcher) .having((c) => c.expressionChangeInfos.single, 'expressionChangeInfos.single', infoMatcher); TypeMatcher havingNullCheckWithInfo(dynamic matcher) => havingExpressionChange(TypeMatcher(), matcher); TypeMatcher havingNoValidMigrationWithInfo(dynamic matcher) => havingExpressionChange(TypeMatcher(), matcher); TypeMatcher havingIndroduceAsWithInfo( dynamic typeStringMatcher, dynamic infoMatcher) => havingExpressionChange( TypeMatcher().having( (c) => c.type.toString(), 'type (string)', typeStringMatcher), infoMatcher); }