Allow non-nullable usage to imply non-null intent.
If a method parameter is used in unconditional control flow in a way
that a `null` value would directly lead to an exception (i.e. by
dereferencing it, or by passing it to a method that requires a
non-nullable value), this is treated as implying that the method
parameter is intended to be non-nullable.
Change-Id: I4f55e4c95b3cfaee0a2ba9367b47d51083e0b7b1
Reviewed-on: https://dart-review.googlesource.com/c/93363
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart b/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
index 417bf77..4beb041 100644
--- a/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
+++ b/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
@@ -441,6 +441,14 @@
} finally {
_guards.removeLast();
}
+ if (!_inConditionalControlFlow && destinationType.nullable == null) {
+ // The destination type can never be nullable so this demonstrates
+ // non-null intent.
+ var nonNullIntent = sourceType.nonNullIntent;
+ if (nonNullIntent != null) {
+ _recordFact(nonNullIntent);
+ }
+ }
}
// TODO(paulberry): it's a cheat to pass in expression=null for the
// recursive checks. Really we want to unify all the checks in a single
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 d01508f..1f045cc 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -648,6 +648,19 @@
checkExpression('c.m').nullCheck);
}
+ test_methodInvocation_target_demonstrates_non_null_intent() async {
+ await analyze('''
+class C {
+ void m() {}
+}
+void test(C c) {
+ c.m();
+}
+''');
+
+ assertConstraint([], decoratedTypeAnnotation('C c').nonNullIntent);
+ }
+
test_parenthesizedExpression() async {
await analyze('''
int f() {
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 cf56fef..2fc92fd 100644
--- a/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
+++ b/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
@@ -108,6 +108,58 @@
await _checkSingleFileChanges(content, expected);
}
+ test_conditional_dereference_does_not_imply_non_null_intent() async {
+ var content = '''
+void f(bool b, int i) {
+ if (b) i.abs();
+}
+void g(bool b, int i) {
+ if (b) f(b, i);
+}
+main() {
+ g(false, null);
+}
+''';
+ var expected = '''
+void f(bool b, int? i) {
+ if (b) i!.abs();
+}
+void g(bool b, int? i) {
+ if (b) f(b, i);
+}
+main() {
+ g(false, null);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ test_conditional_non_null_usage_does_not_imply_non_null_intent() async {
+ var content = '''
+void f(bool b, int i, int j) {
+ if (b) i.gcd(j);
+}
+void g(bool b, int i, int j) {
+ if (b) f(b, i, j);
+}
+main() {
+ g(false, 0, null);
+}
+''';
+ var expected = '''
+void f(bool b, int i, int? j) {
+ if (b) i.gcd(j!);
+}
+void g(bool b, int i, int? j) {
+ if (b) f(b, i, j);
+}
+main() {
+ g(false, 0, null);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
test_data_flow_generic_inward() async {
var content = '''
class C<T> {
@@ -631,6 +683,58 @@
''';
await _checkSingleFileChanges(content, expected);
}
+
+ test_unconditional_dereference_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_non_null_usage_implies_non_null_intent() async {
+ var content = '''
+void f(int i, int j) {
+ i.gcd(j);
+}
+void g(bool b, int i, int j) {
+ if (b) f(i, j);
+}
+main() {
+ g(false, 0, null);
+}
+''';
+ var expected = '''
+void f(int i, int j) {
+ i.gcd(j);
+}
+void g(bool b, int i, int? j) {
+ if (b) f(i, j!);
+}
+main() {
+ g(false, 0, null);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
}
@reflectiveTest
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
index 94f6ed8..8aa9bd3 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -218,6 +218,8 @@
int operator ~();
+ int gcd(int other);
+
external static int parse(String source,
{int radix, int onError(String source)});
}