Skip to content

Commit 9999f26

Browse files
authored
Improve binding element type inference using CheckMode (rebased) (#56753)
Signed-off-by: Babak K. Shandiz <[email protected]>
1 parent 385db44 commit 9999f26

10 files changed

+500
-16
lines changed

src/compiler/checker.ts

+30-10
Original file line numberDiff line numberDiff line change
@@ -11460,7 +11460,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1146011460
// contextual type or, if the element itself is a binding pattern, with the type implied by that binding
1146111461
// pattern.
1146211462
const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType;
11463-
return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType)));
11463+
return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, reportErrors ? CheckMode.Normal : CheckMode.Contextual, contextualType)));
1146411464
}
1146511465
if (isBindingPattern(element.name)) {
1146611466
return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors);
@@ -11618,24 +11618,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1161811618
return false;
1161911619
}
1162011620

11621-
function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type {
11621+
function getTypeOfVariableOrParameterOrProperty(symbol: Symbol, checkMode?: CheckMode): Type {
1162211622
const links = getSymbolLinks(symbol);
1162311623
if (!links.type) {
11624-
const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol);
11624+
const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol, checkMode);
1162511625
// For a contextually typed parameter it is possible that a type has already
1162611626
// been assigned (in assignTypeToParameterAndFixTypeParameters), and we want
1162711627
// to preserve this type. In fact, we need to _prefer_ that type, but it won't
1162811628
// be assigned until contextual typing is complete, so we need to defer in
1162911629
// cases where contextual typing may take place.
11630-
if (!links.type && !isParameterOfContextSensitiveSignature(symbol)) {
11630+
if (!links.type && !isParameterOfContextSensitiveSignature(symbol) && !checkMode) {
1163111631
links.type = type;
1163211632
}
1163311633
return type;
1163411634
}
1163511635
return links.type;
1163611636
}
1163711637

11638-
function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol): Type {
11638+
function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol, checkMode?: CheckMode): Type {
1163911639
// Handle prototype property
1164011640
if (symbol.flags & SymbolFlags.Prototype) {
1164111641
return getTypeOfPrototypeProperty(symbol);
@@ -11678,6 +11678,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1167811678
if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) {
1167911679
return getTypeOfFuncClassEnumModule(symbol);
1168011680
}
11681+
11682+
// When trying to get the *contextual* type of a binding element, it's possible to fall in a loop and therefore
11683+
// end up in a circularity-like situation. This is not a true circularity so we should not report such an error.
11684+
// For example, here the looping could happen when trying to get the type of `a` (binding element):
11685+
//
11686+
// const { a, b = a } = { a: 0 }
11687+
//
11688+
if (isBindingElement(declaration) && checkMode === CheckMode.Contextual) {
11689+
return errorType;
11690+
}
1168111691
return reportCircularityError(symbol);
1168211692
}
1168311693
let type: Type;
@@ -11750,6 +11760,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1175011760
if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) {
1175111761
return getTypeOfFuncClassEnumModule(symbol);
1175211762
}
11763+
11764+
// When trying to get the *contextual* type of a binding element, it's possible to fall in a loop and therefore
11765+
// end up in a circularity-like situation. This is not a true circularity so we should not report such an error.
11766+
// For example, here the looping could happen when trying to get the type of `a` (binding element):
11767+
//
11768+
// const { a, b = a } = { a: 0 }
11769+
//
11770+
if (isBindingElement(declaration) && checkMode === CheckMode.Contextual) {
11771+
return type;
11772+
}
1175311773
return reportCircularityError(symbol);
1175411774
}
1175511775
return type;
@@ -12032,7 +12052,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1203212052
return getTypeOfSymbol(symbol);
1203312053
}
1203412054

12035-
function getTypeOfSymbol(symbol: Symbol): Type {
12055+
function getTypeOfSymbol(symbol: Symbol, checkMode?: CheckMode): Type {
1203612056
const checkFlags = getCheckFlags(symbol);
1203712057
if (checkFlags & CheckFlags.DeferredType) {
1203812058
return getTypeOfSymbolWithDeferredType(symbol);
@@ -12047,7 +12067,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1204712067
return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol);
1204812068
}
1204912069
if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) {
12050-
return getTypeOfVariableOrParameterOrProperty(symbol);
12070+
return getTypeOfVariableOrParameterOrProperty(symbol, checkMode);
1205112071
}
1205212072
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
1205312073
return getTypeOfFuncClassEnumModule(symbol);
@@ -29069,8 +29089,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2906929089
}
2907029090
}
2907129091

29072-
function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) {
29073-
const type = getTypeOfSymbol(symbol);
29092+
function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier, checkMode?: CheckMode) {
29093+
const type = getTypeOfSymbol(symbol, checkMode);
2907429094
const declaration = symbol.valueDeclaration;
2907529095
if (declaration) {
2907629096
// If we have a non-rest binding element with no initializer declared as a const variable or a const-like
@@ -29233,7 +29253,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2923329253

2923429254
checkNestedBlockScopedBinding(node, symbol);
2923529255

29236-
let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node);
29256+
let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node, checkMode);
2923729257
const assignmentKind = getAssignmentTargetKind(node);
2923829258

