Narrow destructured variables based on initializer's analysis result

This PR introduces a method to narrow the types of destructured
variables through control flow analysis without relying on the
discriminant member.

When the initializer of the `ObjectBindingPattern` is available,
we attempt to narrow its type, which in turn helps further refine
the type of the destructured variable.

Partially fixes #59657
This commit is contained in:
Yi Sun 2024-08-25 03:18:04 +08:00
Родитель a86b5e2b01
Коммит 77cb42b3d5
5 изменённых файлов: 472 добавлений и 1 удалений

Просмотреть файл

@ -30286,7 +30286,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
links.flags &= ~NodeCheckFlags.InCheckIdentifier;
if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) {
const pattern = declaration.parent;
const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode);
const initialType = parent.initializer
? getFlowTypeOfReference(parent.initializer, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode)
: parentTypeConstraint;
const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, initialType, /*flowContainer*/ undefined, location.flowNode);
if (narrowedType.flags & TypeFlags.Never) {
return neverType;
}

Просмотреть файл

@ -0,0 +1,72 @@
//// [tests/cases/compiler/narrowDestructuredVariables.ts] ////
//// [narrowDestructuredVariables.ts]
interface Ref<T> { current: T };
type ToRefs<T> = { [K in keyof T]: Ref<T[K]> };
declare function toRefs<T>(o: T): ToRefs<T>;
interface DataPrepared {
prepared: true
payload: string
};
interface DataPending {
prepared: false
payload: null
};
type Data = DataPrepared | DataPending;
declare function isDataRefsPrepared(refs: ToRefs<Data>): refs is ToRefs<DataPrepared>;
declare const data: Data;
const dataRefs = toRefs(data);
const { prepared, payload } = dataRefs;
if (prepared.current) {
prepared.current;
payload.current;
data.prepared;
data.payload;
}
if (isDataRefsPrepared(dataRefs)) {
prepared.current;
payload.current;
data.prepared;
data.payload;
}
if (data.prepared) {
prepared.current;
payload.current;
data.prepared;
data.payload;
}
//// [narrowDestructuredVariables.js]
"use strict";
;
;
;
const dataRefs = toRefs(data);
const { prepared, payload } = dataRefs;
if (prepared.current) {
prepared.current;
payload.current;
data.prepared;
data.payload;
}
if (isDataRefsPrepared(dataRefs)) {
prepared.current;
payload.current;
data.prepared;
data.payload;
}
if (data.prepared) {
prepared.current;
payload.current;
data.prepared;
data.payload;
}

Просмотреть файл

