TypeScript を使って開発しているときに、次のようなエラーに出くわしたことはありませんか?
Property 'name' does not exist on type 'never'.
このエラーは、TypeScript の型推論が「値が存在しない(never)」と結論づけたときに発生します。実装上はプロパティがあるはずなのに、型システム上ではアクセスできないとみなされている状態です。
本記事では、このエラーが発生する仕組み、よくある原因パターン、デバッグ方法、実運用での注意点、さらによくある誤った回避例まで詳しく解説します。
1. エラーの発生仕組み
TypeScript の never
型は「到達不可能」または「値が存在しない」ことを意味します。通常は次のような場面で推論されます。
- Union 型を絞り込みすぎた結果、該当する型がなくなった場合
- 変数が空配列として初期化され、型推論がされないままアクセスされた場合
- switch 文や if 文で全パターンを排除してしまった場合
例として、次のようなコードを考えます。
type Animal = { kind: 'dog', name: string } | { kind: 'cat', age: number }
function printDogName(animal: Animal) {
if (animal.kind === 'cat') {
return
}
console.log(animal.name) // OK
}
このコードは問題ありませんが、複雑な配列操作や条件分岐で animal
の型が never
にまで絞り込まれると、以下のようなエラーメッセージが出ます。
TS2339: Property 'name' does not exist on type 'never'.
IDE(VSCodeなど)では変数にカーソルを合わせると「animal: never」と表示されるため、型が完全に絞り込まれていることがわかります。
2. よくある原因パターン
このエラーは特定のパターンで頻繁に出ます。
- 空配列初期化
const items = []
items.push({ name: 'foo' }) // 型エラー: never[] には push できない
- filter 絞り込みで型が失われる
type User = { active: boolean, name: string }
const users: User[] = [...]
const actives = users.filter(u => u.active)
// 推論結果が never[] になることがある
- Union 型を網羅しきれない分岐
type Shape = { kind: 'circle', r: number } | { kind: 'square', size: number }
function area(shape: Shape) {
switch (shape.kind) {
case 'circle': return Math.PI * shape.r ** 2
case 'square': return shape.size ** 2
default:
const _exhaustive: never = shape // ここで never エラー
}
}
どれも、型情報を明示することで解消可能です。
3. デバッグ方法と型推論の可視化
型を確認するのが第一歩です。
- VSCodeでホバー表示して型を見る
console.log()
ではなく一時的に型注釈を追加してみるtsc --noEmit
を使い、型エラー一覧を確認
const items: { name: string }[] = []
items.push({ name: 'foo' }) // OK
また、型レベルでデバッグする場合、type
エイリアスを使って一時的に型を出力する方法もあります。
type DebugType<T> = T
type ItemType = DebugType<typeof items> // VSCodeで型が見える
4. 正しい修正方法
エラーを解決するためには、正しい型情報を与えることが最重要です。
- 初期値に型注釈をつける
const results: string[] = []
- ジェネリクスを明示する
function identity<T>(value: T): T { return value }
const a = identity<string>('foo')
- 型ガードを使う
function isDog(animal: Animal): animal is { kind: 'dog', name: string } {
return animal.kind === 'dog'
}
if (isDog(animal)) {
console.log(animal.name) // エラーにならない
}
- Union 型に網羅性チェックを導入
function exhaustiveCheck(param: never): never {
throw new Error(`Unexpected value: ${param}`)
}
この関数を default 節で呼べば、新しい型が追加された際にコンパイルエラーで気づけます。
5. よくある誤った回避例
このエラーを「手っ取り早く消す」ために以下のような書き方をする例があります。
- any キャスト
const foo: any = items
console.log(foo.name) // コンパイル通るが安全性喪失
- 非 null アサーションの乱用
console.log((animal as any).name)
これらは短期的にはエラーが消えますが、型安全性が失われ、ランタイムエラーを引き起こす可能性が高いです。本番コードでは避けるべきです。
6. 実運用での注意点とCI/CDでの防止策
チーム開発では、never
型が出る状況を早期に検知する仕組みが重要です。
- ESLint で
no-explicit-any
を有効にする - CI で
tsc --noEmit
を必ず実行 - PR レビュー時に「型注釈が欠けていないか」を確認
- Storybook やテストで型絞り込みのケースを網羅
特にジェネリクスや複雑な型を使うライブラリ開発では、テストケースを細かく作り、never
推論が発生しないかをチェックするのがおすすめです。
まとめ
「Property ‘x’ does not exist on type ‘never’」エラーは、TypeScript の型推論が「その値は存在しない」と結論づけた際に発生するものです。
空配列や Union 型の絞り込みなど、日常的なパターンで出現するため、型注釈や型ガードで明示的に型を与えることで回避可能です。安易な any キャストではなく、型システムを活用した修正が推奨されます。CI/CD で型チェックを強制し、エラーを早期に検出する仕組みを整えることで、安全で保守しやすいコードベースを維持できます。
参考
- TypeScript Handbook – Never [https://www.typescriptlang.org/docs/handbook/basic-types.html#never]
- TypeScript Deep Dive – Type Guards [https://basarat.gitbook.io/typescript/type-system/typeguard]
- ESLint Rules – no-explicit-any [https://eslint.org/docs/latest/rules/@typescript-eslint/no-explicit-any]