2923929259
if (assignmentKind) {

tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
=== destructuringArrayBindingPatternAndAssignment3.ts ===
44
const [a, b = a] = [1]; // ok
5-
>a : any
6-
>b : any
7-
>a : any
5+
>a : number
6+
>b : number
7+
>a : number
88
>[1] : [number]
99
>1 : 1
1010

1111
const [c, d = c, e = e] = [1]; // error for e = e
12-
>c : any
13-
>d : any
14-
>c : any
12+
>c : number
13+
>d : number
14+
>c : number
1515
>e : any
1616
>e : any
1717
>[1] : [number]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//// [tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts] ////
2+
3+
//// [destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts]
4+
// To be inferred as `number`
5+
function f1() {
6+
const [a1, b1 = a1] = [1];
7+
const [a2, b2 = 1 + a2] = [1];
8+
}
9+
10+
// To be inferred as `string`
11+
function f2() {
12+
const [a1, b1 = a1] = ['hi'];
13+
const [a2, b2 = a2 + '!'] = ['hi'];
14+
}
15+
16+
// To be inferred as `string | number`
17+
function f3() {
18+
const [a1, b1 = a1] = ['hi', 1];
19+
const [a2, b2 = a2 + '!'] = ['hi', 1];
20+
}
21+
22+
// Based on comment:
23+
// - https://github.com/microsoft/TypeScript/issues/49989#issuecomment-1852694486
24+
declare const yadda: [number, number] | undefined
25+
function f4() {
26+
const [ a, b = a ] = yadda ?? [];
27+
}
28+
29+
30+
//// [destructuringArrayBindingPatternAndAssignment5SiblingInitializer.js]
31+
// To be inferred as `number`
32+
function f1() {
33+
var _a = [1], a1 = _a[0], _b = _a[1], b1 = _b === void 0 ? a1 : _b;
34+
var _c = [1], a2 = _c[0], _d = _c[1], b2 = _d === void 0 ? 1 + a2 : _d;
35+
}
36+
// To be inferred as `string`
37+
function f2() {
38+
var _a = ['hi'], a1 = _a[0], _b = _a[1], b1 = _b === void 0 ? a1 : _b;
39+
var _c = ['hi'], a2 = _c[0], _d = _c[1], b2 = _d === void 0 ? a2 + '!' : _d;
40+
}
41+
// To be inferred as `string | number`
42+
function f3() {
43+
var _a = ['hi', 1], a1 = _a[0], _b = _a[1], b1 = _b === void 0 ? a1 : _b;
44+
var _c = ['hi', 1], a2 = _c[0], _d = _c[1], b2 = _d === void 0 ? a2 + '!' : _d;
45+
}
46+
function f4() {
47+
var _a = yadda !== null && yadda !== void 0 ? yadda : [], a = _a[0], _b = _a[1], b = _b === void 0 ? a : _b;
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//// [tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts] ////
2+
3+
=== destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts ===
4+
// To be inferred as `number`
5+
function f1() {
6+
>f1 : Symbol(f1, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 0, 0))
7+
8+
const [a1, b1 = a1] = [1];
9+
>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 2, 11))
10+
>b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 2, 14))
11+
>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 2, 11))
12+
13+
const [a2, b2 = 1 + a2] = [1];
14+
>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 3, 11))
15+
>b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 3, 14))
16+
>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 3, 11))
17+
}
18+
19+
// To be inferred as `string`
20+
function f2() {
21+
>f2 : Symbol(f2, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 4, 1))
22+
23+
const [a1, b1 = a1] = ['hi'];
24+
>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 8, 11))
25+
>b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 8, 14))
26+
>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 8, 11))
27+
28+
const [a2, b2 = a2 + '!'] = ['hi'];
29+
>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 9, 11))
30+
>b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 9, 14))
31+
>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 9, 11))
32+
}
33+
34+
// To be inferred as `string | number`
35+
function f3() {
36+
>f3 : Symbol(f3, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 10, 1))
37+
38+
const [a1, b1 = a1] = ['hi', 1];
39+
>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 14, 11))
40+
>b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 14, 14))
41+
>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 14, 11))
42+
43+
const [a2, b2 = a2 + '!'] = ['hi', 1];
44+
>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 15, 11))
45+
>b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 15, 14))
46+
>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 15, 11))
47+
}
48+
49+
// Based on comment:
50+
// - https://github.com/microsoft/TypeScript/issues/49989#issuecomment-1852694486
51+
declare const yadda: [number, number] | undefined
52+
>yadda : Symbol(yadda, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 20, 13))
53+
54+
function f4() {
55+
>f4 : Symbol(f4, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 20, 49))
56+
57+
const [ a, b = a ] = yadda ?? [];
58+
>a : Symbol(a, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 22, 11))
59+
>b : Symbol(b, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 22, 14))
60+
>a : Symbol(a, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 22, 11))
61+
>yadda : Symbol(yadda, Decl(destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts, 20, 13))
62+
}
63+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//// [tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts] ////
2+
3+
=== destructuringArrayBindingPatternAndAssignment5SiblingInitializer.ts ===
4+
// To be inferred as `number`
5+
function f1() {
6+
>f1 : () => void
7+
8+
const [a1, b1 = a1] = [1];
9+
>a1 : number
10+
>b1 : number
11+
>a1 : number
12+
>[1] : [number]
13+
>1 : 1
14+
15+
const [a2, b2 = 1 + a2] = [1];
16+
>a2 : number
17+
>b2 : number
18+
>1 + a2 : number
19+
>1 : 1
20+
>a2 : number
21+
>[1] : [number]
22+
>1 : 1
23+
}
24+
25+
// To be inferred as `string`
26+
function f2() {
27+
>f2 : () => void
28+
29+
const [a1, b1 = a1] = ['hi'];
30+
>a1 : string
31+
>b1 : string
32+
>a1 : string
33+
>['hi'] : [string]
34+
>'hi' : "hi"
35+
36+
const [a2, b2 = a2 + '!'] = ['hi'];
37+
>a2 : string
38+
>b2 : string
39+
>a2 + '!' : string
40+
>a2 : string
41+
>'!' : "!"
42+
>['hi'] : [string]
43+
>'hi' : "hi"
44+
}
45+
46+
// To be inferred as `string | number`
47+
function f3() {
48+
>f3 : () => void
49+
50+
const [a1, b1 = a1] = ['hi', 1];
51+
>a1 : string
52+
>b1 : string | number
53+
>a1 : string
54+
>['hi', 1] : [string, number]
55+
>'hi' : "hi"
56+
>1 : 1
57+
58+
const [a2, b2 = a2 + '!'] = ['hi', 1];
59+
>a2 : string
60+
>b2 : string | number
61+
>a2 + '!' : string
62+
>a2 : string
63+
>'!' : "!"
64+
>['hi', 1] : [string, number]
65+
>'hi' : "hi"
66+
>1 : 1
67+
}
68+
69+
// Based on comment:
70+
// - https://github.com/microsoft/TypeScript/issues/49989#issuecomment-1852694486
71+
declare const yadda: [number, number] | undefined
72+
>yadda : [number, number]
73+
74+
function f4() {
75+
>f4 : () => void
76+
77+
const [ a, b = a ] = yadda ?? [];
78+
>a : number
79+
>b : number
80+
>a : number
81+
>yadda ?? [] : [number, number] | []
82+
>yadda : [number, number]
83+
>[] : []
84+
}
85+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//// [tests/cases/conformance/es6/destructuring/destructuringObjectBindingPatternAndAssignment9SiblingInitializer.ts] ////
2+
3+
//// [destructuringObjectBindingPatternAndAssignment9SiblingInitializer.ts]
4+
// To be inferred as `number`
5+
function f1() {
6+
const { a1, b1 = a1 } = { a1: 1 };
7+
const { a2, b2 = 1 + a2 } = { a2: 1 };
8+
}
9+
10+
// To be inferred as `string`
11+
function f2() {
12+
const { a1, b1 = a1 } = { a1: 'hi' };
13+
const { a2, b2 = a2 + '!' } = { a2: 'hi' };
14+
}
15+
16+
// To be inferred as `string | number`
17+
function f3() {
18+
const { a1, b1 = a1 } = { a1: 'hi', b1: 1 };
19+
const { a2, b2 = a2 + '!' } = { a2: 'hi', b2: 1 };
20+
}
21+
22+
// Based on comment:
23+
// - https://github.com/microsoft/TypeScript/issues/49989#issuecomment-1852694486
24+
declare const yadda: { a?: number, b?: number } | undefined
25+
function f4() {
26+
const { a, b = a } = yadda ?? {};
27+
}
28+
29+
30+
//// [destructuringObjectBindingPatternAndAssignment9SiblingInitializer.js]
31+
// To be inferred as `number`
32+
function f1() {
33+
var _a = { a1: 1 }, a1 = _a.a1, _b = _a.b1, b1 = _b === void 0 ? a1 : _b;
34+
var _c = { a2: 1 }, a2 = _c.a2, _d = _c.b2, b2 = _d === void 0 ? 1 + a2 : _d;
35+
}
36+
// To be inferred as `string`
37+
function f2() {
38+
var _a = { a1: 'hi' }, a1 = _a.a1, _b = _a.b1, b1 = _b === void 0 ? a1 : _b;
39+
var _c = { a2: 'hi' }, a2 = _c.a2, _d = _c.b2, b2 = _d === void 0 ? a2 + '!' : _d;
40+
}
41+
// To be inferred as `string | number`
42+
function f3() {
43+
var _a = { a1: 'hi', b1: 1 }, a1 = _a.a1, _b = _a.b1, b1 = _b === void 0 ? a1 : _b;
44+
var _c = { a2: 'hi', b2: 1 }, a2 = _c.a2, _d = _c.b2, b2 = _d === void 0 ? a2 + '!' : _d;
45+
}
46+
function f4() {
47+
var _a = yadda !== null && yadda !== void 0 ? yadda : {}, a = _a.a, _b = _a.b, b = _b === void 0 ? a : _b;
48+
}

0 commit comments

Comments
 (0)