TypeScriptで「Property ‘x’ does not exist on type ‘never’」エラーが出る原因と解決法

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. 正しい修正方法

エラーを解決するためには、正しい型情報を与えることが最重要です。

  1. 初期値に型注釈をつける
const results: string[] = []
  1. ジェネリクスを明示する
function identity<T>(value: T): T { return value }
const a = identity<string>('foo')
  1. 型ガードを使う
function isDog(animal: Animal): animal is { kind: 'dog', name: string } {
  return animal.kind === 'dog'
}

if (isDog(animal)) {
  console.log(animal.name) // エラーにならない
}
  1. 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 で型チェックを強制し、エラーを早期に検出する仕組みを整えることで、安全で保守しやすいコードベースを維持できます。

参考

タイトルとURLをコピーしました