Skip to content

Commit f8bfc6f

Browse files
authored
Contextually typed binding element initializers (microsoft#35855)
* Binding element initializers contextually typed by parent initializers * Accept new baselines * Literal type widening should be last step in inference * Accept new baselines * Add tests
1 parent df3b5bb commit f8bfc6f

10 files changed

+243
-72
lines changed

src/compiler/checker.ts

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6981,7 +6981,7 @@ namespace ts {
69816981
getTypeWithFacts(type, TypeFacts.NEUndefined) :
69826982
type;
69836983
}
6984-
return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype);
6984+
return widenTypeInferredFromInitializer(declaration, getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype));
69856985
}
69866986

69876987
function getTypeForDeclarationFromJSDocComment(declaration: Node) {
@@ -7098,7 +7098,7 @@ namespace ts {
70987098
// Use the type of the initializer expression if one is present and the declaration is
70997099
// not a parameter of a contextually typed function
71007100
if (declaration.initializer && !isParameterOfContextuallyTypedFunction(declaration)) {
7101-
const type = checkDeclarationInitializer(declaration);
7101+
const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration));
71027102
return addOptionality(type, isOptional);
71037103
}
71047104

@@ -7330,7 +7330,11 @@ namespace ts {
73307330
// pattern. Otherwise, it is the type any.
73317331
function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type {
73327332
if (element.initializer) {
7333-
return addOptionality(checkDeclarationInitializer(element));
7333+
// The type implied by a binding pattern is independent of context, so we check the initializer with no
7334+
// contextual type or, if the element itself is a binding pattern, with the type implied by that binding
7335+
// pattern.
7336+
const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType;
7337+
return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, contextualType)));
73347338
}
73357339
if (isBindingPattern(element.name)) {
73367340
return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors);
@@ -21195,9 +21199,10 @@ namespace ts {
2119521199
}
2119621200

