Fix contextual typing for symbol-named properties (#46558)

* Properly handle symbol-named properties in contextual types

* Update index signature in PropertyDescriptorMap

* Add regression tests
This commit is contained in:
Anders Hejlsberg 2021-11-02 14:05:41 -07:00 committed by GitHub
parent 373accf28f
commit 33fe1b6ffc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 232 additions and 5 deletions

View File

@ -26122,12 +26122,12 @@ namespace ts {
return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0);
}
function getTypeOfPropertyOfContextualType(type: Type, name: __String) {
function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) {
return mapType(type, t => {
if (isGenericMappedType(t)) {
const constraint = getConstraintTypeFromMappedType(t);
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
const propertyNameType = getStringLiteralType(unescapeLeadingUnderscores(name));
const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name));
if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
return substituteIndexedMappedType(t, propertyNameType);
}
@ -26143,7 +26143,7 @@ namespace ts {
return restType;
}
}
return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
}
return undefined;
}, /*noReductions*/ true);
@ -26173,7 +26173,8 @@ namespace ts {
// For a (non-symbol) computed property, there is no reason to look up the name
// in the type. It will just be "__computed", which does not appear in any
// SymbolTable.
return getTypeOfPropertyOfContextualType(type, getSymbolOfNode(element).escapedName);
const symbol = getSymbolOfNode(element);
return getTypeOfPropertyOfContextualType(type, symbol.escapedName, getSymbolLinks(symbol).nameType);
}
if (element.name) {
const nameType = getLiteralTypeFromPropertyName(element.name);

2
src/lib/es5.d.ts vendored
View File

@ -96,7 +96,7 @@ interface PropertyDescriptor {
}
interface PropertyDescriptorMap {
[s: string]: PropertyDescriptor;
[key: PropertyKey]: PropertyDescriptor;
}
interface Object {

View File

@ -0,0 +1,53 @@
//// [contextuallyTypedSymbolNamedProperties.ts]
// Repros from #43628
const A = Symbol("A");
const B = Symbol("B");
type Action =
| {type: typeof A, data: string}
| {type: typeof B, data: number}
declare const ab: Action;
declare function f<T extends { type: string | symbol }>(action: T, blah: { [K in T['type']]: (p: K) => void }): any;
f(ab, {
[A]: ap => { ap.description },
[B]: bp => { bp.description },
})
const x: { [sym: symbol]: (p: string) => void } = { [A]: s => s.length };
//// [contextuallyTypedSymbolNamedProperties.js]
"use strict";
// Repros from #43628
const A = Symbol("A");
const B = Symbol("B");
f(ab, {
[A]: ap => { ap.description; },
[B]: bp => { bp.description; },
});
const x = { [A]: s => s.length };
//// [contextuallyTypedSymbolNamedProperties.d.ts]
declare const A: unique symbol;
declare const B: unique symbol;
declare type Action = {
type: typeof A;
data: string;
} | {
type: typeof B;
data: number;
};
declare const ab: Action;
declare function f<T extends {
type: string | symbol;
}>(action: T, blah: {
[K in T['type']]: (p: K) => void;
}): any;
declare const x: {
[sym: symbol]: (p: string) => void;
};

View File

@ -0,0 +1,73 @@
=== tests/cases/compiler/contextuallyTypedSymbolNamedProperties.ts ===
// Repros from #43628
const A = Symbol("A");
>A : Symbol(A, Decl(contextuallyTypedSymbolNamedProperties.ts, 2, 5))
>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, --, --))
const B = Symbol("B");
>B : Symbol(B, Decl(contextuallyTypedSymbolNamedProperties.ts, 3, 5))
>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, --, --))
type Action =
>Action : Symbol(Action, Decl(contextuallyTypedSymbolNamedProperties.ts, 3, 22))
| {type: typeof A, data: string}
>type : Symbol(type, Decl(contextuallyTypedSymbolNamedProperties.ts, 6, 7))
>A : Symbol(A, Decl(contextuallyTypedSymbolNamedProperties.ts, 2, 5))
>data : Symbol(data, Decl(contextuallyTypedSymbolNamedProperties.ts, 6, 22))
| {type: typeof B, data: number}
>type : Symbol(type, Decl(contextuallyTypedSymbolNamedProperties.ts, 7, 7))
>B : Symbol(B, Decl(contextuallyTypedSymbolNamedProperties.ts, 3, 5))
>data : Symbol(data, Decl(contextuallyTypedSymbolNamedProperties.ts, 7, 22))
declare const ab: Action;
>ab : Symbol(ab, Decl(contextuallyTypedSymbolNamedProperties.ts, 9, 13))
>Action : Symbol(Action, Decl(contextuallyTypedSymbolNamedProperties.ts, 3, 22))
declare function f<T extends { type: string | symbol }>(action: T, blah: { [K in T['type']]: (p: K) => void }): any;
>f : Symbol(f, Decl(contextuallyTypedSymbolNamedProperties.ts, 9, 25))
>T : Symbol(T, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 19))
>type : Symbol(type, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 30))
>action : Symbol(action, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 56))
>T : Symbol(T, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 19))
>blah : Symbol(blah, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 66))
>K : Symbol(K, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 76))
>T : Symbol(T, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 19))
>p : Symbol(p, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 94))
>K : Symbol(K, Decl(contextuallyTypedSymbolNamedProperties.ts, 11, 76))
f(ab, {
>f : Symbol(f, Decl(contextuallyTypedSymbolNamedProperties.ts, 9, 25))
>ab : Symbol(ab, Decl(contextuallyTypedSymbolNamedProperties.ts, 9, 13))
[A]: ap => { ap.description },
>[A] : Symbol([A], Decl(contextuallyTypedSymbolNamedProperties.ts, 13, 7))
>A : Symbol(A, Decl(contextuallyTypedSymbolNamedProperties.ts, 2, 5))
>ap : Symbol(ap, Decl(contextuallyTypedSymbolNamedProperties.ts, 14, 8))
>ap.description : Symbol(Symbol.description, Decl(lib.es2019.symbol.d.ts, --, --))
>ap : Symbol(ap, Decl(contextuallyTypedSymbolNamedProperties.ts, 14, 8))
>description : Symbol(Symbol.description, Decl(lib.es2019.symbol.d.ts, --, --))
[B]: bp => { bp.description },
>[B] : Symbol([B], Decl(contextuallyTypedSymbolNamedProperties.ts, 14, 34))
>B : Symbol(B, Decl(contextuallyTypedSymbolNamedProperties.ts, 3, 5))
>bp : Symbol(bp, Decl(contextuallyTypedSymbolNamedProperties.ts, 15, 8))
>bp.description : Symbol(Symbol.description, Decl(lib.es2019.symbol.d.ts, --, --))
>bp : Symbol(bp, Decl(contextuallyTypedSymbolNamedProperties.ts, 15, 8))
>description : Symbol(Symbol.description, Decl(lib.es2019.symbol.d.ts, --, --))
})
const x: { [sym: symbol]: (p: string) => void } = { [A]: s => s.length };
>x : Symbol(x, Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 5))
>sym : Symbol(sym, Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 12))
>p : Symbol(p, Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 27))
>[A] : Symbol([A], Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 51))
>A : Symbol(A, Decl(contextuallyTypedSymbolNamedProperties.ts, 2, 5))
>s : Symbol(s, Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 56))
>s.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
>s : Symbol(s, Decl(contextuallyTypedSymbolNamedProperties.ts, 18, 56))
>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))

