Code Fix

上級

as unknown as Xの二重キャストは型チェックを完全に迂回し、実行時エラーの原因になる

TypeScriptは通常、明らかに無関係な型同士のasによるキャストをエラーにします。しかしunknownを経由する(as unknown as X)と、この安全装置を完全に迂回でき、どんな型にも変換できてしまいます。

エラーメッセージの読み方

main.ts:5
main.ts
ファイル名 — コンパイル前のTypeScriptソースです(sourceMapで変換後の位置を逆引きしています)
5
行番号 — 実際にクラッシュした行
TypeError
例外クラス — 何が起きたか。ここを検索するのが最短です
Cannot read properties of undefined (reading 'toUpperCase')
内容 — 期待していたもの、または受け付けられなかったもの

このエラーが出る典型パターン

パターン1

 1  interface User {
 2    name: string;
 3  }
 4  function greet(u: User): string {
 5    return "Hello, " + u.name.toUpperCase();
 6  }
 7  const raw: number = 42;
 8  console.log(greet(raw as unknown as User));
                                 ^
main.ts:5 TypeError: Cannot read properties of undefined (reading 'toUpperCase')

rawは実際にはnumberです。as unknown as Userは型チェックを完全に迂回するため、実行時にはnameプロパティが存在せずundefinedになります。

直し方: raw as unknown as User を { name: "Al" } にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  function sumAll(items: number[]): number {
 2    return items.reduce((a, b) => a + b, 0);
 3  }
 4  const raw: string = "5";
 5  console.log(sumAll(raw as unknown as number[]));
                                    ^
main.ts:2 TypeError: items.reduce is not a function

rawは実際には文字列です。as unknown as number[]でコンパイラを欺いても、文字列にreduceメソッドは無いため実行時に例外になります。

直し方: raw as unknown as number[] を [5] にします。

この問題を解いてみる →

パターン3

 1  interface Shape {
 2    describe(): string;
 3  }
 4  function show(s: Shape): string {
 5    return s.describe();
 6  }
 7  const raw: boolean = true;
 8  console.log(show(raw as unknown as Shape      ));
                                   ^
main.ts:5 TypeError: s.describe is not a function

rawは実際にはbooleanです。as unknown as Shapeで型を偽装しても、実行時にはdescribeメソッドが存在せず例外になります。

直し方: raw as unknown as Shape を { describe: () => "a shape" } にします。

この問題を解いてみる →

パターン4

 1  interface Animal2 {
 2    speak(): string;
 3  }
 4  function callSpeak(a: Animal2): string {
 5    return a.speak();
 6  }
 7  const raw: number = 7;
 8  console.log(callSpeak(raw as unknown as Animal2));
                                      ^
main.ts:5 TypeError: a.speak is not a function

rawは実際にはnumberです。as unknown as Animal2は型チェックを完全に迂回するため、実行時にはspeakメソッドが存在せず例外になります。

直し方: raw as unknown as Animal2 を { speak: () => "woof" } にします。

この問題を解いてみる →

パターン5

 1  function joinAll(items: string[]): string {
 2    return items.join(",");
 3  }
 4  const raw: boolean = true;
 5  console.log(joinAll(raw as unknown as string[]));
                                     ^
main.ts:2 TypeError: items.join is not a function

rawは実際にはbooleanです。as unknown as string[]でコンパイラを欺いても、実行時にはjoinメソッドが存在せず例外になります。

直し方: raw as unknown as string[] を ["a", "b"] にします。

この問題を解いてみる →

よくある誤解

「TypeScriptのasは無茶な変換を防いでくれるはず」という安心感は、unknownを経由した二重キャストには通用しません。unknownはあらゆる型からあらゆる型への変換を許すため、安全確認の意味を持たなくなります。

実務での勘所

unknown型がこの抜け道になる理由は、型の階層構造における特殊な位置づけにあります。あらゆる型の値はunknown型の変数に代入できる一方、unknown型の値を他の型としてasで扱うことも、型を絞り込む正当な操作として常に許可されます。この2つの性質を組み合わせると、値をいったんunknownという「なんでも受け入れる中継地点」に通すことで、本来なら直接は変換できない、まったく無関係な型同士の変換を実現できてしまいます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)