@ -0,0 +1,153 @@
//// [tests/cases/compiler/narrowDestructuredVariables.ts] ////
=== narrowDestructuredVariables.ts ===
interface Ref<T> { current: T };
>Ref : Symbol(Ref, Decl(narrowDestructuredVariables.ts, 0, 0))
>T : Symbol(T, Decl(narrowDestructuredVariables.ts, 0, 14))
>current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18))
>T : Symbol(T, Decl(narrowDestructuredVariables.ts, 0, 14))
type ToRefs<T> = { [K in keyof T]: Ref<T[K]> };
>ToRefs : Symbol(ToRefs, Decl(narrowDestructuredVariables.ts, 0, 32))
>T : Symbol(T, Decl(narrowDestructuredVariables.ts, 1, 12))
>K : Symbol(K, Decl(narrowDestructuredVariables.ts, 1, 20))
>T : Symbol(T, Decl(narrowDestructuredVariables.ts, 1, 12))
>Ref : Symbol(Ref, Decl(narrowDestructuredVariables.ts, 0, 0))
>T : Symbol(T, Decl(narrowDestructuredVariables.ts, 1, 12))
>K : Symbol(K, Decl(narrowDestructuredVariables.ts, 1, 20))
declare function toRefs<T>(o: T): ToRefs<T>;
>toRefs : Symbol(toRefs, Decl(narrowDestructuredVariables.ts, 1, 47))
>T : Symbol(T, Decl(narrowDestructuredVariables.ts, 2, 24))
>o : Symbol(o, Decl(narrowDestructuredVariables.ts, 2, 27))
>T : Symbol(T, Decl(narrowDestructuredVariables.ts, 2, 24))
>ToRefs : Symbol(ToRefs, Decl(narrowDestructuredVariables.ts, 0, 32))
>T : Symbol(T, Decl(narrowDestructuredVariables.ts, 2, 24))
interface DataPrepared {
>DataPrepared : Symbol(DataPrepared, Decl(narrowDestructuredVariables.ts, 2, 44))
prepared: true
>prepared : Symbol(DataPrepared.prepared, Decl(narrowDestructuredVariables.ts, 4, 24))
payload: string
>payload : Symbol(DataPrepared.payload, Decl(narrowDestructuredVariables.ts, 5, 18))
};
interface DataPending {
>DataPending : Symbol(DataPending, Decl(narrowDestructuredVariables.ts, 7, 2))
prepared: false
>prepared : Symbol(DataPending.prepared, Decl(narrowDestructuredVariables.ts, 9, 23))
payload: null
>payload : Symbol(DataPending.payload, Decl(narrowDestructuredVariables.ts, 10, 19))
};
type Data = DataPrepared | DataPending;
>Data : Symbol(Data, Decl(narrowDestructuredVariables.ts, 12, 2))
>DataPrepared : Symbol(DataPrepared, Decl(narrowDestructuredVariables.ts, 2, 44))
>DataPending : Symbol(DataPending, Decl(narrowDestructuredVariables.ts, 7, 2))
declare function isDataRefsPrepared(refs: ToRefs<Data>): refs is ToRefs<DataPrepared>;
>isDataRefsPrepared : Symbol(isDataRefsPrepared, Decl(narrowDestructuredVariables.ts, 14, 39))
>refs : Symbol(refs, Decl(narrowDestructuredVariables.ts, 16, 36))
>ToRefs : Symbol(ToRefs, Decl(narrowDestructuredVariables.ts, 0, 32))
>Data : Symbol(Data, Decl(narrowDestructuredVariables.ts, 12, 2))
>refs : Symbol(refs, Decl(narrowDestructuredVariables.ts, 16, 36))
>ToRefs : Symbol(ToRefs, Decl(narrowDestructuredVariables.ts, 0, 32))
>DataPrepared : Symbol(DataPrepared, Decl(narrowDestructuredVariables.ts, 2, 44))
declare const data: Data;
>data : Symbol(data, Decl(narrowDestructuredVariables.ts, 18, 13))
>Data : Symbol(Data, Decl(narrowDestructuredVariables.ts, 12, 2))
const dataRefs = toRefs(data);
>dataRefs : Symbol(dataRefs, Decl(narrowDestructuredVariables.ts, 19, 5))
>toRefs : Symbol(toRefs, Decl(narrowDestructuredVariables.ts, 1, 47))
>data : Symbol(data, Decl(narrowDestructuredVariables.ts, 18, 13))
const { prepared, payload } = dataRefs;
>prepared : Symbol(prepared, Decl(narrowDestructuredVariables.ts, 20, 7))
>payload : Symbol(payload, Decl(narrowDestructuredVariables.ts, 20, 17))
>dataRefs : Symbol(dataRefs, Decl(narrowDestructuredVariables.ts, 19, 5))
if (prepared.current) {
>prepared.current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18), Decl(narrowDestructuredVariables.ts, 0, 18))
>prepared : Symbol(prepared, Decl(narrowDestructuredVariables.ts, 20, 7))
>current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18), Decl(narrowDestructuredVariables.ts, 0, 18))
prepared.current;
>prepared.current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18))
>prepared : Symbol(prepared, Decl(narrowDestructuredVariables.ts, 20, 7))
>current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18))
payload.current;
>payload.current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18), Decl(narrowDestructuredVariables.ts, 0, 18))
>payload : Symbol(payload, Decl(narrowDestructuredVariables.ts, 20, 17))
>current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18), Decl(narrowDestructuredVariables.ts, 0, 18))
data.prepared;
>data.prepared : Symbol(prepared, Decl(narrowDestructuredVariables.ts, 4, 24), Decl(narrowDestructuredVariables.ts, 9, 23))
>data : Symbol(data, Decl(narrowDestructuredVariables.ts, 18, 13))
>prepared : Symbol(prepared, Decl(narrowDestructuredVariables.ts, 4, 24), Decl(narrowDestructuredVariables.ts, 9, 23))
data.payload;
>data.payload : Symbol(payload, Decl(narrowDestructuredVariables.ts, 5, 18), Decl(narrowDestructuredVariables.ts, 10, 19))
>data : Symbol(data, Decl(narrowDestructuredVariables.ts, 18, 13))
>payload : Symbol(payload, Decl(narrowDestructuredVariables.ts, 5, 18), Decl(narrowDestructuredVariables.ts, 10, 19))
}
if (isDataRefsPrepared(dataRefs)) {
>isDataRefsPrepared : Symbol(isDataRefsPrepared, Decl(narrowDestructuredVariables.ts, 14, 39))
>dataRefs : Symbol(dataRefs, Decl(narrowDestructuredVariables.ts, 19, 5))
prepared.current;
>prepared.current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18))
>prepared : Symbol(prepared, Decl(narrowDestructuredVariables.ts, 20, 7))
>current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18))
payload.current;
>payload.current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18))
>payload : Symbol(payload, Decl(narrowDestructuredVariables.ts, 20, 17))
>current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18))
data.prepared;
>data.prepared : Symbol(prepared, Decl(narrowDestructuredVariables.ts, 4, 24), Decl(narrowDestructuredVariables.ts, 9, 23))
>data : Symbol(data, Decl(narrowDestructuredVariables.ts, 18, 13))
>prepared : Symbol(prepared, Decl(narrowDestructuredVariables.ts, 4, 24), Decl(narrowDestructuredVariables.ts, 9, 23))
data.payload;
>data.payload : Symbol(payload, Decl(narrowDestructuredVariables.ts, 5, 18), Decl(narrowDestructuredVariables.ts, 10, 19))
>data : Symbol(data, Decl(narrowDestructuredVariables.ts, 18, 13))
>payload : Symbol(payload, Decl(narrowDestructuredVariables.ts, 5, 18), Decl(narrowDestructuredVariables.ts, 10, 19))
}
if (data.prepared) {
>data.prepared : Symbol(prepared, Decl(narrowDestructuredVariables.ts, 4, 24), Decl(narrowDestructuredVariables.ts, 9, 23))
>data : Symbol(data, Decl(narrowDestructuredVariables.ts, 18, 13))
>prepared : Symbol(prepared, Decl(narrowDestructuredVariables.ts, 4, 24), Decl(narrowDestructuredVariables.ts, 9, 23))
prepared.current;
>prepared.current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18), Decl(narrowDestructuredVariables.ts, 0, 18))
>prepared : Symbol(prepared, Decl(narrowDestructuredVariables.ts, 20, 7))
>current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18), Decl(narrowDestructuredVariables.ts, 0, 18))
payload.current;
>payload.current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18), Decl(narrowDestructuredVariables.ts, 0, 18))
>payload : Symbol(payload, Decl(narrowDestructuredVariables.ts, 20, 17))
>current : Symbol(Ref.current, Decl(narrowDestructuredVariables.ts, 0, 18), Decl(narrowDestructuredVariables.ts, 0, 18))
data.prepared;
>data.prepared : Symbol(DataPrepared.prepared, Decl(narrowDestructuredVariables.ts, 4, 24))
>data : Symbol(data, Decl(narrowDestructuredVariables.ts, 18, 13))
>prepared : Symbol(DataPrepared.prepared, Decl(narrowDestructuredVariables.ts, 4, 24))
data.payload;
>data.payload : Symbol(DataPrepared.payload, Decl(narrowDestructuredVariables.ts, 5, 18))
>data : Symbol(data, Decl(narrowDestructuredVariables.ts, 18, 13))
>payload : Symbol(DataPrepared.payload, Decl(narrowDestructuredVariables.ts, 5, 18))
}

