Skip to content

Commit e406505

Browse files
committed
Handle generic mapped types in getTypeOfPropertyOfContextualType.
Fixes #24694.
1 parent 85a3475 commit e406505

5 files changed

+83
-13
lines changed

src/compiler/checker.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9431,19 +9431,19 @@ namespace ts {
94319431
// construct the type Box<T[X]>. We do not further simplify the result because mapped types can be recursive
94329432
// and we might never terminate.
94339433
if (isGenericMappedType(objectType)) {
9434-
return type.simplified = substituteIndexedMappedType(objectType, type);
9434+
return type.simplified = substituteIndexedMappedType(objectType, type.indexType);
94359435
}
94369436
if (objectType.flags & TypeFlags.TypeParameter) {
94379437
const constraint = getConstraintOfTypeParameter(objectType as TypeParameter);
94389438
if (constraint && isGenericMappedType(constraint)) {
9439-
return type.simplified = substituteIndexedMappedType(constraint, type);
9439+
return type.simplified = substituteIndexedMappedType(constraint, type.indexType);
94409440
}
94419441
}
94429442
return type.simplified = type;
94439443
}
94449444

9445-
function substituteIndexedMappedType(objectType: MappedType, type: IndexedAccessType) {
9446-
const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [type.indexType]);
9445+
function substituteIndexedMappedType(objectType: MappedType, indexType: Type) {
9446+
const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [indexType]);
94479447
const templateMapper = combineTypeMappers(objectType.mapper, mapper);
94489448
return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper);
94499449
}
@@ -16651,8 +16651,18 @@ namespace ts {
1665116651
}
1665216652
}
1665316653

16654-
function getTypeOfPropertyOfContextualType(type: Type, name: __String) {
16654+
function getTypeOfPropertyOfContextualType(type: Type, name: __String, node?: Node) {
1665516655
return mapType(type, t => {
16656+
if (isGenericMappedType(t)) {
16657+
const contextualMapper = node ? cloneTypeMapper(getContextualMapper(node)) : identityMapper;
16658+
const instantiatedConstraintType = instantiateType(getConstraintTypeFromMappedType(t), contextualMapper);
16659+
const propertyNameType = getLiteralType(unescapeLeadingUnderscores(name));
16660+
if (isTypeAssignableTo(propertyNameType, instantiatedConstraintType)) {
16661+
return substituteIndexedMappedType(t, propertyNameType);
16662+
}
16663+
// Fall back to looking at the apparent type: needed for
16664+
// tests/cases/compiler/reactDefaultPropsInferenceSuccess.tsx .
16665+
}
1665616666
if (t.flags & TypeFlags.StructuredType) {
1665716667
const prop = getPropertyOfType(t, name);
1665816668
if (prop) {
@@ -16700,7 +16710,7 @@ namespace ts {
1670016710
// in the type. It will just be "__computed", which does not appear in any
1670116711
// SymbolTable.
1670216712
const symbolName = getSymbolOfNode(element).escapedName;
16703-
const propertyType = getTypeOfPropertyOfContextualType(type, symbolName);
16713+
const propertyType = getTypeOfPropertyOfContextualType(type, symbolName, element);
1670416714
if (propertyType) {
1670516715
return propertyType;
1670616716
}
@@ -16717,9 +16727,9 @@ namespace ts {
1671716727
// the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature,
1671816728
// it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated
1671916729
// type of T.
16720-
function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined {
16730+
function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number, node?: Node): Type | undefined {
1672116731
return arrayContextualType && (
16722-
getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String)
16732+
getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String, node)
1672316733
|| getIndexTypeOfContextualType(arrayContextualType, IndexKind.Number)
1672416734
|| getIteratedTypeOrElementType(arrayContextualType, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false, /*checkAssignability*/ false));
1672516735
}
@@ -16734,7 +16744,7 @@ namespace ts {
1673416744
const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName);
1673516745
// JSX expression is in children of JSX Element, we will look for an "children" atttribute (we get the name from JSX.ElementAttributesProperty)
1673616746
const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
16737-
return attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "" ? getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName) : undefined;
16747+
return attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "" ? getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName, node) : undefined;
1673816748
}
1673916749

1674016750
function getContextualTypeForJsxExpression(node: JsxExpression): Type | undefined {
@@ -16755,7 +16765,7 @@ namespace ts {
1675516765
if (!attributesType || isTypeAny(attributesType)) {
1675616766
return undefined;
1675716767
}
16758-
return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText);
16768+
return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText, attribute.parent);
1675916769
}
1676016770
else {
1676116771
return getContextualType(attribute.parent);
@@ -16879,7 +16889,7 @@ namespace ts {
1687916889
case SyntaxKind.ArrayLiteralExpression: {
1688016890
const arrayLiteral = <ArrayLiteralExpression>parent;
1688116891
const type = getApparentTypeOfContextualType(arrayLiteral);
16882-
return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node));
16892+
return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node), node);
1688316893
}
1688416894
case SyntaxKind.ConditionalExpression:
1688516895
return getContextualTypeForConditionalOperand(node);
@@ -17199,7 +17209,7 @@ namespace ts {
1719917209
}
1720017210
}
1720117211
else {
17202-
const elementContextualType = getContextualTypeForElementExpression(contextualType, index);
17212+
const elementContextualType = getContextualTypeForElementExpression(contextualType, index, node);
1720317213
const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple);
1720417214
elementTypes.push(type);
1720517215
}
@@ -17669,7 +17679,7 @@ namespace ts {
1766917679
}
1767017680

