Open nekolab opened 2 months ago
š I'm working on the "alternative solution" which aims to use type predicts or assert function to narrow the type of the destructured variables.
My findings:
declare const data: Data
const { ready, payload } = toRefs(data)
if (isDataPrepared(data): data is DataPrepared) {
ready.value
// ^ narrow |ready| based on asserted |data| because it shares same symbol with argument |data| in |toRefs|
}
Now I can check the initializer is toRefs(data)
while checking the identifier ready
. In ready
's flow list, it's not difficult to infer the argument data
in isDataPrepared(data)
is DataPrepared
.
My idea is to add more logic to narrowTypeByCallExpression
, while the reference
is a call expression and has an argument overlap with the callExpression
, we can re-calculate the return type of the reference
.
So my current question is: Is it possible to narrow a CallExpression toRefs(data)
with the given argument type data
? I've checked the checkCallExpression
, and the argument type narrowing seems only to happen with the identifier's flow, it seems not possible to specify a flow to checkCallExpression
and tell it to use the flow to narrow the type.
Wish I can hear some advice here, thanks
Update: Found this doc https://github.com/microsoft/TypeScript/wiki/Reference-Checker-Inference#type-parameter-inference
š Search Terms
discriminant union, ref, control flow guard, type narrowing
ā Viability Checklist
ā Suggestion
Treat types like
Ref<T>
as a discriminant property in a union or find a way to narrow the type ofpayload
š 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
If we can improve this type narrowing behavior, the narrowed
payload
type can helps developer write safer code than beforeš» 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 findingsgetDiscriminantPropertyAccess
cannot treatready
as a discriminant property now because it needs to checkCheckFlags.Discriminant
which impliesCheckFlags.HasLiteralType
. It's a pretty strict check and as its name describes,Ref<T>
has no chance to pass this check.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 }