TypeScriptの幽霊:`{}`(空オブジェクト型)が引き起こす型安全性の崩壊と、コンパイラを欺く魔術の解体
TypeScriptの型システムにおける最大のパラドックスの一つ、それが空オブジェクト型 `{}` である。
多くのジュニア、あるいは中堅のエンジニアですら、`{}` を「プロパティを持たない空のオブジェクト」という直感的なメンタルモデルで捉えている。しかし、コンパイラの内部実装、そしてTypeScriptがターゲットとするJavaScriptの動的ランタイムの現実において、`{}` は「何もない」どころか、「`null` と `undefined` 以外のあらゆるプリミティブ値とオブジェクトを受け入れる究極のトップ型に近い何か」として振る舞う。
本稿では、TypeScriptコアの型評価メカニズム、構造的型付け(Structural Subtyping)の数学的帰結、そしてこの `{}` が引き起こすセキュリティ上の脆弱性や予期せぬランタイムクラッシュを防ぐための防壁構築について、極限の低レイヤ視点から解き明かす。
—
1. コンパイラ視点における `{}` の正体:なぜ `null`/`undefined` 以外の全てを許容するのか?
TypeScriptの型チェッカー(`checker.ts`)において、型は集合論の概念に基づいて評価される。
構造的型付けシステムの下では、型 $A$ が型 $B$ の部分型(subtype)であるとは、$B$ が持つ要件を $A$ がすべて満たしていることを意味する。
ここで `{}` という型を考えてみよう。
「プロパティを一切要求しないオブジェクトの型」は、逆説的に「どのようなプロパティを持っていても、要求された条件(プロパティなし)に違反しない」という魔術的な性質を持つ。
結果として、TypeScriptの型システムにおいて、`{}` は `null` と `undefined` を除くほぼすべてのプリミティブ値(`string`, `number`, `boolean`, `symbol`, `bigint`)や関数、配列、すべてのオブジェクトのスーパートップ型として機能する。
// — コンパイラの型評価の闇 —
const a: {} = “Hello, TypeScript”; // コンパイルエラーにならない! (stringは{}に代入可能)
const b: {} = 42; // コンパイルエラーにならない! (numberは{}に代入可能)
const c: {} = () => {}; // 当然、関数も代入可能
const d: {} = []; // 配列も代入可能
// では、何が弾かれるのか?
const e: {} = null; // Error: Type ‘null’ is not assignable to type ‘{}’.
const f: {} = undefined; // Error: Type ‘undefined’ is not assignable to type ‘{}’.
この挙動の根源は、JavaScriptのプリミティブ値がメソッド呼び出し時やプロパティアクセス時に一時的なオブジェクト(Wrapper Object)へボクシング(Box)されるランタイム仕様に、型システムが引きずられている点にある。TypeScriptの設計思想において、`{}` は「プロパティが存在しない」のではなく、「プロパティの存在を検証しない(プロパティアクセスを一切保証しないが、値の存在は要求する)」という極めて危険なアノテーションなのだ。
—
2. ランタイムの現実と型安全性の崩壊:ボクシングとメソッド借用
この `{}` の挙動が実務のコードベースでどのように牙をむくか、具体例を見てみよう。
例えば、関数の引数に「任意のオブジェクト」を受け入れたいという意図で `{}` を指定したとする。
function processConfig(config: {}) {
// コンパイラはエラーを出さないが、ランタイムで何が起きるか?
console.log(Object.keys(config));
}
// 呼び出し側
processConfig(“malicious-payload”);
// 実行結果: [‘0’, ‘1’, ‘2’, ‘3’, ‘4’, … ] (文字列のインデックスがキーとして列挙される)
文字列 `”malicious-payload”` が `config` に渡された場合、ランタイムではJavaScriptの仕様により文字列がStringオブジェクトにボクシングされ、`Object.keys()` はインデックスの配列を返す。もしこの関数が `config` を「設定用のハッシュマップ」として期待し、内部でプロパティの書き換えや意図しないキーの存在確認を行っていた場合、予期せぬバグやセキュリティホールの温床となる。
さらに最悪なのは、プリミティブのプロトタイプチェーン汚染や、意図しないメソッドの借用(Method Borrowing)を引き起こすリスクだ。
let userCount: {} = 100;
// number型であるにもかかわらず、{} 型として扱われるため、
// 開発者は「オブジェクトである」と誤認したままロジックを組み上げる可能性がある
if (‘toFixed’ in userCount) {
// ここに入れるが、これはオブジェクトではなく数値のメソッドである
}
—
3. 厳密な防壁の構築:`Record` と `object` 型の正しい使い分け
意図しない値の代入を防ぎ、真に安全なコードベースを構築するためには、`{}` をコードから完全に駆逐し、適切な型制約に置き換える必要がある。
A. `object` 型の活用
`object` 型(小文字の `o`)は、「プリミティブ値を除外したすべてのオブジェクト型」を表す。配列や関数は含まれるが、`string` や `number` などのプリミティブは厳格に弾かれる。
let strictObj: object;
strictObj = { id: 1 }; // OK
strictObj = []; // OK (JavaScriptにおいて配列はオブジェクト)
strictObj = () => {}; // OK (関数もオブジェクト)
// strictObj = “string”; // Error: Type ‘string’ is not assignable to type ‘object’.
// strictObj = 42; // Error: Type ‘number’ is not assignable to type ‘object’.
B. 任意のキーを持つハッシュマップには `Record`
「任意のプロパティを持つ辞書型(Dictionary)」を表現したい場合は、`{}` ではなく `Record
ここで `unknown` を値の型に指定することが極めて重要である。`any` や `{}` を値にしてしまうと、型安全性が破壊される。
// 安全な設定オブジェクトの受取口
function parseSettings(settings: Record
const timeout = settings.timeout;
// 型ガードを通すことで初めて安全に値を利用できる
if (typeof timeout === ‘number’) {
console.log(`Timeout is set to ${timeout}ms`);
}
}
parseSettings({ timeout: 5000 }); // OK
// parseSettings(“not an object”); // Error: Argument of type ‘string’ is not assignable to parameter of type ‘Record
—
4. チーフアーキテクトからの提言:ESLintによる機械的強制
TypeScriptの型システムは強力だが、開発者の「なんとなく `{}` を書く」という悪癖をコンパイルエラーだけで防ぐことは難しい。特に、TypeScriptの標準ライブラリ(`lib.d.ts`)やサードパーティ製ライブラリの型定義の歴史的経緯から、意図せず `{}` が露出するケースは後を絶たない。
真のプロダクション環境においては、ESLintのルールを用いて `{}` の使用を機械的に禁止し、CI/CDパイプラインで完全に遮断すべきである。
{
“rules”: {
“@typescript-eslint/ban-types”: [
“error”,
{
“types”: {
“{}”: {
“message”: “Use ‘object’ for non-primitives, or ‘Record
“fixWith”: “Record
}
}
}
]
}
}
—
結びにかえて
TypeScriptにおける `{}` は、かつてのJavaScriptの曖昧なオブジェクト概念と、現代の厳密な静的型システムを繋ぐ「パンドラの箱」である。
その挙動の裏にあるコンパイラの型評価ロジック、そしてランタイムでのボクシングの仕様を理解していれば、コードレビューにおいて「なぜその型定義が危険なのか」を論理的かつ圧倒的な説得力を持って指摘できるはずだ。
型安全とは、単にコンパイルエラーを出さないことではない。
「ランタイムで起こり得るすべての不確定要素を、静的解析の網の目の下に完全に屈服させること」に他ならない。今日からコードベースの `{}` を見直し、真の堅牢性を手に入れよう。