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>
This commit is contained in:
Paul Berry 2019-02-16 13:35:48 +00:00 committed by commit-bot@chromium.org
parent 24928ffcef
commit ab38cf7897
4 changed files with 127 additions and 0 deletions

View file

@ -441,6 +441,14 @@ class ConstraintGatherer extends GeneralizingAstVisitor<DecoratedType> {
} 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

View file

@ -648,6 +648,19 @@ void test(C c) {
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() {

View file

@ -108,6 +108,58 @@ main() {
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> {
@ -628,6 +680,58 @@ void g(bool b, int? i) {
main() {
g(false, null);
}
''';
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);
}

View file

@ -218,6 +218,8 @@ abstract class int extends num {
int operator ~();
int gcd(int other);
external static int parse(String source,
{int radix, int onError(String source)});
}