Upgrade to Pro — share decks privately, control downloads, hide ads and more …

プロパティの順序で型推論が壊れる!? TypeScript6.0の修正からContext-Se...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

プロパティの順序で型推論が壊れる!? TypeScript6.0の修正からContext-Sensitivityの仕組みを追う

ブログ記事はこちら: https://tech.findy.co.jp/entry/2026/05/23/070000

TypeScript 6.0 に入った1つの PR を起点に、TypeScript コンパイラの型推論の内部に踏み込むセッションです。

オブジェクトリテラル内でメソッド構文を使うと、プロパティの記述順序を入れ替えただけでコールバック引数の型が number から unknown に変わる挙動がありました。アロー関数なら順序に関係なく推論が通るのに、メソッド構文にした途端に型推論が壊れていました。

この挙動は、型推論における context-sensitive の判定ロジックに起因します。メソッド構文は暗黙の this パラメータを持つため context-sensitive と判定され、推論の処理順が変わります。アロー関数はこれを持たないため、この影響を受けません。

この挙動は PR #62243 によって修正され、TS 6.0 でリリースされました。this を実際に使用していない関数を context-sensitive と見なさないよう変更されています。

本トークではこの PR の差分 (utilities.ts, binder.ts, checker.ts) を追い、変更の意図を読み解きます。この事例を通じて context-sensitivity という概念と、TypeScript の内部に踏み込むきっかけを持ち帰っていただくことを目指します。

Avatar for おおいし

おおいし

May 23, 2026

More Decks by おおいし

Other Decks in Programming

