microsoft / microsoft/TypeScript
Make type narrowing for destructured discriminated unions work for more types
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- Go
- Sterne
- 111k
- Forks
- 14.3k
- Ø Merge
- 1 T. 19 Std.
- Gemergte PRs (30 T.)
- 117
Beschreibung
🔍 Search Terms
discriminant union, ref, control flow guard, type narrowing
✅ Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ 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
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
getDiscriminantPropertyAccesscannot treatreadyas a discriminant property now because it needs to checkCheckFlags.Discriminantwhich impliesCheckFlags.HasLiteralType. It's a pretty strict check and as its name describes,Ref<T>has no chance to pass this check.- 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
- 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
- check
payload's symbol, if its declaration is aBindingPattern - check the flow list for
payload, if the narroweddatais the initializer ofpayload's declaration - narrow the
payloadbased on the narroweddata - maybe it's gibberish but hope it helps
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Reproduziere das destrukturierte Discriminated-Union-Beispiel im verlinkten TypeScript-Playground und lies anschließend checker.ts im Umfeld von getDiscriminantPropertyAccess und der Flow-Analyse. Ermittle, wie Ref an der Narrowing-Analyse teilnehmen könnte, ohne bestehende Diskriminantenprüfungen abzuschwächen. Als erledigt gilt die Aufgabe, wenn das Beispiel payload auf string eingrenzt, wenn ready.value true ist, und geeignete Tests für die unterstützten Fälle hinzugefügt wurden.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- typescript
- Bereich
- compilers
- Issue-Typ
- Feature
- Schwierigkeit
- 5/5
- Geschätzter Aufwand
- Über eine Woche
- Aktivitätsstatus
- Veraltet
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 35/100