TypeScriptでassertsを用いた型アサーションを、const+アロー関数を用いて実装しようとしてつまづいた部分があるので備忘録として記載
おかしな点がありましたらお気軽にコメントお願いします
自身の理解のために書いているので、ある程度TypeScriptを勉強した人であれば自明な内容であるだろうことを事前に断っておきますm(_ _)m
何が起こったか
tsconfigでStrict: trueにしたことで見過ごしていたundefinedの可能性に気づき、この機会に値がNonNullableであることを推論させる型アサーションを書くことにした。
その際、以下のようにconst+assertsを用いて記述した。
const assertConstArrow = <T>(v: T | undefined | null): asserts v is NonNullable<T> => {
if (v === undefined || v === null) {
throw new Error('Unexpected Nullable Value');
};
};
すると、VSCode上で次のような警告が出た。
アサーションでは、呼び出し先のすべての名前が明示的な型の注釈で宣言されている必要があります。
原因
ネットで調べたりChatGPTと壁打ちしたりした結果、変数に値として代入する 関数の型(=シグネチャ) が宣言として与えられてなかったことが原因らしい。
まず、assertsは関数の実装ではなく関数の型(シグネチャ)に対する機能で、使用するにはシグネチャが宣言として与えられていることが必要(ChatGPT情報。ソース見つけ次第更新します)。
超ざっくり言うと、関数の引数や返値などを、TypeScriptが宣言として受け取れる形で記述することが必要となる。
今回の場合constで代入する関数のシグネチャは
<T>(v: T | undefined | null) => asserts v is NonNullable<T>
と表現できる。
今回のコードではシグネチャを代入するアロー関数の外側から与えず、=の内側に記述していた。
そのため、アロー関数の型アノテーションとしては認識されたものの、assertsの要求する宣言されたシグネチャとして扱われなかったよう。
const assertConstArrow /* ここでシグネチャの宣言が必要 */ = /* 内側の記述は宣言されたシグネチャとして扱われない */ <T>(v: T | undefined | null): asserts v is NonNullable<T> => {
if (v === undefined || v === null) {
throw new Error('Unexpected Nullable Value');
};
};
対処&改善
functionを使って実装する。基本はこれで十分
function assertFunction<T>(v: T | undefined | null): asserts v is NonNullable<T> {
if (v === undefined || v === null) {
throw new Error('Unexpected Nullable Value');
};
};
どうしてもconstを使う場合は、以下のように代入する関数のシグネチャを明示的に示す。
const assertConstArrow: <T>(v: T | undefined | null) => asserts v is NonNullable<T> = (v) => {
if (v === undefined || v === null) {
throw new Error('Unexpected Nullable Value');
};
};
asserts以前に
functionは宣言(Statement)であり、書いた段階でシグネチャは宣言として与えられる。
一方アロー関数の変数への代入は代入であり、内側のアロー関数に型注釈をつけても、宣言されたシグネチャとしてはみなされない。
今回の場合、頭の中で以下の2つが同一だと考えていたのが大元の原因。
// assertFunctionのシグネチャは宣言されている
function assertFunction<T>(v: T | undefined | null): asserts v is NonNullable<T> {
if (v === undefined || v === null) {
throw new Error('Unexpected Nullable Value');
};
};
// assertConstArrowのシグネチャは宣言されていない(宣言として扱われない型注釈)
const assertConstArrow = <T>(v: T | undefined | null): asserts v is NonNullable<T> => {
if (v === undefined || v === null) {
throw new Error('Unexpected Nullable Value');
};
};
function宣言的に記述するならこのような表現
// 型が宣言として記述されている
const assertConstArrow: <T>(v: T | undefined | null) => asserts v is NonNullable<T> = (v) => {
if (v === undefined || v === null) {
throw new Error('Unexpected Nullable Value');
};
};
まとめ
- assertsは「関数の実装」ではなく「関数の型(シグネチャ)」に対する機能であり、シグネチャが宣言として与えられている必要あり
- functionはシグネチャの宣言と実装を同時に行うため、特別に記述せずに使える
- アロー関数の代入では=の外側にシグネチャを明示し、宣言として記述することが必要
- 代入する値に対する型注釈(実装の一部)と、外側で関数の型として与える宣言(シグネチャ)は別物
最後まで読んでいただきありがとうございました!!