Repository: TypeScript
Issue: Make type narrowing for destructured discriminated unions work for more types
Labels: Help Wanted,Possible Improvement

### 🔍 Search Terms

discriminant union, ref, control flow guard, type narrowing

### ✅ Viability Checklist

- [X] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [X] This wouldn't change the runtime behavior of existing JavaScript code
- [X] This could be implemented without emitting different JS based on the types of the expressions
- [X] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- [X] This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- [X] This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

### ⭐ Suggestion

```typescript
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](https://pinia.vuejs.org/) state store library, millions of projects use this library and have code like
```typescript
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
```typescript
// 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](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAShBmAeAKgPigXigbygNwEMAbAVwgC4pkoBfAKDtEigBEDgC4CATETHOlCgAnCDxCU4SYMLKpBUMARBEA9j0kJEAZxkBLAHYBzefUbhobDgDlVwLr37YFo8ZqTxi2iPKFKV6tzuiAYkRESmDEyW7AT8VpxijgA+rLG29kkgDNwQAMZEBKJQeaoGulDcsZQJDKXlwDgiWQA0ispqPLT8VRwMevBQABSuvAB0hKQQAJQCfh2BE8RkQlAA9GtQiBg7UIbwEMKi3FAE2lAARLrChkZQqaHhF1AARiSN2gAWqmEnL9DXW50MzwEgGPLAPRlPbaBIOEBDIJpDjTSgnPTnOFZObNYAkYQGSpjUYgJZTYH9QZDDFY8SI2LTWbOIQkskrVbrTbbXb7Q7HU7nF6qVREMSEt4fb6-V7QGRkBT+TrcNnQIQbLY7LC8o4QE5nS6A4z3KCPIjPCVQL4-Ih-AH6YwUuig8GQ6FnbzCYC03iImoMyjuw7Ac7ozGxeE4MwDYasyZkJkKQOe70I3oEaYuLIqjmcjU8gwHHV685y6AAd0+ejynxhUAgAA9IBDda93ntgAByc4GIrCVRllsvPh2T6HdrAT4KhY8bNqrmavYFvkt-VXe13B5hM2tyXW22W9cUoA)

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
```typescript
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