Skip to content

Commit 988f0ce

Browse files
Suggest null-check patterns for type promo (#5679)
Fixes #5666
1 parent 4fe9757 commit 988f0ce

File tree

4 files changed

+50
-16
lines changed

4 files changed

+50
-16
lines changed

examples/misc/lib/effective_dart/usage_good.dart

+10-3
Original file line numberDiff line numberDiff line change
@@ -350,24 +350,31 @@ class Response {
350350
String get reason => '';
351351
}
352352

353-
// #docregion shadow-nullable-field
353+
// #docregion shadow-nullable-field, null-check-promo
354354
class UploadException {
355355
final Response? response;
356356

357357
UploadException([this.response]);
358358

359359
@override
360360
String toString() {
361+
// #enddocregion shadow-nullable-field
362+
if (this.response case var response?) {
363+
return 'Could not complete upload to ${response.url} '
364+
'(error code ${response.errorCode}): ${response.reason}.';
365+
}
366+
// #enddocregion null-check-promo
367+
// #docregion shadow-nullable-field
361368
final response = this.response;
362369
if (response != null) {
363370
return 'Could not complete upload to ${response.url} '
364371
'(error code ${response.errorCode}): ${response.reason}.';
365372
}
366-
373+
// #docregion null-check-promo
367374
return 'Could not upload (no response).';
368375
}
369376
}
370-
// #enddocregion shadow-nullable-field
377+
// #enddocregion shadow-nullable-field, null-check-promo
371378

372379
//----------------------------------------------------------------------------
373380

src/content/effective-dart/_toc.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ the project:
105105
* <a href='/effective-dart/usage#dont-use-an-explicit-default-value-of-null'>DON'T use an explicit default value of <code>null</code>.</a>
106106
* <a href='/effective-dart/usage#dont-use-true-or-false-in-equality-operations'>DON'T use <code>true</code> or <code>false</code> in equality operations.</a>
107107
* <a href='/effective-dart/usage#avoid-late-variables-if-you-need-to-check-whether-they-are-initialized'>AVOID <code>late</code> variables if you need to check whether they are initialized.</a>
108-
* <a href='/effective-dart/usage#consider-assigning-a-nullable-field-to-a-local-variable-to-enable-type-promotion'>CONSIDER assigning a nullable field to a local variable to enable type promotion.</a>
108+
* <a href='/effective-dart/usage#consider-type-promotion-or-null-check-patterns-for-using-nullable-types'>CONSIDER type promotion or null-check patterns for using nullable types.</a>
109109

110110
**Strings**
111111

src/content/effective-dart/usage.md

+38-11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ prevpage:
88
url: /effective-dart/documentation
99
title: Documentation
1010
---
11+
<?code-excerpt plaster="none"?>
1112
<?code-excerpt replace="/([A-Z]\w*)\d\b/$1/g"?>
1213
<?code-excerpt path-base="misc/lib/effective_dart"?>
1314

@@ -325,7 +326,7 @@ Of course, if `null` is a valid initialized value for the variable,
325326
then it probably does make sense to have a separate boolean field.
326327

327328

328-
### CONSIDER assigning a nullable field to a local variable to enable type promotion
329+
### CONSIDER type promotion or null-check patterns for using nullable types
329330

330331
Checking that a nullable variable is not equal to `null` promotes the variable
331332
to a non-nullable type. That lets you access members on the variable and pass it
@@ -337,7 +338,31 @@ private final fields. Values that are open to manipulation
337338

338339
Declaring members [private][] and [final][], as we generally recommend, is often
339340
enough to bypass these limitations. But, that's not always an option.
340-
One pattern to work around this is to assign the field's value
341+
342+
One pattern to work around type promotion limitations is to use a
343+
[null-check pattern][]. This simultaneously confirms the member's value
344+
is not null, and binds that value to a new non-nullable variable of
345+
the same base type.
346+
347+
<?code-excerpt "usage_good.dart (null-check-promo)"?>
348+
```dart tag=good
349+
class UploadException {
350+
final Response? response;
351+
352+
UploadException([this.response]);
353+
354+
@override
355+
String toString() {
356+
if (this.response case var response?) {
357+
return 'Could not complete upload to ${response.url} '
358+
'(error code ${response.errorCode}): ${response.reason}.';
359+
}
360+
return 'Could not upload (no response).';
361+
}
362+
}
363+
```
364+
365+
Another work around is to assign the field's value
341366
to a local variable. Null checks on that variable will promote,
342367
so you can safely treat it as non-nullable.
343368

@@ -355,14 +380,21 @@ class UploadException {
355380
return 'Could not complete upload to ${response.url} '
356381
'(error code ${response.errorCode}): ${response.reason}.';
357382
}
358-
359383
return 'Could not upload (no response).';
360384
}
361385
}
362386
```
363387

364-
Assigning to a local variable can be cleaner and safer than using `!` every
365-
time you need to treat the value as non-null:
388+
Be careful when using a local variable. If you need to write back to the field,
389+
make sure that you don't write back to the local variable instead. (Making the
390+
local variable [`final`][] can prevent such mistakes.) Also, if the field might
391+
change while the local is still in scope, then the local might have a stale
392+
value.
393+
394+
Sometimes it's best to simply [use `!`][] on the field.
395+
In some cases, though, using either a local variable or a null-check pattern
396+
can be cleaner and safer than using `!` every time you need to treat the value
397+
as non-null:
366398

367399
<?code-excerpt "usage_bad.dart (shadow-nullable-field)" replace="/!\./[!!!]./g"?>
368400
```dart tag=bad
@@ -383,15 +415,10 @@ class UploadException {
383415
}
384416
```
385417

386-
Be careful when using a local variable. If you need to write back to the field,
387-
make sure that you don't write back to the local variable instead. (Making the
388-
local variable [`final`][] can prevent such mistakes.) Also, if the field might
389-
change while the local is still in scope, then the local might have a stale
390-
value. Sometimes it's best to simply [use `!`][] on the field.
391-
392418
[can't be type promoted]: /tools/non-promotion-reasons
393419
[private]: /effective-dart/design#prefer-making-declarations-private
394420
[final]: /effective-dart/design#prefer-making-fields-and-top-level-variables-final
421+
[null-check pattern]: /language/pattern-types#null-check
395422
[`final`]: /effective-dart/usage#do-follow-a-consistent-rule-for-var-and-final-on-local-variables
396423
[use `!`]: /null-safety/understanding-null-safety#non-null-assertion-operator
397424

src/content/guides/whats-new.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ we made the following changes to this site:
126126
* Reorganized and simplified site infrastructure across the board, in preparation
127127
to [move away from using Jekyll][].
128128

129-
[type promotion]: /effective-dart/usage#consider-assigning-a-nullable-field-to-a-local-variable-to-enable-type-promotion
129+
[type promotion]: /effective-dart/usage#consider-type-promotion-or-null-check-patterns-for-using-nullable-types
130130
[Understanding Null Safety]: /null-safety/understanding-null-safety
131131
[C interop]: /interop/c-interop#native-assets
132132
[Breaking changes]: /resources/breaking-changes

0 commit comments

Comments
 (0)