2119721201
function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined {
21198-
const parentDeclaration = declaration.parent.parent;
21202+
const parent = declaration.parent.parent;
2119921203
const name = declaration.propertyName || declaration.name;
21200-
const parentType = getContextualTypeForVariableLikeDeclaration(parentDeclaration);
21204+
const parentType = getContextualTypeForVariableLikeDeclaration(parent) ||
21205+
parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent);
2120121206
if (parentType && !isBindingPattern(name) && !isComputedNonLiteralName(name)) {
2120221207
const nameType = getLiteralTypeFromPropertyName(name);
2120321208
if (isTypeUsableAsPropertyName(nameType)) {
@@ -27661,27 +27666,13 @@ namespace ts {
2766127666
return node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression;
2766227667
}
2766327668

27664-
function checkDeclarationInitializer(declaration: HasExpressionInitializer) {
27669+
function checkDeclarationInitializer(declaration: HasExpressionInitializer, contextualType?: Type | undefined) {
2766527670
const initializer = getEffectiveInitializer(declaration)!;
27666-
const type = getQuickTypeOfExpression(initializer) || checkExpressionCached(initializer);
27667-
const padded = isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
27671+
const type = getQuickTypeOfExpression(initializer) ||
27672+
(contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, CheckMode.Normal) : checkExpressionCached(initializer));
27673+
return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
2766827674
isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ?
2766927675
padTupleType(type, declaration.name) : type;
27670-
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const ||
27671-
isDeclarationReadonly(declaration) ||
27672-
isTypeAssertion(initializer) ||
27673-
isLiteralOfContextualType(padded, getContextualType(initializer)) ? padded : getWidenedLiteralType(padded);
27674-
if (isInJSFile(declaration)) {
27675-
if (widened.flags & TypeFlags.Nullable) {
27676-
reportImplicitAny(declaration, anyType);
27677-
return anyType;
27678-
}
27679-
else if (isEmptyArrayLiteralType(widened)) {
27680-
reportImplicitAny(declaration, anyArrayType);
27681-
return anyArrayType;
27682-
}
27683-
}
27684-
return widened;
2768527676
}
2768627677

2768727678
function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) {
@@ -27700,6 +27691,21 @@ namespace ts {
2770027691
return createTupleType(elementTypes, type.target.minLength, /*hasRestElement*/ false, type.target.readonly);
2770127692
}
2770227693

27694+
function widenTypeInferredFromInitializer(declaration: HasExpressionInitializer, type: Type) {
27695+
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || isDeclarationReadonly(declaration) ? type : getWidenedLiteralType(type);
27696+
if (isInJSFile(declaration)) {
27697+
if (widened.flags & TypeFlags.Nullable) {
27698+
reportImplicitAny(declaration, anyType);
27699+
return anyType;
27700+
}
27701+
else if (isEmptyArrayLiteralType(widened)) {
27702+
reportImplicitAny(declaration, anyArrayType);
27703+
return anyArrayType;
27704+
}
27705+
}
27706+
return widened;
27707+
}
27708+
2770327709
function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean {
2770427710
if (contextualType) {
2770527711
if (contextualType.flags & TypeFlags.UnionOrIntersection) {

tests/baselines/reference/crashInGetTextOfComputedPropertyName.errors.txt

Lines changed: 0 additions & 36 deletions
This file was deleted.

tests/baselines/reference/crashInGetTextOfComputedPropertyName.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,16 @@ const {
5656
items: { [itemId]: itemWithTSError } = {} /*happens when default value is provided*/
5757
>items : any
5858
>itemId : "some-id"
59-
>itemWithTSError : any
60-
>{} : { "some-id": any; }
59+
>itemWithTSError : AB
60+
>{} : {}
6161

6262
} = objWithItems
6363
>objWithItems : ObjWithItems
6464

6565
// in order to re-produce the error, uncomment next line:
6666
typeof itemWithTSError // :(
6767
>typeof itemWithTSError : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
68-
>itemWithTSError : any
68+
>itemWithTSError : AB
6969

7070
// will result in:
7171
// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//// [literalTypesAndDestructuring.ts]
2+
declare let x: { a: 0 | 1 | undefined };
3+
4+
let { a: a1 } = x;
5+
let { a: a2 = 0 } = x;
6+
let { a: a3 = 2 } = x;
7+
let { a: a4 = 2 as const } = x;
8+
9+
let b1 = x.a;
10+
let b2 = x.a ?? 0;
11+
let b3 = x.a ?? 2;
12+
let b4 = x.a ?? 2 as const;
13+
14+
// Repro from #35693
15+
16+
interface Foo {
17+
bar: 'yo' | 'ha' | undefined;
18+
}
19+
20+
let { bar = 'yo' } = {} as Foo;
21+
22+
bar; // "yo" | "ha"
23+
24+
25+
//// [literalTypesAndDestructuring.js]
26+
"use strict";
27+
var _a, _b, _c;
28+
var a1 = x.a;
29+
var _d = x.a, a2 = _d === void 0 ? 0 : _d;
30+
var _e = x.a, a3 = _e === void 0 ? 2 : _e;
31+
var _f = x.a, a4 = _f === void 0 ? 2 : _f;
32+
var b1 = x.a;
33+
var b2 = (_a = x.a) !== null && _a !== void 0 ? _a : 0;
34+
var b3 = (_b = x.a) !== null && _b !== void 0 ? _b : 2;
35+
var b4 = (_c = x.a) !== null && _c !== void 0 ? _c : 2;
36+
var _g = {}.bar, bar = _g === void 0 ? 'yo' : _g;
37+
bar; // "yo" | "ha"
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
=== tests/cases/conformance/types/literal/literalTypesAndDestructuring.ts ===
2+
declare let x: { a: 0 | 1 | undefined };
3+
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
4+
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
5+
6+
let { a: a1 } = x;
7+
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
8+
>a1 : Symbol(a1, Decl(literalTypesAndDestructuring.ts, 2, 5))
9+
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
10+
11+
let { a: a2 = 0 } = x;
12+
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
13+
>a2 : Symbol(a2, Decl(literalTypesAndDestructuring.ts, 3, 5))
14+
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
15+
16+
let { a: a3 = 2 } = x;
17+
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
18+
>a3 : Symbol(a3, Decl(literalTypesAndDestructuring.ts, 4, 5))
19+
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
20+
21+
let { a: a4 = 2 as const } = x;
22+
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
23+
>a4 : Symbol(a4, Decl(literalTypesAndDestructuring.ts, 5, 5))
24+
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
25+
26+
let b1 = x.a;
27+
>b1 : Symbol(b1, Decl(literalTypesAndDestructuring.ts, 7, 3))
28+
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
29+
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
30+
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
31+
32+
let b2 = x.a ?? 0;
33+
>b2 : Symbol(b2, Decl(literalTypesAndDestructuring.ts, 8, 3))
34+
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
35+
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
36+
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
37+
38+
let b3 = x.a ?? 2;
39+
>b3 : Symbol(b3, Decl(literalTypesAndDestructuring.ts, 9, 3))
40+
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
41+
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
42+
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
43+
44+
let b4 = x.a ?? 2 as const;
45+
>b4 : Symbol(b4, Decl(literalTypesAndDestructuring.ts, 10, 3))
46+
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
47+
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
48+
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
49+
50+
// Repro from #35693
51+
52+
interface Foo {
53+
>Foo : Symbol(Foo, Decl(literalTypesAndDestructuring.ts, 10, 27))
54+
55+
bar: 'yo' | 'ha' | undefined;
56+
>bar : Symbol(Foo.bar, Decl(literalTypesAndDestructuring.ts, 14, 15))
57+
}
58+
59+
let { bar = 'yo' } = {} as Foo;
60+
>bar : Symbol(bar, Decl(literalTypesAndDestructuring.ts, 18, 5))
61+
>Foo : Symbol(Foo, Decl(literalTypesAndDestructuring.ts, 10, 27))
62+
63+
bar; // "yo" | "ha"
64+
>bar : Symbol(bar, Decl(literalTypesAndDestructuring.ts, 18, 5))
65+
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
=== tests/cases/conformance/types/literal/literalTypesAndDestructuring.ts ===
2+
declare let x: { a: 0 | 1 | undefined };
3+
>x : { a: 0 | 1 | undefined; }
4+
>a : 0 | 1 | undefined
5+
6+
let { a: a1 } = x;
7+
>a : any
8+
>a1 : 0 | 1 | undefined
9+
>x : { a: 0 | 1 | undefined; }
10+
11+
let { a: a2 = 0 } = x;
12+
>a : any
13+
>a2 : 0 | 1
14+
>0 : 0
15+
>x : { a: 0 | 1 | undefined; }
16+
17+
let { a: a3 = 2 } = x;
18+
>a : any
19+
>a3 : number
20+
>2 : 2
21+
>x : { a: 0 | 1 | undefined; }
22+
23+
let { a: a4 = 2 as const } = x;
24+
>a : any
25+
>a4 : 0 | 1 | 2
26+
>2 as const : 2
27+
>2 : 2
28+
>x : { a: 0 | 1 | undefined; }
29+
30+
let b1 = x.a;
31+
>b1 : 0 | 1 | undefined
32+
>x.a : 0 | 1 | undefined
33+
>x : { a: 0 | 1 | undefined; }
34+
>a : 0 | 1 | undefined
35+
36+
let b2 = x.a ?? 0;
37+
>b2 : 0 | 1
38+
>x.a ?? 0 : 0 | 1
39+
>x.a : 0 | 1 | undefined
40+
>x : { a: 0 | 1 | undefined; }
41+
>a : 0 | 1 | undefined
42+
>0 : 0
43+
44+
let b3 = x.a ?? 2;
45+
>b3 : number
46+
>x.a ?? 2 : 0 | 1 | 2
47+
>x.a : 0 | 1 | undefined
48+
>x : { a: 0 | 1 | undefined; }
49+
>a : 0 | 1 | undefined
50+
>2 : 2
51+
52+
let b4 = x.a ?? 2 as const;
53+
>b4 : 0 | 1 | 2
54+
>x.a ?? 2 as const : 0 | 1 | 2
55+
>x.a : 0 | 1 | undefined
56+
>x : { a: 0 | 1 | undefined; }
57+
>a : 0 | 1 | undefined
58+
>2 as const : 2
59+
>2 : 2
60+
61+
// Repro from #35693
62+
63+
interface Foo {
64+
bar: 'yo' | 'ha' | undefined;
65+
>bar : "yo" | "ha" | undefined
66+
}
67+
68+
let { bar = 'yo' } = {} as Foo;
69+
>bar : "yo" | "ha"
70+
>'yo' : "yo"
71+
>{} as Foo : Foo
72+
>{} : {}
73+
74+
bar; // "yo" | "ha"
75+
>bar : "yo" | "ha"
76+

tests/baselines/reference/literalTypesAndTypeAssertions.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ let { a = "foo" } = { a: "foo" };
3636
>"foo" : "foo"
3737

3838
let { b = "foo" as "foo" } = { b: "bar" };
39-
>b : "foo" | "bar"
39+
>b : string
4040
>"foo" as "foo" : "foo"
4141
>"foo" : "foo"
4242
>{ b: "bar" } : { b?: "bar"; }

tests/baselines/reference/sourceMapValidationDestructuringForObjectBindingPatternDefaultValues.types

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ for (let {
144144
>"secondary" : "secondary"
145145

146146
} = { primary: "none", secondary: "none" }
147-
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
147+
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
148148
>primary : string
149149
>"none" : "none"
150150
>secondary : string
@@ -182,7 +182,7 @@ for (let {
182182
>"secondary" : "secondary"
183183

184184
} = { primary: "none", secondary: "none" }
185-
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
185+
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
186186
>primary : string
187187
>"none" : "none"
188188
>secondary : string
@@ -221,7 +221,7 @@ for (let {
221221
>"secondary" : "secondary"
222222

223223
} = { primary: "none", secondary: "none" }
224-
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
224+
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
225225
>primary : string
226226
>"none" : "none"
227227
>secondary : string
@@ -351,7 +351,7 @@ for (let {
351351
>"secondary" : "secondary"
352352

353353
} = { primary: "none", secondary: "none" }
354-
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
354+
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
355355
>primary : string
356356
>"none" : "none"
357357
>secondary : string
@@ -394,7 +394,7 @@ for (let {
394394
>"secondary" : "secondary"
395395

396396
} = { primary: "none", secondary: "none" }
397-
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
397+
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
398398
>primary : string
399399
>"none" : "none"
400400
>secondary : string
@@ -438,7 +438,7 @@ for (let {
438438
>"secondary" : "secondary"
439439

440440
} = { primary: "none", secondary: "none" }
441-
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
441+
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
442442
>primary : string
443443
>"none" : "none"
444444
>secondary : string

0 commit comments

Comments
 (0)