🧐TypeScriptのexhaustiveness checkをスマートに書く2022/08/14に公開8件JavaScriptTypeScripttechDiscussionYAMAMOTO Yuji2023/01/30 それどこか、逆に分岐漏れがあるときにのみ型エラーが起きてしまう。 「それどころか、逆に分岐漏れがないときにのみ型エラーが起きてしまう。」ですかね。説明しているコードでは action.type が never になるでしょうし。 返信を追加Ryo Ota - @nwtgck2023/02/03に更新偶然 type: "__invalid__" が登場してしまう可能性への対応を考えてみました。 throw new Error(`Unknown type: ${(action as { readonly type: unique symbol} as { type: unknown }).type}`); unique symbol を利用している unique symbol なので readonly が必要 unique symbol を出力するために as { type: unknown } 原案と同じくトランスパイルされるとthrow new Error(`Unknown type: ${action.type}`); になる Playground https://twitter.com/nwtgck_ja/status/1558727404071550978 追記 シンプルに以下で良さそうな気がしてきました。 throw new Error(`Unknown type: ${(action as { type: never }).type}`); Playground luma2024/01/26追記の (action as { type: never }).type は実はすべての構造体にタグ以外の余分なプロパティがある必要があって, type HeadAction = { type: "Head"; }; が候補にあったりすると,すり抜けてしまいます Playground luma2024/01/26また,一個目の action as { readonly type: unique symbol} as { type: unknown }).type} は記事内にあるように @typescript-eslint/restrict-template-expressions を入れていると常にESLintエラーになってしまいます 返信を追加Hideaki Noshiro2023/11/09に更新https://typescript-eslint.io/rules/switch-exhaustiveness-check/ この eslint ルールを有効にすれば default ケースを書かない前提で網羅性チェックが可能だと思うのですが、上の eslint ルールを使用しなくて済むことを重視した手法、あるいは他のメリットがあるという話だったりするのでしょうか? luma2024/01/26冒頭に 分岐が網羅的であることの保証を実行時と型検査時の両方で賢く行う方法 とあるように,実行時にも検査したい,という前提があり, switch-exhaustiveness-check では実行時の検査はできないという点が異なるかと思います メリットで言えば,私の意見ですが, 型が付いてるものの,ユーザーインプットだったり特殊な状況で関係ないものが来る,もしくは関連サービスがアップデートで新たな構造体が入ってくるような変更をして,型で想定しないものが実行時に到達しうる(新しいイベントタイプができました,とか) HMR等で型チェック/ESLintが通ってない状態でもトランスパイルしてとりあえず動かすときに到達しうる みたいなときに,早めに失敗してくれるのが単純に嬉しい (fail fast) と思っています 返信を追加luma2024/01/26に更新TypeScriptのsatisfies使ったら "__invalid__" のような決め打ちなしで行けないかなと試してみたら,以下のようなものができました(なにかしら @typescript-eslint/restrict-template-expressions に引っかからないものを 0 の位置に置く必要はありますが,何をおいても,それが通り抜けていても検知できます) (action satisfies never as { type: 0 }).type 返信を追加geDem2024/04/07初心者質問で恐縮ですが、こちらの方法は const exhaustiveCheck:never = action を記述する方法と比べてどういった点で優れているのでしょうか 返信を追加
YAMAMOTO Yuji2023/01/30 それどこか、逆に分岐漏れがあるときにのみ型エラーが起きてしまう。 「それどころか、逆に分岐漏れがないときにのみ型エラーが起きてしまう。」ですかね。説明しているコードでは action.type が never になるでしょうし。 返信を追加
Ryo Ota - @nwtgck2023/02/03に更新偶然 type: "__invalid__" が登場してしまう可能性への対応を考えてみました。 throw new Error(`Unknown type: ${(action as { readonly type: unique symbol} as { type: unknown }).type}`); unique symbol を利用している unique symbol なので readonly が必要 unique symbol を出力するために as { type: unknown } 原案と同じくトランスパイルされるとthrow new Error(`Unknown type: ${action.type}`); になる Playground https://twitter.com/nwtgck_ja/status/1558727404071550978 追記 シンプルに以下で良さそうな気がしてきました。 throw new Error(`Unknown type: ${(action as { type: never }).type}`); Playground luma2024/01/26追記の (action as { type: never }).type は実はすべての構造体にタグ以外の余分なプロパティがある必要があって, type HeadAction = { type: "Head"; }; が候補にあったりすると,すり抜けてしまいます Playground luma2024/01/26また,一個目の action as { readonly type: unique symbol} as { type: unknown }).type} は記事内にあるように @typescript-eslint/restrict-template-expressions を入れていると常にESLintエラーになってしまいます 返信を追加
luma2024/01/26追記の (action as { type: never }).type は実はすべての構造体にタグ以外の余分なプロパティがある必要があって, type HeadAction = { type: "Head"; }; が候補にあったりすると,すり抜けてしまいます Playground
luma2024/01/26また,一個目の action as { readonly type: unique symbol} as { type: unknown }).type} は記事内にあるように @typescript-eslint/restrict-template-expressions を入れていると常にESLintエラーになってしまいます
Hideaki Noshiro2023/11/09に更新https://typescript-eslint.io/rules/switch-exhaustiveness-check/ この eslint ルールを有効にすれば default ケースを書かない前提で網羅性チェックが可能だと思うのですが、上の eslint ルールを使用しなくて済むことを重視した手法、あるいは他のメリットがあるという話だったりするのでしょうか? luma2024/01/26冒頭に 分岐が網羅的であることの保証を実行時と型検査時の両方で賢く行う方法 とあるように,実行時にも検査したい,という前提があり, switch-exhaustiveness-check では実行時の検査はできないという点が異なるかと思います メリットで言えば,私の意見ですが, 型が付いてるものの,ユーザーインプットだったり特殊な状況で関係ないものが来る,もしくは関連サービスがアップデートで新たな構造体が入ってくるような変更をして,型で想定しないものが実行時に到達しうる(新しいイベントタイプができました,とか) HMR等で型チェック/ESLintが通ってない状態でもトランスパイルしてとりあえず動かすときに到達しうる みたいなときに,早めに失敗してくれるのが単純に嬉しい (fail fast) と思っています 返信を追加
luma2024/01/26冒頭に 分岐が網羅的であることの保証を実行時と型検査時の両方で賢く行う方法 とあるように,実行時にも検査したい,という前提があり, switch-exhaustiveness-check では実行時の検査はできないという点が異なるかと思います メリットで言えば,私の意見ですが, 型が付いてるものの,ユーザーインプットだったり特殊な状況で関係ないものが来る,もしくは関連サービスがアップデートで新たな構造体が入ってくるような変更をして,型で想定しないものが実行時に到達しうる(新しいイベントタイプができました,とか) HMR等で型チェック/ESLintが通ってない状態でもトランスパイルしてとりあえず動かすときに到達しうる みたいなときに,早めに失敗してくれるのが単純に嬉しい (fail fast) と思っています
luma2024/01/26に更新TypeScriptのsatisfies使ったら "__invalid__" のような決め打ちなしで行けないかなと試してみたら,以下のようなものができました(なにかしら @typescript-eslint/restrict-template-expressions に引っかからないものを 0 の位置に置く必要はありますが,何をおいても,それが通り抜けていても検知できます) (action satisfies never as { type: 0 }).type 返信を追加
geDem2024/04/07初心者質問で恐縮ですが、こちらの方法は const exhaustiveCheck:never = action を記述する方法と比べてどういった点で優れているのでしょうか 返信を追加
Discussion
「それどころか、逆に分岐漏れがないときにのみ型エラーが起きてしまう。」ですかね。説明しているコードでは
action.typeがneverになるでしょうし。偶然
type: "__invalid__"が登場してしまう可能性への対応を考えてみました。unique symbolを利用しているunique symbolなのでreadonlyが必要unique symbolを出力するためにas { type: unknown }throw new Error(`Unknown type: ${action.type}`);になる追記
シンプルに以下で良さそうな気がしてきました。
Playground
追記の
(action as { type: never }).typeは実はすべての構造体にタグ以外の余分なプロパティがある必要があって,が候補にあったりすると,すり抜けてしまいます
Playground
また,一個目の
action as { readonly type: unique symbol} as { type: unknown }).type}は記事内にあるように @typescript-eslint/restrict-template-expressions を入れていると常にESLintエラーになってしまいますこの eslint ルールを有効にすれば default ケースを書かない前提で網羅性チェックが可能だと思うのですが、上の eslint ルールを使用しなくて済むことを重視した手法、あるいは他のメリットがあるという話だったりするのでしょうか?
冒頭に
とあるように,実行時にも検査したい,という前提があり, switch-exhaustiveness-check では実行時の検査はできないという点が異なるかと思います
メリットで言えば,私の意見ですが,
みたいなときに,早めに失敗してくれるのが単純に嬉しい (fail fast) と思っています
TypeScriptのsatisfies使ったら
"__invalid__"のような決め打ちなしで行けないかなと試してみたら,以下のようなものができました(なにかしら @typescript-eslint/restrict-template-expressions に引っかからないものを0の位置に置く必要はありますが,何をおいても,それが通り抜けていても検知できます)初心者質問で恐縮ですが、こちらの方法は
const exhaustiveCheck:never = action
を記述する方法と比べてどういった点で優れているのでしょうか