Migration: finish EdgeBuilder support for prefix/postfix expressions.
A few changes here:
- Prefix expressions didn't support dynamic invocations.
- Prefix expressions didn't support generic types.
- Postfix expressions were handled incorrectly (we used the return
type of operator+ for `x++`, whereas the type should have been the
same as the type of `x`).
Change-Id: I35077b82a6e9c4e4f6ad8bb1264f5d725ec0d9a9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/121401
Reviewed-by: Mike Fairhurst <mfairhurst@google.com>
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index f08cb75..cc52881 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -983,21 +983,7 @@
var operatorType = node.operator.type;
if (operatorType == TokenType.PLUS_PLUS ||
operatorType == TokenType.MINUS_MINUS) {
- _checkExpressionNotNull(node.operand);
- var callee = node.staticElement;
- if (callee is ClassMemberElement &&
- (callee.enclosingElement as ClassElement).typeParameters.isNotEmpty) {
- // TODO(paulberry)
- _unimplemented(node,
- 'Operator ${operatorType.lexeme} defined on a class with type parameters');
- }
- if (callee == null) {
- // TODO(paulberry)
- _unimplemented(node, 'Unresolved operator ${operatorType.lexeme}');
- }
- var calleeType = getOrComputeElementType(callee);
- // TODO(paulberry): substitute if necessary
- return _fixNumericTypes(calleeType.returnType, node.staticType);
+ return _checkExpressionNotNull(node.operand);
}
_unimplemented(
node, 'Postfix expression with operator ${node.operator.lexeme}');
@@ -1020,26 +1006,22 @@
if (operatorType == TokenType.BANG) {
_flowAnalysis.logicalNot_end(node, node.operand);
return _nonNullableBoolType;
- } else if (operatorType == TokenType.PLUS_PLUS ||
- operatorType == TokenType.MINUS_MINUS) {
- var callee = node.staticElement;
- if (callee is ClassMemberElement &&
- (callee.enclosingElement as ClassElement).typeParameters.isNotEmpty) {
- // TODO(paulberry)
- _unimplemented(node,
- 'Operator ${operatorType.lexeme} defined on a class with type parameters');
- }
- if (callee == null) {
- // TODO(paulberry)
- _unimplemented(node, 'Unresolved operator ${operatorType.lexeme}');
- }
- var calleeType = getOrComputeElementType(callee);
- // TODO(paulberry): substitute if necessary
- return _fixNumericTypes(calleeType.returnType, node.staticType);
} else {
var callee = node.staticElement;
+ if (callee == null) {
+ // Dynamic dispatch. The return type is `dynamic`.
+ // TODO(paulberry): would it be better to assume a return type of `Never`
+ // so that we don't unnecessarily propagate nullabilities everywhere?
+ return _dynamicType;
+ }
var calleeType = getOrComputeElementType(callee, targetType: targetType);
- return _handleInvocationArguments(node, [], null, null, calleeType, null);
+ if (operatorType == TokenType.PLUS_PLUS ||
+ operatorType == TokenType.MINUS_MINUS) {
+ return _fixNumericTypes(calleeType.returnType, node.staticType);
+ } else {
+ return _handleInvocationArguments(
+ node, [], null, null, calleeType, null);
+ }
}
}
diff --git a/pkg/nnbd_migration/test/edge_builder_test.dart b/pkg/nnbd_migration/test/edge_builder_test.dart
index f5ad4c3..230ac17 100644
--- a/pkg/nnbd_migration/test/edge_builder_test.dart
+++ b/pkg/nnbd_migration/test/edge_builder_test.dart
@@ -4221,7 +4221,7 @@
assertNullCheck(use, assertEdge(declaration, never, hard: true));
var returnType = decoratedTypeAnnotation('int f').node;
- assertEdge(never, returnType, hard: false);
+ assertEdge(declaration, returnType, hard: false);
}
test_postfixExpression_plusPlus() async {
@@ -4236,7 +4236,37 @@
assertNullCheck(use, assertEdge(declaration, never, hard: true));
var returnType = decoratedTypeAnnotation('int f').node;
- assertEdge(never, returnType, hard: false);
+ assertEdge(declaration, returnType, hard: false);
+ }
+
+ test_postfixExpression_plusPlus_dynamic() async {
+ await analyze('''
+Object f(dynamic d) {
+ return d++;
+}
+''');
+ assertEdge(decoratedTypeAnnotation('dynamic d').node,
+ decoratedTypeAnnotation('Object f').node,
+ hard: false);
+ }
+
+ test_postfixExpression_plusPlus_substituted() async {
+ await analyze('''
+abstract class C<T> {
+ C<T> operator+(int x);
+}
+C<int> f(C<int> c) {
+ return c++;
+}
+''');
+
+ var cType = decoratedTypeAnnotation('C<int> c');
+ var returnType = decoratedTypeAnnotation('C<int> f');
+ assertNullCheck(
+ checkExpression('c++'), assertEdge(cType.node, never, hard: true));
+ assertEdge(cType.node, returnType.node, hard: false);
+ assertEdge(cType.typeArguments[0].node, returnType.typeArguments[0].node,
+ hard: false);
}
test_prefixedIdentifier_field_type() async {
@@ -4339,6 +4369,20 @@
assertEdge(never, return_f, hard: false);
}
+ test_prefixExpression_bang_dynamic() async {
+ await analyze('''
+Object f(dynamic d) {
+ return !d;
+}
+''');
+
+ var nullable_d = decoratedTypeAnnotation('dynamic d').node;
+ var check_d = checkExpression('d;');
+
+ var return_f = decoratedTypeAnnotation('Object f').node;
+ assertEdge(never, return_f, hard: false);
+ }
+
test_prefixExpression_minus() async {
await analyze('''
abstract class C {
@@ -4353,6 +4397,35 @@
assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true));
}
+ test_prefixExpression_minus_dynamic() async {
+ await analyze('''
+Object test(dynamic d) => -d;
+''');
+ assertEdge(always, decoratedTypeAnnotation('Object test').node,
+ hard: false);
+ assertEdge(decoratedTypeAnnotation('dynamic d').node, never, hard: true);
+ }
+
+ test_prefixExpression_minus_substituted() async {
+ await analyze('''
+abstract class C<T> {
+ List<T> operator-();
+}
+List<int> test(C<int> c) => -c/*check*/;
+''');
+ var operatorReturnType = decoratedTypeAnnotation('List<T> operator');
+ var cType = decoratedTypeAnnotation('C<int> c');
+ var testReturnType = decoratedTypeAnnotation('List<int> test');
+ assertEdge(operatorReturnType.node, testReturnType.node, hard: false);
+ assertNullCheck(checkExpression('c/*check*/'),
+ assertEdge(cType.node, never, hard: true));
+ assertEdge(
+ substitutionNode(cType.typeArguments[0].node,
+ operatorReturnType.typeArguments[0].node),
+ testReturnType.typeArguments[0].node,
+ hard: false);
+ }
+
test_prefixExpression_minusMinus() async {
await analyze('''
int f(int i) {
@@ -4383,6 +4456,35 @@
assertEdge(never, returnType, hard: false);
}
+ test_prefixExpression_plusPlus_dynamic() async {
+ await analyze('''
+Object f(dynamic d) {
+ return ++d;
+}
+''');
+ var returnType = decoratedTypeAnnotation('Object f').node;
+ assertEdge(always, returnType, hard: false);
+ }
+
+ test_prefixExpression_plusPlus_substituted() async {
+ await analyze('''
+abstract class C<T> {
+ C<T> operator+(int i);
+}
+C<int> f(C<int> x) => ++x;
+ ''');
+ var xType = decoratedTypeAnnotation('C<int> x');
+ var plusReturnType = decoratedTypeAnnotation('C<T> operator');
+ var fReturnType = decoratedTypeAnnotation('C<int> f');
+ assertEdge(xType.node, never, hard: true);
+ assertEdge(plusReturnType.node, fReturnType.node, hard: false);
+ assertEdge(
+ substitutionNode(
+ xType.typeArguments[0].node, plusReturnType.typeArguments[0].node),
+ fReturnType.typeArguments[0].node,
+ hard: false);
+ }
+
test_propertyAccess_dynamic() async {
await analyze('''
class C {