Skip to content

Commit 1c11e45

Browse files
authored
Fixed dependent variables narrowing when they are declared using nested binding patterns (#56306)
1 parent b6121e4 commit 1c11e45

File tree

4 files changed

+344
-2
lines changed

4 files changed

+344
-2
lines changed

src/compiler/checker.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28718,14 +28718,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2871828718
// destructuring from the narrowed parent type.
2871928719
if (isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) {
2872028720
const parent = declaration.parent.parent;
28721-
if (parent.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlagsCached(declaration) & NodeFlags.Constant || parent.kind === SyntaxKind.Parameter) {
28721+
const rootDeclaration = getRootDeclaration(parent);
28722+
if (rootDeclaration.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlagsCached(rootDeclaration) & NodeFlags.Constant || rootDeclaration.kind === SyntaxKind.Parameter) {
2872228723
const links = getNodeLinks(parent);
2872328724
if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) {
2872428725
links.flags |= NodeCheckFlags.InCheckIdentifier;
2872528726
const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal);
2872628727
const parentTypeConstraint = parentType && mapType(parentType, getBaseConstraintOrType);
2872728728
links.flags &= ~NodeCheckFlags.InCheckIdentifier;
28728-
if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(parent.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) {
28729+
if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) {
2872928730
const pattern = declaration.parent;
2873028731
const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode);
2873128732
if (narrowedType.flags & TypeFlags.Never) {
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
//// [tests/cases/conformance/controlFlow/dependentDestructuredVariablesFromNestedPatterns.ts] ////
2+
3+
=== dependentDestructuredVariablesFromNestedPatterns.ts ===
4+
function test1(arg: [[undefined, Error] | [number, undefined]]) {
5+
>test1 : Symbol(test1, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 0, 0))
6+
>arg : Symbol(arg, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 0, 15))
7+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --))
8+
9+
const [[p1, p1Error]] = arg;
10+
>p1 : Symbol(p1, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 1, 10))
11+
>p1Error : Symbol(p1Error, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 1, 13))
12+
>arg : Symbol(arg, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 0, 15))
13+
14+
if (p1Error) {
15+
>p1Error : Symbol(p1Error, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 1, 13))
16+
17+
return;
18+
}
19+
20+
p1;
21+
>p1 : Symbol(p1, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 1, 10))
22+
}
23+
24+
function test2([[p1, p1Error]]: [[undefined, Error] | [number, undefined]]) {
25+
>test2 : Symbol(test2, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 8, 1))
26+
>p1 : Symbol(p1, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 10, 17))
27+
>p1Error : Symbol(p1Error, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 10, 20))
28+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --))
29+
30+
if (p1Error) {
31+
>p1Error : Symbol(p1Error, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 10, 20))
32+
33+
return;
34+
}
35+
36+
p1;
37+
>p1 : Symbol(p1, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 10, 17))
38+
}
39+
40+
async function myAllSettled<T extends readonly unknown[]>(fn: () => T) {
41+
>myAllSettled : Symbol(myAllSettled, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 16, 1))
42+
>T : Symbol(T, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 18, 28))
43+
>fn : Symbol(fn, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 18, 58))
44+
>T : Symbol(T, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 18, 28))
45+
46+
const promises = await Promise.allSettled(fn());
47+
>promises : Symbol(promises, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 19, 7))
48+
>Promise.allSettled : Symbol(PromiseConstructor.allSettled, Decl(lib.es2020.promise.d.ts, --, --), Decl(lib.es2020.promise.d.ts, --, --))
49+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
50+
>allSettled : Symbol(PromiseConstructor.allSettled, Decl(lib.es2020.promise.d.ts, --, --), Decl(lib.es2020.promise.d.ts, --, --))
51+
>fn : Symbol(fn, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 18, 58))
52+
53+
return promises.map((result) =>
54+
>promises.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
55+
>promises : Symbol(promises, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 19, 7))
56+
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
57+
>result : Symbol(result, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 21, 23))
58+
59+
result.status === "fulfilled"
60+
>result.status : Symbol(status, Decl(lib.es2020.promise.d.ts, --, --), Decl(lib.es2020.promise.d.ts, --, --))
61+
>result : Symbol(result, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 21, 23))
62+
>status : Symbol(status, Decl(lib.es2020.promise.d.ts, --, --), Decl(lib.es2020.promise.d.ts, --, --))
63+
64+
? [result.value, undefined]
65+
>result.value : Symbol(PromiseFulfilledResult.value, Decl(lib.es2020.promise.d.ts, --, --))
66+
>result : Symbol(result, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 21, 23))
67+
>value : Symbol(PromiseFulfilledResult.value, Decl(lib.es2020.promise.d.ts, --, --))
68+
>undefined : Symbol(undefined)
69+
70+
: [undefined, new Error(String(result.reason))],
71+
>undefined : Symbol(undefined)
72+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --))
73+
>String : Symbol(String, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --) ... and 6 more)
74+
>result.reason : Symbol(PromiseRejectedResult.reason, Decl(lib.es2020.promise.d.ts, --, --))
75+
>result : Symbol(result, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 21, 23))
76+
>reason : Symbol(PromiseRejectedResult.reason, Decl(lib.es2020.promise.d.ts, --, --))
77+
78+
) as { [K in keyof T]: [Awaited<T[K]>, undefined] | [undefined, Error] };
79+
>K : Symbol(K, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 25, 10))
80+
>T : Symbol(T, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 18, 28))
81+
>Awaited : Symbol(Awaited, Decl(lib.es5.d.ts, --, --))
82+
>T : Symbol(T, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 18, 28))
83+
>K : Symbol(K, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 25, 10))
84+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --))
85+
}
86+
87+
async function test3() {
88+
>test3 : Symbol(test3, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 26, 1))
89+
90+
const [[p1, p1Error], _] = await myAllSettled(
91+
>p1 : Symbol(p1, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 29, 10))
92+
>p1Error : Symbol(p1Error, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 29, 13))
93+
>_ : Symbol(_, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 29, 23))
94+
>myAllSettled : Symbol(myAllSettled, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 16, 1))
95+
96+
() => [Promise.resolve(0), Promise.reject(1)] as const,
97+
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
98+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
99+
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
100+
>Promise.reject : Symbol(PromiseConstructor.reject, Decl(lib.es2015.promise.d.ts, --, --))
101+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
102+
>reject : Symbol(PromiseConstructor.reject, Decl(lib.es2015.promise.d.ts, --, --))
103+
>const : Symbol(const)
104+
105+
);
106+
107+
if (p1Error) return;
108+
>p1Error : Symbol(p1Error, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 29, 13))
109+
110+
p1;
111+
>p1 : Symbol(p1, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 29, 10))
112+
}
113+
114+
function test4([[p1, p1Error]]: [[undefined, Error] | [number, undefined]]) {
115+
>test4 : Symbol(test4, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 36, 1))
116+
>p1 : Symbol(p1, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 38, 17))
117+
>p1Error : Symbol(p1Error, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 38, 20))
118+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --))
119+
120+
if (Math.random()) {
121+
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
122+
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
123+
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
124+
125+
p1 = undefined;
126+
>p1 : Symbol(p1, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 38, 17))
127+
>undefined : Symbol(undefined)
128+
}
129+
if (p1Error) {
130+
>p1Error : Symbol(p1Error, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 38, 20))
131+
132+
return;
133+
}
134+
135+
p1;
136+
>p1 : Symbol(p1, Decl(dependentDestructuredVariablesFromNestedPatterns.ts, 38, 17))
137+
}
138+
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
//// [tests/cases/conformance/controlFlow/dependentDestructuredVariablesFromNestedPatterns.ts] ////
2+
3+
=== dependentDestructuredVariablesFromNestedPatterns.ts ===
4+
function test1(arg: [[undefined, Error] | [number, undefined]]) {
5+
>test1 : (arg: [[undefined, Error] | [number, undefined]]) => void
6+
>arg : [[undefined, Error] | [number, undefined]]
7+
8+
const [[p1, p1Error]] = arg;
9+
>p1 : number | undefined
10+
>p1Error : Error | undefined
11+
>arg : [[undefined, Error] | [number, undefined]]
12+
13+
if (p1Error) {
14+
>p1Error : Error | undefined
15+
16+
return;
17+
}
18+
19+
p1;
20+
>p1 : number
21+
}
22+
23+
function test2([[p1, p1Error]]: [[undefined, Error] | [number, undefined]]) {
24+
>test2 : ([[p1, p1Error]]: [[undefined, Error] | [number, undefined]]) => void
25+
>p1 : number | undefined
26+
>p1Error : Error | undefined
27+
28+
if (p1Error) {
29+
>p1Error : Error | undefined
30+
31+
return;
32+
}
33+
34+
p1;
35+
>p1 : number
36+
}
37+
38+
async function myAllSettled<T extends readonly unknown[]>(fn: () => T) {
39+
>myAllSettled : <T extends readonly unknown[]>(fn: () => T) => Promise<{ [K in keyof T]: [undefined, Error] | [Awaited<T[K]>, undefined]; }>
40+
>fn : () => T
41+
42+
const promises = await Promise.allSettled(fn());
43+
>promises : { -readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>>; }
44+
>await Promise.allSettled(fn()) : { -readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>>; }
45+
>Promise.allSettled(fn()) : Promise<{ -readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>>; }>
46+
>Promise.allSettled : { <T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>>; }>; <T>(values: Iterable<T | PromiseLike<T>>): Promise<PromiseSettledResult<Awaited<T>>[]>; }
47+
>Promise : PromiseConstructor
48+
>allSettled : { <T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>>; }>; <T>(values: Iterable<T | PromiseLike<T>>): Promise<PromiseSettledResult<Awaited<T>>[]>; }
49+
>fn() : T
50+
>fn : () => T
51+
52+
return promises.map((result) =>
53+
>promises.map((result) => result.status === "fulfilled" ? [result.value, undefined] : [undefined, new Error(String(result.reason))], ) as { [K in keyof T]: [Awaited<T[K]>, undefined] | [undefined, Error] } : { [K in keyof T]: [undefined, Error] | [Awaited<T[K]>, undefined]; }
54+
>promises.map((result) => result.status === "fulfilled" ? [result.value, undefined] : [undefined, new Error(String(result.reason))], ) : ([undefined, Error] | [unknown, undefined])[]
55+
>promises.map : <U>(callbackfn: (value: PromiseSettledResult<unknown>, index: number, array: PromiseSettledResult<unknown>[]) => U, thisArg?: any) => U[]
56+
>promises : { -readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>>; }
57+
>map : <U>(callbackfn: (value: PromiseSettledResult<unknown>, index: number, array: PromiseSettledResult<unknown>[]) => U, thisArg?: any) => U[]
58+
>(result) => result.status === "fulfilled" ? [result.value, undefined] : [undefined, new Error(String(result.reason))] : (result: PromiseSettledResult<unknown>) => [undefined, Error] | [unknown, undefined]
59+
>result : PromiseSettledResult<unknown>
60+
61+
result.status === "fulfilled"
62+
>result.status === "fulfilled" ? [result.value, undefined] : [undefined, new Error(String(result.reason))] : [unknown, undefined] | [undefined, Error]
63+
>result.status === "fulfilled" : boolean
64+
>result.status : "rejected" | "fulfilled"
65+
>result : PromiseSettledResult<unknown>
66+
>status : "rejected" | "fulfilled"
67+
>"fulfilled" : "fulfilled"
68+
69+
? [result.value, undefined]
70+
>[result.value, undefined] : [unknown, undefined]
71+
>result.value : unknown
72+
>result : PromiseFulfilledResult<unknown>
73+
>value : unknown
74+
>undefined : undefined
75+
76+
: [undefined, new Error(String(result.reason))],
77+
>[undefined, new Error(String(result.reason))] : [undefined, Error]
78+
>undefined : undefined
79+
>new Error(String(result.reason)) : Error
80+
>Error : ErrorConstructor
81+
>String(result.reason) : string
82+
>String : StringConstructor
83+
>result.reason : any
84+
>result : PromiseRejectedResult
85+
>reason : any
86+
87+
) as { [K in keyof T]: [Awaited<T[K]>, undefined] | [undefined, Error] };
88+
}
89+
90+
async function test3() {
91+
>test3 : () => Promise<void>
92+
93+
const [[p1, p1Error], _] = await myAllSettled(
94+
>p1 : number | undefined
95+
>p1Error : Error | undefined
96+
>_ : [undefined, Error] | [never, undefined]
97+
>await myAllSettled( () => [Promise.resolve(0), Promise.reject(1)] as const, ) : [[undefined, Error] | [number, undefined], [undefined, Error] | [never, undefined]]
98+
>myAllSettled( () => [Promise.resolve(0), Promise.reject(1)] as const, ) : Promise<[[undefined, Error] | [number, undefined], [undefined, Error] | [never, undefined]]>
99+
>myAllSettled : <T extends readonly unknown[]>(fn: () => T) => Promise<{ [K in keyof T]: [undefined, Error] | [Awaited<T[K]>, undefined]; }>
100+
101+
() => [Promise.resolve(0), Promise.reject(1)] as const,
102+
>() => [Promise.resolve(0), Promise.reject(1)] as const : () => [Promise<number>, Promise<never>]
103+
>[Promise.resolve(0), Promise.reject(1)] as const : [Promise<number>, Promise<never>]
104+
>[Promise.resolve(0), Promise.reject(1)] : [Promise<number>, Promise<never>]
105+
>Promise.resolve(0) : Promise<number>
106+
>Promise.resolve : { (): Promise<void>; <T>(value: T): Promise<Awaited<T>>; <T>(value: T | PromiseLike<T>): Promise<Awaited<T>>; }
107+
>Promise : PromiseConstructor
108+
>resolve : { (): Promise<void>; <T>(value: T): Promise<Awaited<T>>; <T>(value: T | PromiseLike<T>): Promise<Awaited<T>>; }
109+
>0 : 0
110+
>Promise.reject(1) : Promise<never>
111+
>Promise.reject : <T = never>(reason?: any) => Promise<T>
112+
>Promise : PromiseConstructor
113+
>reject : <T = never>(reason?: any) => Promise<T>
114+
>1 : 1
115+
116+
);
117+
118+
if (p1Error) return;
119+
>p1Error : Error | undefined
120+
121+
p1;
122+
>p1 : number
123+
}
124+
125+
function test4([[p1, p1Error]]: [[undefined, Error] | [number, undefined]]) {
126+
>test4 : ([[p1, p1Error]]: [[undefined, Error] | [number, undefined]]) => void
127+
>p1 : number | undefined
128+
>p1Error : Error | undefined
129+
130+
if (Math.random()) {
131+
>Math.random() : number
132+
>Math.random : () => number
133+
>Math : Math
134+
>random : () => number
135+
136+
p1 = undefined;
137+
>p1 = undefined : undefined
138+
>p1 : number | undefined
139+
>undefined : undefined
140+
}
141+
if (p1Error) {
142+
>p1Error : Error | undefined
143+
144+
return;
145+
}
146+
147+
p1;
148+
>p1 : number | undefined
149+
}
150+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// @strict: true
2+
// @target: esnext
3+
// @lib: esnext
4+
// @noEmit: true
5+
6+
function test1(arg: [[undefined, Error] | [number, undefined]]) {
7+
const [[p1, p1Error]] = arg;
8+
9+
if (p1Error) {
10+
return;
11+
}
12+
13+
p1;
14+
}
15+
16+
function test2([[p1, p1Error]]: [[undefined, Error] | [number, undefined]]) {
17+
if (p1Error) {
18+
return;
19+
}
20+
21+
p1;
22+
}
23+
24+
async function myAllSettled<T extends readonly unknown[]>(fn: () => T) {
25+
const promises = await Promise.allSettled(fn());
26+
27+
return promises.map((result) =>
28+
result.status === "fulfilled"
29+
? [result.value, undefined]
30+
: [undefined, new Error(String(result.reason))],
31+
) as { [K in keyof T]: [Awaited<T[K]>, undefined] | [undefined, Error] };
32+
}
33+
34+
async function test3() {
35+
const [[p1, p1Error], _] = await myAllSettled(
36+
() => [Promise.resolve(0), Promise.reject(1)] as const,
37+
);
38+
39+
if (p1Error) return;
40+
41+
p1;
42+
}
43+
44+
function test4([[p1, p1Error]]: [[undefined, Error] | [number, undefined]]) {
45+
if (Math.random()) {
46+
p1 = undefined;
47+
}
48+
if (p1Error) {
49+
return;
50+
}
51+
52+
p1;
53+
}

0 commit comments

Comments
 (0)