Dartのflow analysisについて理解する
TL;DR
-
params.value
でnullable判定できなくなったらローカル変数に定義し直したらエラー回避できるよ
Dartにてnullableなプロパティを扱うことはままあるでしょう。
そのなかで以下のコードではnullチェックしているのに、nullableな判定が外れず、null assertionが必要になることがあります。
if (params.value != null)
Hoge(value: params.value) // error! need `value!`
しかし、これはローカル変数に再定義することで型エラーは解消されます。
final value = params.value;
if (value != null)
Hoge(value: value) // no error
この挙動について整理してみようと思います。
flow analysis
定義されていた型から、ステートメントによって型が promoted (昇格) される動作です。これはDartのコードではよく行われているでしょう。
// With (or without) null safety:
bool isEmptyList(Object object) {
if (object is List) {
return object.isEmpty; // <-- OK!
} else {
return false;
}
}
このコードではObject
型で定義されていたものを is
で判定することで、そのブロック内ではobject
はList
型であると認識されています。
(と、書いたもののほとんど公式ドキュメントのママの説明になっています)
では、この仕様がどう繋がるかというと、flow analysis
は「引数のオブジェクト」や「ローカル変数」には適用されるが、「オブジェクトのフィールド・プロパティ」には適用されない(その能力がない)ということです。
実際に、以下のコードではエラーが発生します。
class Params {
final String? name;
const Params({this.name});
}
class Argument {
final Params? params;
const Argument({required this.params});
}
checkParams(Argument args) {
if (args.params is Params) {
print('params: ${args.params.name}');
}
}
void main() {
final optionalParams = Params();
final args = Argument(params: optionalParams);
checkParams(args);
}
dart.pad で実行すると以下のエラーになります。
// console
Error compiling to JavaScript:
lib/main.dart:15:34:
Error: Property 'name' cannot be accessed on 'Params?' because it is potentially null.
- 'Params' is from 'package:dartpad_sample/main.dart' ('lib/main.dart').
print('params: ${args.params.name}');
^^^^
lib/main.dart:8:17:
Info: 'params' refers to a property so it couldn't be promoted.
final Params? params;
^
Error: Compilation failed.
では checkParams(Argument args)
をcheckParams(Params params)
に変えてみましょう。
checkParams(Params params) {
if (params is Params) {
print('params: ${params.name}');
}
}
void main() {
final optionalParams = Params();
checkParams(optionalParams);
}
エラーは発生せず、flow analysis
によって、non-nullableにpromotedされ、実行されました。
// console
params: null
これが先程の「引数のオブジェクト」のケースです。
では、次はローカル変数で試してみましょう。
checkParams(Argument args) {
final params = args.params;
if (params is Params) {
print('params: ${params.name}');
}
}
void main() {
final optionalParams = Params();
final args = Argument(params: optionalParams);
checkParams(args);
}
checkParams
にローカル変数(params
)を定義して取り回しました。
すると同様に、flow analysis
によって、non-nullableにpromotedされ、実行されまし
params: null
まとめ
Dartの型解析の仕様・動作を踏まえておくと、適切な形で型定義ができ、不用意な Non-Null Assertion Operator(!
) を呼び出さなくて済みます。
不用意な Non-Null Assertion Operator はNullチェックのエラー(Null check operator used on a null value
)に繋がりますので、できるだけ!
は避けてコードを書いていきましょう!
Discussion