要約
Typescriptのnever
型を用いたexhaustive checkで型エラー検知用の変数を使わない時に'変数名' is declared but its value is never read.
のエラーが出る場合、tsconfigのnoUnusedLocals
オプションが有効になっていないかを確認するべき。
Typescriptのexhaustiveness checkとは
Typescriptのunion型に種類を追加した時、never
型を用いて既存のif文やswitch文で対応漏れが発生していないかをチェックするexhaustiveness check
というテクニックがあります。
// Typescriptの記事より引用
// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
// 例えば、shape.kindに"triangle"が増えた場合、上のcase文を増やさないと
// ここに到達する可能性が出てくるため、never型と不整合が生じ型エラーが起きる
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
問題と解決
このテクニックでは型検知用の変数を定義してそれをreturnしているが、それはあくまで検知用なので使いたくない場合がある。
例えば上記では_exhaustiveCheck
をランタイムで返すと挙動がおかしくなるのでundefined
を返したいなど。
自分があるプロジェクトでそれをやろうとした時、'_exhaustiveCheck' is declared but its value is never read.
というエラーが出てしまい、最初はLinterのエラーかと思ってESLintのno-unused-vars
を消すなどして時間を消費してしまった。
結果的に、tsconfigでnoUnusedLocals
オプションがオンになっていたためtscエラーを起こしていたというオチだった、、、
このtscのエラーは// @ts-ignore
アノテーションを付与することにより回避できるが、その場合exhaustiveness checkもignoreされて本末転倒な感じになってしまう。このテクニックを使いたいならTypescriptのnoUnusedLocals
オプションはオフにしてeslintのno-unused-vars
に移行した上で// eslint-disable-next-line
するのがいいと思う。