Skip to content

Commit 33fe1b6

Browse files
authored
Fix contextual typing for symbol-named properties (microsoft#46558)
* Properly handle symbol-named properties in contextual types * Update index signature in PropertyDescriptorMap * Add regression tests
1 parent 373accf commit 33fe1b6

File tree

6 files changed

+232
-5
lines changed

6 files changed

+232
-5
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26122,12 +26122,12 @@ namespace ts {
2612226122
return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0);
2612326123
}
2612426124

26125-
function getTypeOfPropertyOfContextualType(type: Type, name: __String) {
26125+
function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) {
2612626126
return mapType(type, t => {
2612726127
if (isGenericMappedType(t)) {
2612826128
const constraint = getConstraintTypeFromMappedType(t);
2612926129
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
26130-
const propertyNameType = getStringLiteralType(unescapeLeadingUnderscores(name));
26130+
const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name));
2613126131
if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
2613226132
return substituteIndexedMappedType(t, propertyNameType);
2613326133
}
@@ -26143,7 +26143,7 @@ namespace ts {
2614326143
return restType;
2614426144
}
2614526145
}
26146-
return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
26146+
return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
2614726147
}
2614826148
return undefined;
2614926149
}, /*noReductions*/ true);
@@ -26173,7 +26173,8 @@ namespace ts {
2617326173
// For a (non-symbol) computed property, there is no reason to look up the name
2617426174
// in the type. It will just be "__computed", which does not appear in any
2617526175
// SymbolTable.
26176-
return getTypeOfPropertyOfContextualType(type, getSymbolOfNode(element).escapedName);
26176+
const symbol = getSymbolOfNode(element);
26177+
return getTypeOfPropertyOfContextualType(type, symbol.escapedName, getSymbolLinks(symbol).nameType);
2617726178
}
2617826179
if (element.name) {
2617926180
const nameType = getLiteralTypeFromPropertyName(element.name);

src/lib/es5.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ interface PropertyDescriptor {
9696
}
9797

9898
interface PropertyDescriptorMap {
99-
[s: string]: PropertyDescriptor;
99+
[key: PropertyKey]: PropertyDescriptor;
100100
}
101101

102102
interface Object {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//// [contextuallyTypedSymbolNamedProperties.ts]
2+
// Repros from #43628
3+
4+
const A = Symbol("A");
5+
const B = Symbol("B");
6+
7+
type Action =
8+
| {type: typeof A, data: string}
9+
| {type: typeof B, data: number}
10+
11+
declare const ab: Action;
12+
13+
declare function f<T extends { type: string | symbol }>(action: T, blah: { [K in T['type']]: (p: K) => void }): any;
14+
15+
f(ab, {
16+
[A]: ap => { ap.description },
17+
[B]: bp => { bp.description },
18+
})
19+
20+
const x: { [sym: symbol]: (p: string) => void } = { [A]: s => s.length };
21+
22+
23+
//// [contextuallyTypedSymbolNamedProperties.js]
24+
"use strict";
25+
// Repros from #43628
26+
const A = Symbol("A");
27+
const B = Symbol("B");
28+
f(ab, {
29+
[A]: ap => { ap.description; },
30+
[B]: bp => { bp.description; },
31+
});
32+
const x = { [A]: s => s.length };
33+
34+
35+
//// [contextuallyTypedSymbolNamedProperties.d.ts]
36+
declare const A: unique symbol;
37+
declare const B: unique symbol;
38+
declare type Action = {
39+
type: typeof A;
40+
data: string;
41+
} | {
42+
type: typeof B;
43+
data: number;
44+
};
45+
declare const ab: Action;
46+
declare function f<T extends {
47+
type: string | symbol;
48+
}>(action: T, blah: {
49+
[K in T['type']]: (p: K) => void;
50+
}): any;
51+
declare const x: {
52+
[sym: symbol]: (p: string) => void;
53+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
=== tests/cases/compiler/contextuallyTypedSymbolNamedProperties.ts ===
2+
// Repros from #43628
3+
4+
const A = Symbol("A");
5+
>A : Symbol(A, Decl(contextuallyTypedSymbolNamedProperties.ts, 2, 5))
6+
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --))
7+
8+
const B = Symbol("B");
9+
>B : Symbol(B, Decl(contextuallyTypedSymbolNamedProperties.ts, 3, 5))
10+
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --))
11+
12+
type Action =
13+
>Action : Symbol(Action, Decl(contextuallyTypedSymbolNamedProperties.ts, 3, 22))
14+
15+
| {type: typeof A, data: string}
16+
>type : Symbol(type, Decl(contextuallyTypedSymbolNamedProperties.ts, 6, 7))
17+
>A : Symbol(A, Decl(contextuallyTypedSymbolNamedProperties.ts, 2, 5))
18+
>data : Symbol(data, Decl(contextuallyTypedSymbolNamedProperties.ts, 6, 22))
19+
20+
| {type: typeof B, data: number}
21+
>type : Symbol(type, Decl(contextuallyTypedSymbolNamedProperties.ts, 7, 7))
22+
>B : Symbol(B, Decl(contextuallyTypedSymbolNamedProperties.ts, 3, 5))
23+
>data : Symbol(data, Decl(contextuallyTypedSymbolNamedProperties.ts, 7, 22))
24+
25+
declare const ab: Action;
26+
>ab : Symbol(ab, Decl(contextuallyTypedSymbolNamedProperties.ts, 9, 13))
27+
>Action : Symbol(Action, Decl(contextuallyTypedSymbolNamedProperties.ts, 3, 22))
28+
29+
declare function f<T extends { type: string | symbol }>(action: T, blah: { [K in T['type']]: (p: K) => void }): any;
30+
>f : Symbol(f, Decl(contextuallyTypedSymbolNamedProperties.ts, 9, 25))
31+
>T : Symbol(T, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 19))
32+
>type : Symbol(type, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 30))
33+
>action : Symbol(action, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 56))
34+
>T : Symbol(T, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 19))
35+
>blah : Symbol(blah, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 66))
36+
>K : Symbol(K, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 76))
37+
>T : Symbol(T, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 19))
38+
>p : Symbol(p, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 94))
39+
>K : Symbol(K, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 76))
40+
41+
f(ab, {
42+
>f : Symbol(f, Decl(contextuallyTypedSymbolNamedProperties.ts, 9, 25))
43+
>ab : Symbol(ab, Decl(contextuallyTypedSymbolNamedProperties.ts, 9, 13))
44+
45+
[A]: ap => { ap.description },
46+
>[A] : Symbol([A], Decl(contextuallyTypedSymbolNamedProperties.ts, 13, 7))
47+
>A : Symbol(A, Decl(contextuallyTypedSymbolNamedProperties.ts, 2, 5))
48+
>ap : Symbol(ap, Decl(contextuallyTypedSymbolNamedProperties.ts, 14, 8))
49+
>ap.description : Symbol(Symbol.description, Decl(lib.es2019.symbol.d.ts, --, --))
50+
>ap : Symbol(ap, Decl(contextuallyTypedSymbolNamedProperties.ts, 14, 8))
51+
>description : Symbol(Symbol.description, Decl(lib.es2019.symbol.d.ts, --, --))
52+
53+
[B]: bp => { bp.description },
54+
>[B] : Symbol([B], Decl(contextuallyTypedSymbolNamedProperties.ts, 14, 34))
55+
>B : Symbol(B, Decl(contextuallyTypedSymbolNamedProperties.ts, 3, 5))
56+
>bp : Symbol(bp, Decl(contextuallyTypedSymbolNamedProperties.ts, 15, 8))
57+
>bp.description : Symbol(Symbol.description, Decl(lib.es2019.symbol.d.ts, --, --))
58+
>bp : Symbol(bp, Decl(contextuallyTypedSymbolNamedProperties.ts, 15, 8))
59+
>description : Symbol(Symbol.description, Decl(lib.es2019.symbol.d.ts, --, --))
60+
61+
})
62+
63+
const x: { [sym: symbol]: (p: string) => void } = { [A]: s => s.length };
64+
>x : Symbol(x, Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 5))
65+
>sym : Symbol(sym, Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 12))
66+
>p : Symbol(p, Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 27))
67+
>[A] : Symbol([A], Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 51))
68+
>A : Symbol(A, Decl(contextuallyTypedSymbolNamedProperties.ts, 2, 5))
69+
>s : Symbol(s, Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 56))
70+
>s.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
71+
>s : Symbol(s, Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 56))
72+
>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
73+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
=== tests/cases/compiler/contextuallyTypedSymbolNamedProperties.ts ===
2+
// Repros from #43628
3+
4+
const A = Symbol("A");
5+
>A : unique symbol
6+
>Symbol("A") : unique symbol
7+
>Symbol : SymbolConstructor
8+
>"A" : "A"
9+
10+
const B = Symbol("B");
11+
>B : unique symbol
12+
>Symbol("B") : unique symbol
13+
>Symbol : SymbolConstructor
14+
>"B" : "B"
15+
16+
type Action =
17+
>Action : Action
18+
19+
| {type: typeof A, data: string}
20+
>type : unique symbol
21+
>A : unique symbol
22+
>data : string
23+
24+
| {type: typeof B, data: number}
25+
>type : unique symbol
26+
>B : unique symbol
27+
>data : number
28+
29+
declare const ab: Action;
30+
>ab : Action
31+
32+
declare function f<T extends { type: string | symbol }>(action: T, blah: { [K in T['type']]: (p: K) => void }): any;
33+
>f : <T extends { type: string | symbol; }>(action: T, blah: { [K in T["type"]]: (p: K) => void; }) => any
34+
>type : string | symbol
35+
>action : T
36+
>blah : { [K in T["type"]]: (p: K) => void; }
37+
>p : K
38+
39+
f(ab, {
40+
>f(ab, { [A]: ap => { ap.description }, [B]: bp => { bp.description },}) : any
41+
>f : <T extends { type: string | symbol; }>(action: T, blah: { [K in T["type"]]: (p: K) => void; }) => any
42+
>ab : Action
43+
>{ [A]: ap => { ap.description }, [B]: bp => { bp.description },} : { [A]: (ap: unique symbol) => void; [B]: (bp: unique symbol) => void; }
44+
45+
[A]: ap => { ap.description },
46+
>[A] : (ap: unique symbol) => void
47+
>A : unique symbol
48+
>ap => { ap.description } : (ap: unique symbol) => void
49+
>ap : unique symbol
50+
>ap.description : string | undefined
51+
>ap : unique symbol
52+
>description : string | undefined
53+
54+
[B]: bp => { bp.description },
55+
>[B] : (bp: unique symbol) => void
56+
>B : unique symbol
57+
>bp => { bp.description } : (bp: unique symbol) => void
58+
>bp : unique symbol
59+
>bp.description : string | undefined
60+
>bp : unique symbol
61+
>description : string | undefined
62+
63+
})
64+
65+
const x: { [sym: symbol]: (p: string) => void } = { [A]: s => s.length };
66+
>x : { [sym: symbol]: (p: string) => void; }
67+
>sym : symbol
68+
>p : string
69+
>{ [A]: s => s.length } : { [A]: (s: string) => number; }
70+
>[A] : (s: string) => number
71+
>A : unique symbol
72+
>s => s.length : (s: string) => number
73+
>s : string
74+
>s.length : number
75+
>s : string
76+
>length : number
77+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// @strict: true
2+
// @declaration: true
3+
// @target: esnext
4+
5+
// Repros from #43628
6+
7+
const A = Symbol("A");
8+
const B = Symbol("B");
9+
10+
type Action =
11+
| {type: typeof A, data: string}
12+
| {type: typeof B, data: number}
13+
14+
declare const ab: Action;
15+
16+
declare function f<T extends { type: string | symbol }>(action: T, blah: { [K in T['type']]: (p: K) => void }): any;
17+
18+
f(ab, {
19+
[A]: ap => { ap.description },
20+
[B]: bp => { bp.description },
21+
})
22+
23+
const x: { [sym: symbol]: (p: string) => void } = { [A]: s => s.length };

0 commit comments

Comments
 (0)