Migration: add FixBuilder support for prefix/postfix expressions.
Change-Id: I62a705af1c8d9944a417d074373aca4c93e99b6d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/121402
Reviewed-by: Mike Fairhurst <mfairhurst@google.com>
diff --git a/pkg/nnbd_migration/lib/src/fix_builder.dart b/pkg/nnbd_migration/lib/src/fix_builder.dart
index cf44fad..f3abc57 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -335,6 +335,49 @@
}
@override
+ DartType visitPostfixExpression(PostfixExpression node) {
+ if (node.operator.type == TokenType.BANG) {
+ throw UnimplementedError(
+ 'TODO(paulberry): re-migration of already migrated code not '
+ 'supported yet');
+ } else {
+ var targetInfo = visitAssignmentTarget(node.operand);
+ _handleIncrementOrDecrement(node.staticElement, targetInfo, node);
+ return targetInfo.readType;
+ }
+ }
+
+ @override
+ DartType visitPrefixExpression(PrefixExpression node) {
+ var operand = node.operand;
+ switch (node.operator.type) {
+ case TokenType.BANG:
+ visitSubexpression(operand, _typeProvider.boolType);
+ _flowAnalysis.logicalNot_end(node, operand);
+ return _typeProvider.boolType;
+ case TokenType.MINUS:
+ case TokenType.TILDE:
+ var targetType = visitSubexpression(operand, _typeProvider.objectType);
+ var staticElement = node.staticElement;
+ if (staticElement == null) {
+ return _typeProvider.dynamicType;
+ } else {
+ var methodType =
+ _computeMigratedType(staticElement, targetType: targetType)
+ as FunctionType;
+ return methodType.returnType;
+ }
+ break;
+ case TokenType.PLUS_PLUS:
+ case TokenType.MINUS_MINUS:
+ return _handleIncrementOrDecrement(
+ node.staticElement, visitAssignmentTarget(operand), node);
+ default:
+ throw StateError('Unexpected prefix operator: ${node.operator}');
+ }
+ }
+
+ @override
DartType visitSimpleIdentifier(SimpleIdentifier node) {
assert(!node.inSetterContext(),
'Should use visitAssignmentTarget in setter contexts');
@@ -496,6 +539,26 @@
}
}
+ DartType _handleIncrementOrDecrement(MethodElement combiner,
+ AssignmentTargetInfo targetInfo, Expression node) {
+ DartType combinedType;
+ if (combiner == null) {
+ combinedType = _typeProvider.dynamicType;
+ } else {
+ if (_typeSystem.isNullable(targetInfo.readType)) {
+ addProblem(node, const CompoundAssignmentReadNullable());
+ }
+ var combinerType = _computeMigratedType(combiner) as FunctionType;
+ combinedType = _fixNumericTypes(combinerType.returnType, node.staticType);
+ }
+ if (_doesAssignmentNeedCheck(
+ from: combinedType, to: targetInfo.writeType)) {
+ addProblem(node, const CompoundAssignmentCombinedNullable());
+ combinedType = _typeSystem.promoteToNonNull(combinedType as TypeImpl);
+ }
+ return combinedType;
+ }
+
/// Visits all the type arguments in a [TypeArgumentList] and returns the
/// types they ger migrated to.
List<DartType> _visitTypeArgumentList(TypeArgumentList arguments) =>
diff --git a/pkg/nnbd_migration/test/fix_builder_test.dart b/pkg/nnbd_migration/test/fix_builder_test.dart
index b7499b5..f25ba0b 100644
--- a/pkg/nnbd_migration/test/fix_builder_test.dart
+++ b/pkg/nnbd_migration/test/fix_builder_test.dart
@@ -698,6 +698,260 @@
visitSubexpression(findNode.binary('&&'), 'bool');
}
+ 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');
+ }
+
+ @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/38833')
+ 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');
+ }
+
+ 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()}
+ });
+ }
+
+ test_postfixExpression_dynamic() async {
+ await analyze('''
+_f(dynamic x) => x++;
+''');
+ visitSubexpression(findNode.postfix('++'), 'dynamic');
+ }
+
+ 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()}
+ });
+ }
+
+ 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');
+ }
+
+ 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'));
+ }
+
+ test_prefixExpression_bang_nonNullable() async {
+ await analyze('''
+_f(bool/*!*/ x) => !x;
+''');
+ visitSubexpression(findNode.prefix('!x'), 'bool');
+ }
+
+ test_prefixExpression_bang_nullable() async {
+ await analyze('''
+_f(bool/*?*/ x) => !x;
+''');
+ visitSubexpression(findNode.prefix('!x'), 'bool',
+ nullChecked: {findNode.simple('x;')});
+ }
+
+ 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?');
+ }
+
+ 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');
+ }
+
+ 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()}
+ });
+ }
+
+ test_prefixExpression_intRules() async {
+ await analyze('''
+_f(int x) => ++x;
+''');
+ visitSubexpression(findNode.prefix('++'), 'int');
+ }
+
+ 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()}
+ });
+ }
+
+ test_prefixExpression_minus_dynamic() async {
+ await analyze('''
+_f(dynamic x) => -x;
+''');
+ visitSubexpression(findNode.prefix('-x'), 'dynamic');
+ }
+
+ test_prefixExpression_minus_nonNullable() async {
+ await analyze('''
+_f(int/*!*/ x) => -x;
+''');
+ visitSubexpression(findNode.prefix('-x'), 'int');
+ }
+
+ test_prefixExpression_minus_nullable() async {
+ await analyze('''
+_f(int/*?*/ x) => -x;
+''');
+ visitSubexpression(findNode.prefix('-x'), 'int',
+ nullChecked: {findNode.simple('x;')});
+ }
+
+ test_prefixExpression_minus_substitution() async {
+ await analyze('''
+abstract class _C<T> {
+ List<T> operator-();
+}
+_f(_C<int> x) => -x;
+''');
+ visitSubexpression(findNode.prefix('-x'), 'List<int>');
+ }
+
+ 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');
+ }
+
+ test_prefixExpression_tilde_dynamic() async {
+ await analyze('''
+_f(dynamic x) => ~x;
+''');
+ visitSubexpression(findNode.prefix('~x'), 'dynamic');
+ }
+
+ test_prefixExpression_tilde_nonNullable() async {
+ await analyze('''
+_f(int/*!*/ x) => ~x;
+''');
+ visitSubexpression(findNode.prefix('~x'), 'int');
+ }
+
+ test_prefixExpression_tilde_nullable() async {
+ await analyze('''
+_f(int/*?*/ x) => ~x;
+''');
+ visitSubexpression(findNode.prefix('~x'), 'int',
+ nullChecked: {findNode.simple('x;')});
+ }
+
+ test_prefixExpression_tilde_substitution() async {
+ await analyze('''
+abstract class _C<T> {
+ List<T> operator~();
+}
+_f(_C<int> x) => ~x;
+''');
+ visitSubexpression(findNode.prefix('~x'), 'List<int>');
+ }
+
test_simpleIdentifier_className() async {
await analyze('''
_f() => int;