Slide 1

Slide 1 text

© Findy Inc. プロパティの順序で型推論が壊れる!? TypeScript 6.0 の修正から Context-Sensitivity の仕組みを追う 1 2026.05.23 ファインディ株式会社 ⼤⽯ 貴則

Slide 2

Slide 2 text

© 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.

Slide 3

Slide 3 text

© Findy Inc. 3 今回のゴール ➔ ⼀緒にTypeScriptのソースコードを覗いてみましょう 🔍 1つの機能修正を通じてContext-Sensitivityを理解する ● 今回の修正に⾄った背景 ○ 型推論のステップを知り、今回の挙動の原因を探る ● Context-Sensitivityの仕組み ○ どうして推論を「後回し」にする必要があるのか ● 今回の修正内容からTypeScriptのソースコードを読む ○ binder.ts / utilities.ts の変更箇所を追う

Slide 4

Slide 4 text

© Findy Inc. 4 背景

Slide 5

Slide 5 text

© Findy Inc. 5 背景 今回取り上げる関数の例 function test(options: { a: (c: T) => void; b: () => T; }) {} ● c の型推論に着⽬します

Slide 6

Slide 6 text

© Findy Inc. 6 背景 testを呼び出した時、c の型推論はどうなると思いますか? 🤔 function test(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: ???

Slide 7

Slide 7 text

© Findy Inc. 7 背景 1つ⽬はnumber型に推論される 😌 function test(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: ???

Slide 8

Slide 8 text

© Findy Inc. 8 背景 2つ⽬はunknown型で推論されてしまう 🫨 function test(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

Slide 9

Slide 9 text

© 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

Slide 10

Slide 10 text

© Findy Inc. 10 原因

Slide 11

Slide 11 text

© Findy Inc. 11 Context-Sensitive Functionとは ➔ 推論を「後回し」にする必要がある ● 型を決めるために外部 のコンテキストが必要 ● 型推論をスキップし、 他の引数から先に 型情報を集める 型注釈のないパラメータを持つ関数 function test2( callback: (x: T) => void, value: T ) {} test2(x => x.toFixed(), 123); ^ xの型はTに依存 参考: https://devblogs.microsoft.com/typescript/announcing-typescript-6-0-beta/

Slide 12

Slide 12 text

© Findy Inc. 12 ジェネリック関数における型推論の流れ ➔ 全関数がスキップされるとフォールバックに⼊り、 左から右の順番で処理を試みる 1. 引数を⾛査し、context-sensitiveな関数を特定 2. context-sensitiveな関数をスキップ 3. 残りから型パラメータTを推論 4. 確定したTで後回しした関数のパラメータを型付け

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

© 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

Slide 15

Slide 15 text

© 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

Slide 16

Slide 16 text

© Findy Inc. 16 今回の修正内容

Slide 17

Slide 17 text

© 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

Slide 18

Slide 18 text

© 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を使っている場合にフラグを⽴てる

Slide 19

Slide 19 text

© 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にしない

Slide 20

Slide 20 text

© 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のエッジケースに対応

Slide 21

Slide 21 text

© 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

Slide 22

Slide 22 text

© Findy Inc. 22 まとめ

Slide 23

Slide 23 text

© Findy Inc. 23 まとめ ➔ ぜひTypeScriptのソースコードを読んでみてください 󰚗 1つの機能修正を通じてContext-Sensitivityを理解した ● 今回の修正に⾄った背景 ○ 暗黙のthisで型推論がスキップされた ● Context-Sensitivityの仕組み ○ 型注釈がないパラメータを後回しにし、型情報を集める戦略 ● 今回の修正内容からTypeScriptのソースコードを読む ○ thisを使わない関数をcontext-sensitiveにしなくなった

Slide 24

Slide 24 text

© 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