Announcing TypeScript 3.7 Beta に “Optional Chaining” と “Nullish Coalescing” という待望の機能とともに “Assertion Functions” が登場しています。
これは従来からある User-Defined Type Guard と似ていて、こちらは条件に合わない場合に例外を投げるような、別言語での assert 構文・関数と似たものを TypeScript のフロー解析に入れる機能追加です。
Assertion Functions については TypeScript 3.7の asserts x is T 型はどのように危険なのか が全体的に詳しいです。
この投稿の本題は、それの制約に関するメモです。 Assertion Functions を使ってトリッキーなことをしようとして阻まれたわけですが、日本語の記事により救われる時間があると思ったので紹介します。
関連した Pull Request は Error when assertion function calls aren’t CFA’d です。
ここのサンプルコードが物語っていますが、 Assertion Functions の(現時点での)制約に関するものです。コードを引用します。
1 | class Test { |
t.ts(7,5): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation. |
Error
と Ok
の例が列挙されています。一言で言えば「明示的に型を指定している変数・関数でない Assertion Functions はエラーになる」ということです。
assert1
と assert2
は似ていますが、前者は代入の右辺に型を、後者は代入の左辺に型を書きており、後者は「明示的に」変数に型を指定しています。(このくらい許して)
t1
と t2
はどちらもクラスインスタンスですが、これも明示的かどうかという点で同じです。(このくらい許して)
getAssert()
は、変数(や関数)でないのが原因です。サンプルを拡張してみます。
1 | type Assert = (value: unknown) => asserts value; |
<Assert>
のようにキャストなどで型を明示的にしても駄目で、変数(や関数)になっていることが条件です。
例えば Jest みたいな expect(a).toBe(b)
のような書き方の場合に a
に対して直接 Assertion Functions を適用したくてもコンパイルエラーでできず、 const foo: Expect<A> = expect(a); foo(b);
という手順を踏まなければ Assert は機能しません。(当然ながら) foo
への Assert でしかないので a
の型が変わるということはないので、やりたいことができず意味がありません。
Assertion Functions 自体のPull Request では this 型を自己編集する 例を始めトリッキーな用法ができるのではと示されていましたが、このような推論も起こりません。今後の開発次第ですが、 Assert 目的外のトリッキーなことは難しそうです