Migration: add support for cascade expressions.
Change-Id: Iae52bef6a18c9536db3187102566423bc2f1dba8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105325
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/graph_builder.dart b/pkg/analysis_server/lib/src/nullability/graph_builder.dart
index f467a40..232ec55 100644
--- a/pkg/analysis_server/lib/src/nullability/graph_builder.dart
+++ b/pkg/analysis_server/lib/src/nullability/graph_builder.dart
@@ -213,6 +213,13 @@
}
@override
+ DecoratedType visitCascadeExpression(CascadeExpression node) {
+ var type = node.target.accept(this);
+ node.cascadeSections.accept(this);
+ return type;
+ }
+
+ @override
DecoratedType visitClassDeclaration(ClassDeclaration node) {
node.members.accept(this);
return null;
@@ -313,8 +320,9 @@
@override
DecoratedType visitIndexExpression(IndexExpression node) {
DecoratedType targetType;
- if (node.target != null) {
- targetType = _handleAssignment(_notNullType, node.target);
+ var target = node.realTarget;
+ if (target != null) {
+ targetType = _handleAssignment(_notNullType, target);
}
var callee = node.staticElement;
if (callee == null) {
@@ -353,12 +361,17 @@
@override
DecoratedType visitMethodInvocation(MethodInvocation node) {
DecoratedType targetType;
- if (node.target != null) {
- if (node.operator.type != TokenType.PERIOD) {
- throw new UnimplementedError('TODO(paulberry)');
+ var target = node.realTarget;
+ if (target != null) {
+ switch (node.operator.type) {
+ case TokenType.PERIOD:
+ case TokenType.PERIOD_PERIOD:
+ _checkNonObjectMember(node.methodName.name); // TODO(paulberry)
+ targetType = _handleAssignment(_notNullType, target);
+ break;
+ default:
+ throw new UnimplementedError('TODO(paulberry)');
}
- _checkNonObjectMember(node.methodName.name); // TODO(paulberry)
- targetType = _handleAssignment(_notNullType, node.target);
}
var callee = node.methodName.staticElement;
if (callee == null) {
@@ -423,7 +436,8 @@
@override
DecoratedType visitPropertyAccess(PropertyAccess node) {
- return _handlePropertyAccess(node.target, node.operator, node.propertyName);
+ return _handlePropertyAccess(
+ node.realTarget, node.operator, node.propertyName);
}
@override
@@ -568,21 +582,24 @@
DecoratedType _handlePropertyAccess(
Expression target, Token operator, SimpleIdentifier propertyName) {
- if (operator.type != TokenType.PERIOD) {
- throw new UnimplementedError('TODO(paulberry)');
- }
- _checkNonObjectMember(propertyName.name); // TODO(paulberry)
- var targetType = _handleAssignment(_notNullType, target);
- var callee = propertyName.staticElement;
- if (callee == null) {
- throw new UnimplementedError('TODO(paulberry)');
- }
- var calleeType = getOrComputeElementType(callee, targetType: targetType);
- // TODO(paulberry): substitute if necessary
- if (propertyName.inSetterContext()) {
- return calleeType.positionalParameters[0];
- } else {
- return calleeType.returnType;
+ switch (operator.type) {
+ case TokenType.PERIOD:
+ case TokenType.PERIOD_PERIOD:
+ _checkNonObjectMember(propertyName.name); // TODO(paulberry)
+ var targetType = _handleAssignment(_notNullType, target);
+ var callee = propertyName.staticElement;
+ if (callee == null) {
+ throw new UnimplementedError('TODO(paulberry)');
+ }
+ var calleeType =
+ getOrComputeElementType(callee, targetType: targetType);
+ // TODO(paulberry): substitute if necessary
+ if (propertyName.inSetterContext()) {
+ return calleeType.positionalParameters[0];
+ }
+ return calleeType.returnType;
+ default:
+ throw new UnimplementedError('TODO(paulberry)');
}
}
diff --git a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
index daa4bed..b17cff6 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -122,6 +122,20 @@
hard: true);
}
+ test_assignmentExpression_field_cascaded() async {
+ await analyze('''
+class C {
+ int x = 0;
+}
+void f(C c, int i) {
+ c..x = i;
+}
+''');
+ assertEdge(decoratedTypeAnnotation('int i').node,
+ decoratedTypeAnnotation('int x').node,
+ hard: true);
+ }
+
test_assignmentExpression_field_target_check() async {
await analyze('''
class C {
@@ -135,6 +149,19 @@
checkExpression('c.x'), decoratedTypeAnnotation('C c').node);
}
+ test_assignmentExpression_field_target_check_cascaded() async {
+ await analyze('''
+class C {
+ int x = 0;
+}
+void f(C c, int i) {
+ c..x = i;
+}
+''');
+ assertNullCheck(
+ checkExpression('c..x'), decoratedTypeAnnotation('C c').node);
+ }
+
test_assignmentExpression_indexExpression_index() async {
await analyze('''
class C {
@@ -318,6 +345,18 @@
assertNoUpstreamNullability(decoratedTypeAnnotation('bool').node);
}
+ test_cascadeExpression() async {
+ await analyze('''
+class C {
+ int x = 0;
+}
+C f(C c, int i) => c..x = i;
+''');
+ assertEdge(decoratedTypeAnnotation('C c').node,
+ decoratedTypeAnnotation('C f').node,
+ hard: false);
+ }
+
test_conditionalExpression_condition_check() async {
await analyze('''
int f(bool b, int i, int j) {
@@ -680,6 +719,18 @@
hard: true);
}
+ test_indexExpression_index_cascaded() async {
+ await analyze('''
+class C {
+ int operator[](int i) => 1;
+}
+C f(C c, int j) => c..[j];
+''');
+ assertEdge(decoratedTypeAnnotation('int j').node,
+ decoratedTypeAnnotation('int i').node,
+ hard: true);
+ }
+
test_indexExpression_return_type() async {
await analyze('''
class C {
@@ -702,6 +753,37 @@
assertNullCheck(checkExpression('c['), decoratedTypeAnnotation('C c').node);
}
+ test_indexExpression_target_check_cascaded() async {
+ await analyze('''
+class C {
+ int operator[](int i) => 1;
+}
+C f(C c) => c..[0];
+''');
+ assertNullCheck(
+ checkExpression('c..['), decoratedTypeAnnotation('C c').node);
+ }
+
+ test_indexExpression_target_demonstrates_non_null_intent() async {
+ await analyze('''
+class C {
+ int operator[](int i) => 1;
+}
+int f(C c) => c[0];
+''');
+ assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true);
+ }
+
+ test_indexExpression_target_demonstrates_non_null_intent_cascaded() async {
+ await analyze('''
+class C {
+ int operator[](int i) => 1;
+}
+C f(C c) => c..[0];
+''');
+ assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true);
+ }
+
test_intLiteral() async {
await analyze('''
int f() {
@@ -809,6 +891,20 @@
checkExpression('c.m'), decoratedTypeAnnotation('C c').node);
}
+ test_methodInvocation_target_check_cascaded() async {
+ await analyze('''
+class C {
+ void m() {}
+}
+void test(C c) {
+ c..m();
+}
+''');
+
+ assertNullCheck(
+ checkExpression('c..m'), decoratedTypeAnnotation('C c').node);
+ }
+
test_methodInvocation_target_demonstrates_non_null_intent() async {
await analyze('''
class C {
@@ -822,6 +918,19 @@
assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true);
}
+ test_methodInvocation_target_demonstrates_non_null_intent_cascaded() async {
+ await analyze('''
+class C {
+ void m() {}
+}
+void test(C c) {
+ c..m();
+}
+''');
+
+ assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true);
+ }
+
test_never() async {
await analyze('');
diff --git a/pkg/analysis_server/test/src/nullability/provisional_api_test.dart b/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
index 99c27ce37..ea1081f 100644
--- a/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
+++ b/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
@@ -244,6 +244,26 @@
await _checkSingleFileChanges(content, expected);
}
+ test_data_flow_assignment_field_in_cascade() async {
+ var content = '''
+class C {
+ int x = 0;
+}
+void f(C c) {
+ c..x = null;
+}
+''';
+ var expected = '''
+class C {
+ int? x = 0;
+}
+void f(C c) {
+ c..x = null;
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
test_data_flow_assignment_local() async {
var content = '''
void main() {
@@ -386,6 +406,26 @@
await _checkSingleFileChanges(content, expected);
}
+ test_data_flow_indexed_set_index_value_in_cascade() async {
+ var content = '''
+class C {
+ void operator[]=(int i, int j) {}
+}
+void f(C c) {
+ c..[null] = 0;
+}
+''';
+ var expected = '''
+class C {
+ void operator[]=(int? i, int j) {}
+}
+void f(C c) {
+ c..[null] = 0;
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
test_data_flow_indexed_set_value() async {
var content = '''
class C {
@@ -488,6 +528,26 @@
await _checkSingleFileChanges(content, expected);
}
+ test_data_flow_method_call_in_cascade() async {
+ var content = '''
+class C {
+ void m(int x) {}
+}
+void f(C c) {
+ c..m(null);
+}
+''';
+ var expected = '''
+class C {
+ void m(int? x) {}
+}
+void f(C c) {
+ c..m(null);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
test_data_flow_outward() async {
var content = '''
int f(int i) => null;
@@ -880,6 +940,96 @@
await _checkSingleFileChanges(content, expected);
}
+ test_unconditional_cascaded_indexed_set_implies_non_null_intent() async {
+ var content = '''
+class C {
+ operator[]=(int i, int j) {}
+}
+void f(C c) {
+ c..[1] = 2;
+}
+void g(bool b, C c) {
+ if (b) f(c);
+}
+main() {
+ g(false, null);
+}
+''';
+ var expected = '''
+class C {
+ operator[]=(int i, int j) {}
+}
+void f(C c) {
+ c..[1] = 2;
+}
+void g(bool b, C? c) {
+ if (b) f(c!);
+}
+main() {
+ g(false, null);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ test_unconditional_cascaded_method_call_implies_non_null_intent() async {
+ var content = '''
+void f(int i) {
+ i..abs();
+}
+void g(bool b, int i) {
+ if (b) f(i);
+}
+main() {
+ g(false, null);
+}
+''';
+ var expected = '''
+void f(int i) {
+ i..abs();
+}
+void g(bool b, int? i) {
+ if (b) f(i!);
+}
+main() {
+ g(false, null);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ test_unconditional_cascaded_property_set_implies_non_null_intent() async {
+ var content = '''
+class C {
+ int x = 0;
+}
+void f(C c) {
+ c..x = 1;
+}
+void g(bool b, C c) {
+ if (b) f(c);
+}
+main() {
+ g(false, null);
+}
+''';
+ var expected = '''
+class C {
+ int x = 0;
+}
+void f(C c) {
+ c..x = 1;
+}
+void g(bool b, C? c) {
+ if (b) f(c!);
+}
+main() {
+ g(false, null);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
test_unconditional_method_call_implies_non_null_intent() async {
var content = '''
void f(int i) {