View File

@ -0,0 +1,77 @@
=== tests/cases/compiler/contextuallyTypedSymbolNamedProperties.ts ===
// Repros from #43628
const A = Symbol("A");
>A : unique symbol
>Symbol("A") : unique symbol
>Symbol : SymbolConstructor
>"A" : "A"
const B = Symbol("B");
>B : unique symbol
>Symbol("B") : unique symbol
>Symbol : SymbolConstructor
>"B" : "B"
type Action =
>Action : Action
| {type: typeof A, data: string}
>type : unique symbol
>A : unique symbol
>data : string
| {type: typeof B, data: number}
>type : unique symbol
>B : unique symbol
>data : number
declare const ab: Action;
>ab : Action
declare function f<T extends { type: string | symbol }>(action: T, blah: { [K in T['type']]: (p: K) => void }): any;
>f : <T extends { type: string | symbol; }>(action: T, blah: { [K in T["type"]]: (p: K) => void; }) => any
>type : string | symbol
>action : T
>blah : { [K in T["type"]]: (p: K) => void; }
>p : K
f(ab, {
>f(ab, { [A]: ap => { ap.description }, [B]: bp => { bp.description },}) : any
>f : <T extends { type: string | symbol; }>(action: T, blah: { [K in T["type"]]: (p: K) => void; }) => any
>ab : Action
>{ [A]: ap => { ap.description }, [B]: bp => { bp.description },} : { [A]: (ap: unique symbol) => void; [B]: (bp: unique symbol) => void; }
[A]: ap => { ap.description },
>[A] : (ap: unique symbol) => void
>A : unique symbol
>ap => { ap.description } : (ap: unique symbol) => void
>ap : unique symbol
>ap.description : string | undefined
>ap : unique symbol
>description : string | undefined
[B]: bp => { bp.description },
>[B] : (bp: unique symbol) => void
>B : unique symbol
>bp => { bp.description } : (bp: unique symbol) => void
>bp : unique symbol
>bp.description : string | undefined
>bp : unique symbol
>description : string | undefined
})
const x: { [sym: symbol]: (p: string) => void } = { [A]: s => s.length };
>x : { [sym: symbol]: (p: string) => void; }
>sym : symbol
>p : string
>{ [A]: s => s.length } : { [A]: (s: string) => number; }
>[A] : (s: string) => number
>A : unique symbol
>s => s.length : (s: string) => number
>s : string
>s.length : number
>s : string
>length : number

View File

@ -0,0 +1,23 @@
// @strict: true
// @declaration: true
// @target: esnext
// Repros from #43628
const A = Symbol("A");
const B = Symbol("B");
type Action =
| {type: typeof A, data: string}
| {type: typeof B, data: number}
declare const ab: Action;
declare function f<T extends { type: string | symbol }>(action: T, blah: { [K in T['type']]: (p: K) => void }): any;
f(ab, {
[A]: ap => { ap.description },
[B]: bp => { bp.description },
})
const x: { [sym: symbol]: (p: string) => void } = { [A]: s => s.length };