mcpField
C
context-type-inference

context-type-inference

github.com/marius-townhouse開発644 回インストール38 週間前に更新
リポジトリ

context-type-inference について

型推論に周囲のコンテキストがどう影響するかを扱う TypeScript スキルです。値を切り出した後も正確な型を保つため、型注釈、const アサーション、satisfies の使い方を説明します。

主な機能

  1. 01失われた型コンテキストの復元
  2. 02リテラル型の保持
  3. 03タプル型の精度維持
  4. 04satisfies による検証

活用シーン

  1. 01値の切り出し後に生じる型エラーの修正
  2. 02タプル型が配列型へ拡大するのを防止
  3. 03コンテキスト外へ移したコールバックの型付け

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill context-type-inference

インストール後、Claude Code で/context-type-inferenceを入力して読み込まれたか確認します。

詳細

型推論でコンテキストがどのように使われるかを理解する

概要

値をコンテキストから切り離すと、型エラーが発生することがあります。

TypeScript は、値とその値が使われる場所の両方に基づいて型を推論します。値を変数に取り出すとコンテキストが失われ、予想外の型エラーが発生することがあります。

このスキルを使う場面

  • 値を定数に取り出すと型エラーが発生する
  • コールバックのパラメーターの型が正しくない
  • タプル型が配列型になる
  • 文字列リテラル型が一般的な文字列型になる

鉄則

When extraction breaks types, restore context with:
annotations, const assertions, or satisfies.

覚えておきましょう:

  • TypeScript は関数のパラメーターからコンテキストを使います
  • 値を取り出すと、そのコンテキストが失われます
  • as const は型の拡大を防ぎます
  • satisfies は検証を行いつつ、コンテキストを保持します

検出:コンテキストの喪失

type Language = 'JavaScript' | 'TypeScript' | 'Python';

function setLanguage(language: Language) { /* ... */ }

setLanguage('JavaScript');  // OK - context from parameter

let language = 'JavaScript';  // Inferred as string (widened)
setLanguage(language);
//          ~~~~~~~~
// Argument of type 'string' is not assignable to 'Language'

値を取り出したときに、型が string に拡大されました。

解決策 1:型注釈

let language: Language = 'JavaScript';
setLanguage(language);  // OK

型注釈によってコンテキストが与えられます。

解決策 2:const 宣言

const language = 'JavaScript';
//    ^? const language: "JavaScript"
setLanguage(language);  // OK

const 変数の型は、より狭い型(リテラル型)になります。

解決策 3:const アサーション

let language = 'JavaScript' as const;
//  ^? let language: "JavaScript"
setLanguage(language);  // OK

as const は、let を使う場合でも型の拡大を防ぎます。

タプル型:よくあるケース

function panTo(where: [number, number]) { /* ... */ }

panTo([10, 20]);  // OK - inferred as tuple from context

const loc = [10, 20];
//    ^? const loc: number[]  (array, not tuple!)
panTo(loc);
//    ~~~
// Argument of type 'number[]' is not assignable to '[number, number]'
型注釈で修正する
const loc: [number, number] = [10, 20];
panTo(loc);  // OK
const アサーションで修正する
const loc = [10, 20] as const;
//    ^? const loc: readonly [10, 20]

注意:as const を使うと readonly になります。関数が変更可能なタプルを期待する場合、これは使えません。

panTo(loc);
//    ~~~
// 'readonly [10, 20]' is not assignable to '[number, number]'

入力を変更しない関数であれば、readonly [number, number] を受け取るようにする必要があります。

オブジェクトのプロパティ

type Point = [number, number];

const capitals1 = { ny: [-73.7562, 42.6526], ca: [-121.4944, 38.5816] };
//    ^? { ny: number[]; ca: number[]; }  (arrays, not tuples)

const capitals2 = {
  ny: [-73.7562, 42.6526],
  ca: [-121.4944, 38.5816],
} satisfies Record<string, Point>;
//    ^? { ny: [number, number]; ca: [number, number]; }  (tuples!)

satisfies は、正確な型を保持しながらコンテキストを与えます。

satisfies と型注釈の比較

const capitals3: Record<string, Point> = capitals2;
capitals3.pr;  // No error! Property 'pr' returns Point (but undefined at runtime)
//        ^? Point

capitals2.pr;
//        ~~
// Property 'pr' does not exist

satisfies は正確なキーを保持しますが、型注釈では保持されません。

コールバックのコンテキスト

TypeScript は、コンテキストからコールバックのパラメーターの型を推論します。

// Types inferred from app.get declaration
app.get('/health', (request, response) => {
  //                ^? Request<...>
  //                          ^? Response<...>
  response.send('OK');
});

// Don't add redundant annotations:
app.get('/health', (request: express.Request, response: express.Response) => {
  // Redundant and verbose
});

コールバックの取り出し

// Context lost when callback is extracted
const handler = (request, response) => {
  //             ^? any    ^? any
  response.send('OK');
};

// Fix: Add types
const handler: express.RequestHandler = (request, response) => {
  //                                      ^? Request  ^? Response
  response.send('OK');
};

オブジェクトのメソッド

const config = {
  language: 'JavaScript' as const,
  //        ^? "JavaScript" (not string)
};

setLanguage(config.language);  // OK

as const がない場合、config.language は string になります。

深い const アサーション

const obj = {
  settings: {
    language: 'JavaScript',
    version: 4,
  }
} as const;
// ^? { readonly settings: { readonly language: "JavaScript"; readonly version: 4; } }

as const は深く適用され、すべてがリテラル型を持つ読み取り専用になります。

圧力に負けないためのプロトコル

1. 「型アサーションを追加するだけ」

圧力:「as Language を使えば直せます」

回答: 型アサーションは安全性を迂回します。代わりに const または satisfies を使いましょう。

行動: リテラル型に絞り込むには as Type ではなく as const を使います。

2. 「インラインなら動いた」

圧力:「取り出すと、なぜ動かなくなるのですか?」

回答: コンテキストが失われています。明示的に復元しましょう。

行動: 型注釈を追加するか、const または satisfies を使います。

危険信号 — 立ち止まって再検討する

  • 値を変数に取り出した後に型エラーが発生する
  • コールバックのパラメーターが any 型になる
  • タプルが配列になる
  • 文字列リテラル型が string になる

よくある正当化(すべて誤り)

言い訳実際のところ
「同じ値です」値が同じでも、コンテキストが異なれば推論される型も異なります
「TypeScript なら分かるはずです」正確な型を推論するには、TypeScript にコンテキストが必要です
「as を使って直します」型アサーションではなく、as const または satisfies を使いましょう

クイックリファレンス

type Lang = 'JS' | 'TS';
declare function setLang(l: Lang): void;

// DON'T: Lose context
let lang = 'JS';  // string
setLang(lang);    // Error

// DO: Annotation
let lang: Lang = 'JS';

// DO: const
const lang = 'JS';  // "JS"

// DO: const assertion
let lang = 'JS' as const;  // "JS"

// For objects: satisfies
const cfg = { lang: 'JS' } satisfies Record<string, Lang>;

要点

型推論ではコンテキストが重要です。

値を取り出すと、型のコンテキストが失われることがあります。型注釈、const 宣言、as const アサーション、または satisfies を使って復元しましょう。それぞれの状況に適した方法を理解してください。

参考資料

Dan Vanderkam 著「Effective TypeScript」の第 24 項「型推論でコンテキストがどのように使われるかを理解する」に基づいています。

コメント