microsoft / microsoft/TypeScript

Make type narrowing for destructured discriminated unions work for more types

オープン
#59,657 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Domain: check: Control Flow Help Wanted Possible Improvement
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
1日 19時間
マージ済み PR(30日)
117

説明

🔍 Search Terms

discriminant union, ref, control flow guard, type narrowing

✅ Viability Checklist
⭐ Suggestion
type Ref<T> = { value: T }
type Data =
    | { ready: Ref<true>, payload: string }
    | { ready: Ref<false>, payload: null }

declare const data: Data
const { ready, payload } = data

if (ready.value) {
    payload    // <== currently inferred as "string | null" but should be "string"
}

Treat types like Ref<T> as a discriminant property in a union or find a way to narrow the type of payload

📃 Motivating Example

This is a very common use case in the Vue Pinia state store library, millions of projects use this library and have code like

const store = useDataStore()
const { ready, payload } = storeToRefs(store)

If we can improve this type narrowing behavior, the narrowed payload type can helps developer write safer code than before

// before, Non-null assertion everywhere
if (ready.value) {
    payload.xxxx()     // <=== false alert raised by typescript and developers have to use ?. or ! to avoid it
    payload?.xxxx()    // <=== ?. is unnecessary, generates dead code and brings cognitive confusion
    xxxxx(payload!)
}
xxxxx(payload!)        // <=== copied from the if block and forget to remove the ! mark, cannot receive alert from typescript

// after, everything works fine
if (ready.value) {
    payload.xxxx()
    xxxxx(payload)
}
xxxxx(payload)         // received the null check protection from typescript
💻 Use Cases

More detailed playground link

The use cases is actually shown in the motivating example.

I've dig into the checker.ts for some time and here's my findings

  1. getDiscriminantPropertyAccess cannot treat ready as a discriminant property now because it needs to check CheckFlags.Discriminant which implies CheckFlags.HasLiteralType. It's a pretty strict check and as its name describes, Ref<T> has no chance to pass this check.
  2. I'm not sure is it possible for relaxing the discriminate requirements but it seems to be a bad idea after some search. #29110 is what I found but it's a really old PR so maybe time changes now
  3. If we cannot solve it by using discriminant property narrowing, as a newbie to the typescript project, I just tried to debug the checker and have another idea
interface Ref<T> { value: T }
type ToRefs<T> = { [K in keyof T]: Ref<T[K]> }
function toRefs<T>(o: T): ToRefs<T> {
    return {} as any
}

interface DataPrepared {
    ready: true
    payload: string
}

interface DataNotPrepared {
    ready: false
    payload: null
}

type Data = DataPrepared | DataNotPrepared

declare const data: Data
const { ready, payload } = toRefs(data)

function isDataReady(d: Data): d is DataPrepared {
  return d.ready.value
}

if (isDataReady(data)) {
  ready.value     // <=== inferred as boolean but should be true
  payload.value   // <=== inferred as "string | null" but should be string
}

function assertDataReady(d: Data): asserts d is DataPrepared {}

if (ready.value) {
  assertDataReady(data)
  ready.value     // <=== inferred as true which is expected but it's narrowed by other code path
  payload.value   // <=== inferred as "string | null" but should be string
}

Can we use type predicates or assert function to add more information to payload's flow list? If it's possible, maybe we can do following steps while examine the payload

  1. check payload's symbol, if its declaration is a BindingPattern
  2. check the flow list for payload, if the narrowed data is the initializer of payload's declaration
  3. narrow the payload based on the narrowed data
  4. maybe it's gibberish but hope it helps

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

リンク先の TypeScript playground で分割代入された discriminated union の例を再現し、その後 checker.ts の getDiscriminantPropertyAccess 付近とフロー解析を読みます。既存の discriminant チェックを弱めることなく、Ref が narrowing に参加できる方法を判断します。ready.value が true のときに例で payload が string に絞り込まれ、サポートされるケースに適切なテストが追加されていれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
typescript
領域
compilers
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
35/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。