Просмотреть файл

@ -0,0 +1,198 @@
//// [tests/cases/compiler/narrowDestructuredVariables.ts] ////
=== narrowDestructuredVariables.ts ===
interface Ref<T> { current: T };
>current : T
> : ^
type ToRefs<T> = { [K in keyof T]: Ref<T[K]> };
>ToRefs : ToRefs<T>
> : ^^^^^^^^^
declare function toRefs<T>(o: T): ToRefs<T>;
>toRefs : <T>(o: T) => ToRefs<T>
> : ^ ^^ ^^ ^^^^^
>o : T
> : ^
interface DataPrepared {
prepared: true
>prepared : true
> : ^^^^
>true : true
> : ^^^^
payload: string
>payload : string
> : ^^^^^^
};
interface DataPending {
prepared: false
>prepared : false
> : ^^^^^
>false : false
> : ^^^^^
payload: null
>payload : null
> : ^^^^
};
type Data = DataPrepared | DataPending;
>Data : Data
> : ^^^^
declare function isDataRefsPrepared(refs: ToRefs<Data>): refs is ToRefs<DataPrepared>;
>isDataRefsPrepared : (refs: ToRefs<Data>) => refs is ToRefs<DataPrepared>
> : ^ ^^ ^^^^^
>refs : ToRefs<Data>
> : ^^^^^^^^^^^^
declare const data: Data;
>data : Data
> : ^^^^
const dataRefs = toRefs(data);
>dataRefs : ToRefs<Data>
> : ^^^^^^^^^^^^
>toRefs(data) : ToRefs<Data>
> : ^^^^^^^^^^^^
>toRefs : <T>(o: T) => ToRefs<T>
> : ^ ^^ ^^ ^^^^^
>data : Data
> : ^^^^
const { prepared, payload } = dataRefs;
>prepared : Ref<true> | Ref<false>
> : ^^^^^^^^^^^^^^^^^^^^^^
>payload : Ref<string> | Ref<null>
> : ^^^^^^^^^^^^^^^^^^^^^^^
>dataRefs : ToRefs<Data>
> : ^^^^^^^^^^^^
if (prepared.current) {
>prepared.current : boolean
> : ^^^^^^^
>prepared : Ref<true> | Ref<false>
> : ^^^^^^^^^^^^^^^^^^^^^^
>current : boolean
> : ^^^^^^^
prepared.current;
>prepared.current : true
> : ^^^^
>prepared : Ref<true>
> : ^^^^^^^^^
>current : true
> : ^^^^
payload.current;
>payload.current : string | null
> : ^^^^^^^^^^^^^
>payload : Ref<string> | Ref<null>
> : ^^^^^^^^^^^^^^^^^^^^^^^
>current : string | null
> : ^^^^^^^^^^^^^
data.prepared;
>data.prepared : boolean
> : ^^^^^^^
>data : Data
> : ^^^^
>prepared : boolean
> : ^^^^^^^
data.payload;
>data.payload : string | null
> : ^^^^^^^^^^^^^
>data : Data
> : ^^^^
>payload : string | null
> : ^^^^^^^^^^^^^
}
if (isDataRefsPrepared(dataRefs)) {
>isDataRefsPrepared(dataRefs) : boolean
> : ^^^^^^^
>isDataRefsPrepared : (refs: ToRefs<Data>) => refs is ToRefs<DataPrepared>
> : ^ ^^ ^^^^^
>dataRefs : ToRefs<Data>
> : ^^^^^^^^^^^^
prepared.current;
>prepared.current : true
> : ^^^^
>prepared : Ref<true>
> : ^^^^^^^^^
>current : true
> : ^^^^
payload.current;
>payload.current : string
> : ^^^^^^
>payload : Ref<string>
> : ^^^^^^^^^^^
>current : string
> : ^^^^^^
data.prepared;
>data.prepared : boolean
> : ^^^^^^^
>data : Data
> : ^^^^
>prepared : boolean
> : ^^^^^^^
data.payload;
>data.payload : string | null
> : ^^^^^^^^^^^^^
>data : Data
> : ^^^^
>payload : string | null
> : ^^^^^^^^^^^^^
}
if (data.prepared) {
>data.prepared : boolean
> : ^^^^^^^
>data : Data
> : ^^^^
>prepared : boolean
> : ^^^^^^^
prepared.current;
>prepared.current : boolean
> : ^^^^^^^
>prepared : Ref<true> | Ref<false>
> : ^^^^^^^^^^^^^^^^^^^^^^
>current : boolean
> : ^^^^^^^
payload.current;
>payload.current : string | null
> : ^^^^^^^^^^^^^
>payload : Ref<string> | Ref<null>
> : ^^^^^^^^^^^^^^^^^^^^^^^
>current : string | null
> : ^^^^^^^^^^^^^
data.prepared;
>data.prepared : true
> : ^^^^
>data : DataPrepared
> : ^^^^^^^^^^^^
>prepared : true
> : ^^^^
data.payload;
>data.payload : string
> : ^^^^^^
>data : DataPrepared
> : ^^^^^^^^^^^^
>payload : string
> : ^^^^^^
}

Просмотреть файл

@ -0,0 +1,45 @@
// @strict: true
// @target: es2015
interface Ref<T> { current: T };
type ToRefs<T> = { [K in keyof T]: Ref<T[K]> };
declare function toRefs<T>(o: T): ToRefs<T>;
interface DataPrepared {
prepared: true
payload: string
};
interface DataPending {
prepared: false
payload: null
};
type Data = DataPrepared | DataPending;
declare function isDataRefsPrepared(refs: ToRefs<Data>): refs is ToRefs<DataPrepared>;
declare const data: Data;
const dataRefs = toRefs(data);
const { prepared, payload } = dataRefs;
if (prepared.current) {
prepared.current;
payload.current;
data.prepared;
data.payload;
}
if (isDataRefsPrepared(dataRefs)) {
prepared.current;
payload.current;
data.prepared;
data.payload;
}
if (data.prepared) {
prepared.current;
payload.current;
data.prepared;
data.payload;
}