1767117681
const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes);
17672-
const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName);
17682+
const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName, openingLikeElement.attributes);
1767317683
// If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process
1767417684
const childrenPropSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, jsxChildrenPropertyName);
1767517685
childrenPropSymbol.type = childrenTypes.length === 1 ?
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//// [contextualPropertyOfGenericMappedType.ts]
2+
// Repro for #24694
3+
4+
declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
5+
f({ data: 0 }, { data(value, key) {} });
6+
7+
8+
//// [contextualPropertyOfGenericMappedType.js]
9+
// Repro for #24694
10+
f({ data: 0 }, { data: function (value, key) { } });
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
=== tests/cases/compiler/contextualPropertyOfGenericMappedType.ts ===
2+
// Repro for #24694
3+
4+
declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
5+
>f : Symbol(f, Decl(contextualPropertyOfGenericMappedType.ts, 0, 0))
6+
>T : Symbol(T, Decl(contextualPropertyOfGenericMappedType.ts, 2, 19))
7+
>data : Symbol(data, Decl(contextualPropertyOfGenericMappedType.ts, 2, 37))
8+
>T : Symbol(T, Decl(contextualPropertyOfGenericMappedType.ts, 2, 19))
9+
>handlers : Symbol(handlers, Decl(contextualPropertyOfGenericMappedType.ts, 2, 45))
10+
>P : Symbol(P, Decl(contextualPropertyOfGenericMappedType.ts, 2, 59))
11+
>T : Symbol(T, Decl(contextualPropertyOfGenericMappedType.ts, 2, 19))
12+
>value : Symbol(value, Decl(contextualPropertyOfGenericMappedType.ts, 2, 75))
13+
>T : Symbol(T, Decl(contextualPropertyOfGenericMappedType.ts, 2, 19))
14+
>P : Symbol(P, Decl(contextualPropertyOfGenericMappedType.ts, 2, 59))
15+
>prop : Symbol(prop, Decl(contextualPropertyOfGenericMappedType.ts, 2, 87))
16+
>P : Symbol(P, Decl(contextualPropertyOfGenericMappedType.ts, 2, 59))
17+
18+
f({ data: 0 }, { data(value, key) {} });
19+
>f : Symbol(f, Decl(contextualPropertyOfGenericMappedType.ts, 0, 0))
20+
>data : Symbol(data, Decl(contextualPropertyOfGenericMappedType.ts, 3, 3))
21+
>data : Symbol(data, Decl(contextualPropertyOfGenericMappedType.ts, 3, 16))
22+
>value : Symbol(value, Decl(contextualPropertyOfGenericMappedType.ts, 3, 22))
23+
>key : Symbol(key, Decl(contextualPropertyOfGenericMappedType.ts, 3, 28))
24+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== tests/cases/compiler/contextualPropertyOfGenericMappedType.ts ===
2+
// Repro for #24694
3+
4+
declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
5+
>f : <T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }) => void
6+
>data : T
7+
>handlers : { [P in keyof T]: (value: T[P], prop: P) => void; }
8+
>value : T[P]
9+
>prop : P
10+
11+
f({ data: 0 }, { data(value, key) {} });
12+
>f({ data: 0 }, { data(value, key) {} }) : void
13+
>f : <T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }) => void
14+
>{ data: 0 } : { data: number; }
15+
>data : number
16+
>0 : 0
17+
>{ data(value, key) {} } : { data(value: number, key: "data"): void; }
18+
>data : (value: number, key: "data") => void
19+
>value : number
20+
>key : "data"
21+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Repro for #24694
2+
// @noImplicitAny: true
3+
4+
declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
5+
f({ data: 0 }, { data(value, key) {} });

0 commit comments

Comments
 (0)