Transcript

  1. © Findy Inc. 2 @bicstone_me ⼤⽯ 貴則 OISHI Takanori 登壇者紹介

    • ⾼専機械⼯学科卒、元機械設計エンジニア • 現在はSaaS業界に5年携わるWebエンジニア • TypeScript / Ruby / PHP / Python / Dart • Certified ScrumMaster ® @oishi.takanori @bicstone Certified ScrumMaster® is a certification mark of Scrum Alliance, Inc. Any unauthorized use is strictly prohibited.
  2. © Findy Inc. 3 今回のゴール ➔ ⼀緒にTypeScriptのソースコードを覗いてみましょう 🔍 1つの機能修正を通じてContext-Sensitivityを理解する •

    今回の修正に⾄った背景 ◦ 型推論のステップを知り、今回の挙動の原因を探る • Context-Sensitivityの仕組み ◦ どうして推論を「後回し」にする必要があるのか • 今回の修正内容からTypeScriptのソースコードを読む ◦ binder.ts / utilities.ts の変更箇所を追う
  3. © Findy Inc. 5 背景 今回取り上げる関数の例 function test<T>(options: { a:

    (c: T) => void; b: () => T; }) {} • c の型推論に着⽬します
  4. © Findy Inc. 6 背景 testを呼び出した時、c の型推論はどうなると思いますか? 🤔 function test<T>(options:

    { a: (c: T) => void; b: () => T; }) {} test({ b() { return 123 }, a(c) { return c } }); ^ c: ??? test({ a(c) { return c }, b() { return 123 } }); ^ c: ???
  5. © Findy Inc. 7 背景 1つ⽬はnumber型に推論される 😌 function test<T>(options: {

    a: (c: T) => void; b: () => T; }) {} test({ b() { return 123 }, a(c) { return c } }); ^ c: number test({ a(c) { return c }, b() { return 123 } }); ^ c: ???
  6. © Findy Inc. 8 背景 2つ⽬はunknown型で推論されてしまう 🫨 function test<T>(options: {

    a: (c: T) => void; b: () => T; }) {} test({ b() { return 123 }, a(c) { return c } }); ^ c: number test({ a(c) { return c }, b() { return 123 } }); ^ c: unknown
  7. © Findy Inc. 9 背景 アロー関数だと順番⼊れ替えてもunknownにならない 🤯 test({ b() {

    return 123 }, a(c) { return c } }); ^ c: number test({ a(c) { return c }, b() { return 123 } }); ^ c: unknown test({ b: () => 123, a: (c) => { c } }); ^ c: number test({ a: (c) => { c }, b: () => 123 }) ^ c: number
  8. © Findy Inc. 11 Context-Sensitive Functionとは ➔ 推論を「後回し」にする必要がある • 型を決めるために外部

    のコンテキストが必要 • 型推論をスキップし、 他の引数から先に 型情報を集める 型注釈のないパラメータを持つ関数 function test2<T>( callback: (x: T) => void, value: T ) {} test2(x => x.toFixed(), 123); ^ xの型はTに依存 参考: https://devblogs.microsoft.com/typescript/announcing-typescript-6-0-beta/
  9. © Findy Inc. 13 今回の例がcontext-sensitiveになった理由 { a(c) { return c

    } ↓ 推論時の解釈 { a(this: ???, c: ???) { return c } } ^^^^ 暗黙のthisの型がわからない ➔ 暗黙のthisに型注釈がないため、推論が「後回し」にされる 参考: https://github.com/microsoft/TypeScript/pull/62243
  10. © Findy Inc. 14 今回の例における型解釈の流れ test({ a(c) { return c

    }, b() { return 123 } }); ^ c: unknown 1. a(c) → 暗黙の this → context-sensitive → スキップ 2. b() → 暗黙の this → context-sensitive → スキップ 3. 両⽅スキップ → T を推論できない 4. フォールバック: 左→右の順番で処理を試みる 5. a(c) を先に処理 → T が未確定 → c: unknown 参考: https://github.com/microsoft/TypeScript/pull/62243
  11. © Findy Inc. 15 今回の例における型解釈の流れ test({ b() { return 123

    }, a(c) { return c } }); ^ c: number 1. b() → 暗黙の this → context-sensitive → スキップ 2. a(c) → 暗黙の this → context-sensitive → スキップ 3. 両⽅スキップ → T を推論できない 4. フォールバック: 左→右の順番で処理を試みる 5. b() を先に処理 → T が確定 → c: number 参考: https://github.com/microsoft/TypeScript/pull/62243
  12. © Findy Inc. 17 TypeScript 6.0における修正内容 ➔ ⼀緒にTypeScriptのソースコードを覗いてみましょう 🔍 this

    を使っていない関数は context-sensitive と⾒なさない • binder.ts ◦ thisを使っているか確認しフラグを設定 • utilities.ts ◦ フラグが⽴っていなければcontext-sensitiveにしない • checker.ts ◦ Generatorのエッジケースに対応 参考: https://github.com/microsoft/TypeScript/pull/62243
  13. © Findy Inc. 18 binder.tsの変更 (抜粋) function bindContainer(node, containerFlags) {

    // … // thisが出現するとseenThisKeyword = trueになる bindChildren(node); // … if (seenThisKeyword) { node.flags |= NodeFlags.ContainsThis; // フラグを立てる } // … } 参考: https://github.com/microsoft/TypeScript/pull/62243 thisを使っている場合にフラグを⽴てる
  14. © Findy Inc. 19 utilities.tsの変更 (抜粋) function hasContextSensitiveParameters(node) { for

    (const param of node.parameters) { if (isThisParameter(param)) { // ★ ContainsThis がなければ無視 if (!(node.flags & NodeFlags.ContainsThis)) { continue; // this パラメータをスキップ } } if (!parameterHasTypeAnnotation(param)) { return true; // context-sensitive } } return false; } 参考: https://github.com/microsoft/TypeScript/pull/62243 フラグが⽴っていなければcontext-sensitiveにしない
  15. © Findy Inc. • yield式がcontext-sensitiveな 場合にGenerator全体を context-sensitiveとして扱う変更 • ここに書くには余⽩が狭すぎる のでブログをご覧ください

    🙏 https://tech.findy.co.jp/entry/2026/05/23/070000 20 checker.tsの変更 参考: https://tech.findy.co.jp/entry/2026/05/23/070000 Generatorのエッジケースに対応
  16. © Findy Inc. 21 修正後の型解釈の流れ test({ a(c) { return c

    }, b() { return 123 } }); ^ c: number 1. a(c) → this 不使⽤ → スキップしない 2. b() → this 不使⽤ → スキップしない 3. 通常の推論を実施 4. b() の戻り値から T = number 5. a(c) に T = number を適⽤ → c: number 🎉 参考: https://github.com/microsoft/TypeScript/pull/62243
  17. © Findy Inc. 23 まとめ ➔ ぜひTypeScriptのソースコードを読んでみてください 󰚗 1つの機能修正を通じてContext-Sensitivityを理解した •

    今回の修正に⾄った背景 ◦ 暗黙のthisで型推論がスキップされた • Context-Sensitivityの仕組み ◦ 型注釈がないパラメータを後回しにし、型情報を集める戦略 • 今回の修正内容からTypeScriptのソースコードを読む ◦ thisを使わない関数をcontext-sensitiveにしなくなった
  18. © Findy Inc. 24 最後に https://findy.connpass.com/event/392420/ 本スライドの binder.ts / utilities.ts

    / checker.ts のソースコードは TypeScriptから引用しており、Apache License 2.0のもとで配布されています。 https://github.com/microsoft/TypeScript/blob/main/LICENSE.txt