@ryoppippi

satisfies で exhaustiveness check

16 Jun 2024 ・ 3 min read


TL;DR

type A = 'a' | 'b' | 'c';

function exhaustive(v: A) {
	switch (v) {
		case 'a':
			return 'A';
		case 'b':
			return 'B';
		case 'c':
			return 'C';
		default:
			return v satisfies never; // check exhaustiveness
	}
}

はじめに

TypeScriptにsatisfies文が​追加されて久しいですね。

satisfiesが​導入される​前は、switch 文の​ exhaustiveness (​網羅性) チェックを​行う​ために、​以下のような​実装を​よくしていました。

type A = 'a' | 'b' | 'c';

function exhaustive(v: A) {
	switch (v) {
		case 'a':
			return 'A';
		case 'b':
			return 'B';
		case 'c':
			return 'C';
	}
	const _: never = v;
}

または​ if 文を​使って​:

type A = 'a' | 'b' | 'c';

function exhaustive(v: A) {
	if (v === 'a') { return 'A'; }
	if (v === 'b') { return 'B'; }
	if (v === 'c') { return 'C'; }
	const _: never = v;
}

これに​より、もし switch 文や​ if 文の​条件分岐が​変数 v に​対して​網羅的でない​場合、never 型に​ v を​代入する​ことで、​コンパイルエラーを​発生させる​ことができます。

しかし、​いく​つか​問題が​ありました。

  • switch 文の​外で​ const _: never = v; を​書くのは​なんと​なく​気持ち悪い​( case の​中で​値を​宣言するのは​ no-case-declarations 違反なので​できない​)
  • eslint の​ no-unused-vars などの​ルールに​引っかかる​ (一応 _ を​除外する​設定を​する​ことも​できるが​)

satisfies で​ exhaustiveness check

TypeScript 4.9 から、satisfies が​導入されました。

これを​使うと、​以下のように​書く​ことができます。

type A = 'a' | 'b' | 'c';

function exhaustive(v: A) {
	switch (v) {
		case 'a':
			return 'A';
		case 'b':
			return 'B';
		case 'c':
			return 'C';
		default:
			return v satisfies never; // check exhaustiveness
	}
}

こちらの​方が​すっきりと​していますね。 また、eslint の​ルールにも​引っかからないので、​煩わしい​エラーとも​おさらばできます。

switch(true) との​組み合わせ

TypeScript 5.3 より、switch(true) に​よる​型の​narrowingが​改善されました。

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#switch-true-narrowing

その​ため、​複雑な​union型に​対しても、switch(true) と​ satisfies を​組み合わせる​ことで、​網羅性チェックを​行うことができます。

type A = [] | 3 | string;

function exhaustive(v: A) {
	switch (true) {
		case Array.isArray(v):
			return '[]';
		case v === 3:
			return '3';
		case typeof v === 'string':
			return v;
		default:
			return v satisfies never; // check exhaustiveness
	}
}

おわりに

satisfies いいね

comment on bluesky / twitter
CC BY-NC-SA 4.0 2022-PRESENT © ryoppippi