Skip to content

Commit 04da707

Browse files
authored
Merge pull request #13419 from Microsoft/fixKeyofWithIntersectionConstraint
Fix keyof with union or intersection constraint
2 parents 5e6c5ef + 5abd323 commit 04da707

File tree

6 files changed

+143
-68
lines changed

6 files changed

+143
-68
lines changed

src/compiler/checker.ts

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4753,15 +4753,15 @@ namespace ts {
47534753
getPropertiesOfObjectType(type);
47544754
}
47554755

4756-
function getConstraintOfTypeVariable(type: TypeVariable): Type {
4757-
return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(<TypeParameter>type) : getBaseConstraintOfTypeVariable(type);
4756+
function getConstraintOfType(type: TypeVariable | UnionOrIntersectionType): Type {
4757+
return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(<TypeParameter>type) : getBaseConstraintOfType(type);
47584758
}
47594759

47604760
function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type {
47614761
return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined;
47624762
}
47634763

4764-
function getBaseConstraintOfTypeVariable(type: TypeVariable): Type {
4764+
function getBaseConstraintOfType(type: TypeVariable | UnionOrIntersectionType): Type {
47654765
const constraint = getResolvedBaseConstraint(type);
47664766
return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined;
47674767
}
@@ -4775,15 +4775,15 @@ namespace ts {
47754775
* type variable has no constraint, and the circularConstraintType singleton is returned if the constraint
47764776
* circularly references the type variable.
47774777
*/
4778-
function getResolvedBaseConstraint(type: TypeVariable): Type {
4778+
function getResolvedBaseConstraint(type: TypeVariable | UnionOrIntersectionType): Type {
47794779
let typeStack: Type[];
47804780
let circular: boolean;
4781-
if (!type.resolvedApparentType) {
4781+
if (!type.resolvedBaseConstraint) {
47824782
typeStack = [];
47834783
const constraint = getBaseConstraint(type);
4784-
type.resolvedApparentType = circular ? circularConstraintType : getTypeWithThisArgument(constraint || noConstraintType, type);
4784+
type.resolvedBaseConstraint = circular ? circularConstraintType : getTypeWithThisArgument(constraint || noConstraintType, type);
47854785
}
4786-
return type.resolvedApparentType;
4786+
return type.resolvedBaseConstraint;
47874787

47884788
function getBaseConstraint(t: Type): Type {
47894789
if (contains(typeStack, t)) {
@@ -4834,7 +4834,7 @@ namespace ts {
48344834
* type itself. Note that the apparent type of a union type is the union type itself.
48354835
*/
48364836
function getApparentType(type: Type): Type {
4837-
const t = type.flags & TypeFlags.TypeVariable ? getBaseConstraintOfTypeVariable(<TypeVariable>type) || emptyObjectType : type;
4837+
const t = type.flags & TypeFlags.TypeVariable ? getBaseConstraintOfType(<TypeVariable>type) || emptyObjectType : type;
48384838
return t.flags & TypeFlags.StringLike ? globalStringType :
48394839
t.flags & TypeFlags.NumberLike ? globalNumberType :
48404840
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
@@ -7409,16 +7409,6 @@ namespace ts {
74097409
}
74107410
}
74117411
}
7412-
else {
7413-
// Given a type parameter K with a constraint keyof T, a type S is
7414-
// assignable to K if S is assignable to keyof T.
7415-
const constraint = getConstraintOfTypeParameter(<TypeParameter>target);
7416-
if (constraint && constraint.flags & TypeFlags.Index) {
7417-
if (result = isRelatedTo(source, constraint, reportErrors)) {
7418-
return result;
7419-
}
7420-
}
7421-
}
74227412
}
74237413
else if (target.flags & TypeFlags.Index) {
74247414
// A keyof S is related to a keyof T if T is related to S.
@@ -7427,14 +7417,12 @@ namespace ts {
74277417
return result;
74287418
}
74297419
}
7430-
// Given a type variable T with a constraint C, a type S is assignable to
7431-
// keyof T if S is assignable to keyof C.
7432-
if ((<IndexType>target).type.flags & TypeFlags.TypeVariable) {
7433-
const constraint = getConstraintOfTypeVariable(<TypeVariable>(<IndexType>target).type);
7434-
if (constraint) {
7435-
if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) {
7436-
return result;
7437-
}
7420+
// A type S is assignable to keyof T if S is assignable to keyof C, where C is the
7421+
// constraint of T.
7422+
const constraint = getConstraintOfType((<IndexType>target).type);
7423+
if (constraint) {
7424+
if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) {
7425+
return result;
74387426
}
74397427
}
74407428
}
@@ -7448,7 +7436,7 @@ namespace ts {
74487436
}
74497437
// A type S is related to a type T[K] if S is related to A[K], where K is string-like and
74507438
// A is the apparent type of S.
7451-
const constraint = getBaseConstraintOfTypeVariable(<IndexedAccessType>target);
7439+
const constraint = getBaseConstraintOfType(<IndexedAccessType>target);
74527440
if (constraint) {
74537441
if (result = isRelatedTo(source, constraint, reportErrors)) {
74547442
errorInfo = saveErrorInfo;
@@ -7488,7 +7476,7 @@ namespace ts {
74887476
else if (source.flags & TypeFlags.IndexedAccess) {
74897477
// A type S[K] is related to a type T if A[K] is related to T, where K is string-like and
74907478
// A is the apparent type of S.
7491-
const constraint = getBaseConstraintOfTypeVariable(<IndexedAccessType>source);
7479+
const constraint = getBaseConstraintOfType(<IndexedAccessType>source);
74927480
if (constraint) {
74937481
if (result = isRelatedTo(constraint, target, reportErrors)) {
74947482
errorInfo = saveErrorInfo;
@@ -15207,7 +15195,7 @@ namespace ts {
1520715195
function isLiteralContextualType(contextualType: Type) {
1520815196
if (contextualType) {
1520915197
if (contextualType.flags & TypeFlags.TypeVariable) {
15210-
const constraint = getBaseConstraintOfTypeVariable(<TypeVariable>contextualType) || emptyObjectType;
15198+
const constraint = getBaseConstraintOfType(<TypeVariable>contextualType) || emptyObjectType;
1521115199
// If the type parameter is constrained to the base primitive type we're checking for,
1521215200
// consider this a literal context. For example, given a type parameter 'T extends string',
1521315201
// this causes us to infer string literal types for T.

src/compiler/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2923,6 +2923,8 @@ namespace ts {
29232923
/* @internal */
29242924
resolvedIndexType: IndexType;
29252925
/* @internal */
2926+
resolvedBaseConstraint: Type;
2927+
/* @internal */
29262928
couldContainTypeVariables: boolean;
29272929
}
29282930

@@ -2982,7 +2984,7 @@ namespace ts {
29822984

29832985
export interface TypeVariable extends Type {
29842986
/* @internal */
2985-
resolvedApparentType: Type;
2987+
resolvedBaseConstraint: Type;
29862988
/* @internal */
29872989
resolvedIndexType: IndexType;
29882990
}

tests/baselines/reference/keyofAndIndexedAccess.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,16 @@ function addToMyThingy<S extends KeyTypes>(key: S) {
464464
MyThingy[key].push("a");
465465
}
466466

467+
// Repro from #13102
468+
469+
type Handler<T> = {
470+
onChange: (name: keyof T) => void;
471+
};
472+
473+
function onChangeGenericFunction<T>(handler: Handler<T & {preset: number}>) {
474+
handler.onChange('preset')
475+
}
476+
467477
// Repro from #13285
468478

469479
function updateIds<T extends Record<K, string>, K extends string>(
@@ -801,6 +811,9 @@ var MyThingy;
801811
function addToMyThingy(key) {
802812
MyThingy[key].push("a");
803813
}
814+
function onChangeGenericFunction(handler) {
815+
handler.onChange('preset');
816+
}
804817
// Repro from #13285
805818
function updateIds(obj, idFields, idMapping) {
806819
for (var _i = 0, idFields_1 = idFields; _i < idFields_1.length; _i++) {
@@ -1034,6 +1047,12 @@ declare let MyThingy: {
10341047
[key in KeyTypes]: string[];
10351048
};
10361049
declare function addToMyThingy<S extends KeyTypes>(key: S): void;
1050+
declare type Handler<T> = {
1051+
onChange: (name: keyof T) => void;
1052+
};
1053+
declare function onChangeGenericFunction<T>(handler: Handler<T & {
1054+
preset: number;
1055+
}>): void;
10371056
declare function updateIds<T extends Record<K, string>, K extends string>(obj: T, idFields: K[], idMapping: {
10381057
[oldId: string]: string;
10391058
}): Record<K, string>;

tests/baselines/reference/keyofAndIndexedAccess.symbols

Lines changed: 65 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,83 +1699,110 @@ function addToMyThingy<S extends KeyTypes>(key: S) {
16991699
>push : Symbol(Array.push, Decl(lib.d.ts, --, --))
17001700
}
17011701

1702+
// Repro from #13102
1703+
1704+
type Handler<T> = {
1705+
>Handler : Symbol(Handler, Decl(keyofAndIndexedAccess.ts, 463, 1))
1706+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 467, 13))
1707+
1708+
onChange: (name: keyof T) => void;
1709+
>onChange : Symbol(onChange, Decl(keyofAndIndexedAccess.ts, 467, 19))
1710+
>name : Symbol(name, Decl(keyofAndIndexedAccess.ts, 468, 15))
1711+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 467, 13))
1712+
1713+
};
1714+
1715+
function onChangeGenericFunction<T>(handler: Handler<T & {preset: number}>) {
1716+
>onChangeGenericFunction : Symbol(onChangeGenericFunction, Decl(keyofAndIndexedAccess.ts, 469, 2))
1717+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 471, 33))
1718+
>handler : Symbol(handler, Decl(keyofAndIndexedAccess.ts, 471, 36))
1719+
>Handler : Symbol(Handler, Decl(keyofAndIndexedAccess.ts, 463, 1))
1720+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 471, 33))
1721+
>preset : Symbol(preset, Decl(keyofAndIndexedAccess.ts, 471, 58))
1722+
1723+
handler.onChange('preset')
1724+
>handler.onChange : Symbol(onChange, Decl(keyofAndIndexedAccess.ts, 467, 19))
1725+
>handler : Symbol(handler, Decl(keyofAndIndexedAccess.ts, 471, 36))
1726+
>onChange : Symbol(onChange, Decl(keyofAndIndexedAccess.ts, 467, 19))
1727+
}
1728+
17021729
// Repro from #13285
17031730

17041731
function updateIds<T extends Record<K, string>, K extends string>(
1705-
>updateIds : Symbol(updateIds, Decl(keyofAndIndexedAccess.ts, 463, 1))
1706-
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 467, 19))
1732+
>updateIds : Symbol(updateIds, Decl(keyofAndIndexedAccess.ts, 473, 1))
1733+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 477, 19))
17071734
>Record : Symbol(Record, Decl(lib.d.ts, --, --))
1708-
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 467, 47))
1709-
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 467, 47))
1735+
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 477, 47))
1736+
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 477, 47))
17101737

17111738
obj: T,
1712-
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 467, 66))
1713-
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 467, 19))
1739+
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 477, 66))
1740+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 477, 19))
17141741

17151742
idFields: K[],
1716-
>idFields : Symbol(idFields, Decl(keyofAndIndexedAccess.ts, 468, 11))
1717-
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 467, 47))
1743+
>idFields : Symbol(idFields, Decl(keyofAndIndexedAccess.ts, 478, 11))
1744+
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 477, 47))
17181745

17191746
idMapping: { [oldId: string]: string }
1720-
>idMapping : Symbol(idMapping, Decl(keyofAndIndexedAccess.ts, 469, 18))
1721-
>oldId : Symbol(oldId, Decl(keyofAndIndexedAccess.ts, 470, 18))
1747+
>idMapping : Symbol(idMapping, Decl(keyofAndIndexedAccess.ts, 479, 18))
1748+
>oldId : Symbol(oldId, Decl(keyofAndIndexedAccess.ts, 480, 18))
17221749

17231750
): Record<K, string> {
17241751
>Record : Symbol(Record, Decl(lib.d.ts, --, --))
1725-
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 467, 47))
1752+
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 477, 47))
17261753

17271754
for (const idField of idFields) {
1728-
>idField : Symbol(idField, Decl(keyofAndIndexedAccess.ts, 472, 14))
1729-
>idFields : Symbol(idFields, Decl(keyofAndIndexedAccess.ts, 468, 11))
1755+
>idField : Symbol(idField, Decl(keyofAndIndexedAccess.ts, 482, 14))
1756+
>idFields : Symbol(idFields, Decl(keyofAndIndexedAccess.ts, 478, 11))
17301757

17311758
const newId = idMapping[obj[idField]];
1732-
>newId : Symbol(newId, Decl(keyofAndIndexedAccess.ts, 473, 13))
1733-
>idMapping : Symbol(idMapping, Decl(keyofAndIndexedAccess.ts, 469, 18))
1734-
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 467, 66))
1735-
>idField : Symbol(idField, Decl(keyofAndIndexedAccess.ts, 472, 14))
1759+
>newId : Symbol(newId, Decl(keyofAndIndexedAccess.ts, 483, 13))
1760+
>idMapping : Symbol(idMapping, Decl(keyofAndIndexedAccess.ts, 479, 18))
1761+
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 477, 66))
1762+
>idField : Symbol(idField, Decl(keyofAndIndexedAccess.ts, 482, 14))
17361763

17371764
if (newId) {
1738-
>newId : Symbol(newId, Decl(keyofAndIndexedAccess.ts, 473, 13))
1765+
>newId : Symbol(newId, Decl(keyofAndIndexedAccess.ts, 483, 13))
17391766

17401767
obj[idField] = newId;
1741-
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 467, 66))
1742-
>idField : Symbol(idField, Decl(keyofAndIndexedAccess.ts, 472, 14))
1743-
>newId : Symbol(newId, Decl(keyofAndIndexedAccess.ts, 473, 13))
1768+
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 477, 66))
1769+
>idField : Symbol(idField, Decl(keyofAndIndexedAccess.ts, 482, 14))
1770+
>newId : Symbol(newId, Decl(keyofAndIndexedAccess.ts, 483, 13))
17441771
}
17451772
}
17461773
return obj;
1747-
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 467, 66))
1774+
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 477, 66))
17481775
}
17491776

17501777
// Repro from #13285
17511778

17521779
function updateIds2<T extends { [x: string]: string }, K extends keyof T>(
1753-
>updateIds2 : Symbol(updateIds2, Decl(keyofAndIndexedAccess.ts, 479, 1))
1754-
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 483, 20))
1755-
>x : Symbol(x, Decl(keyofAndIndexedAccess.ts, 483, 33))
1756-
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 483, 54))
1757-
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 483, 20))
1780+
>updateIds2 : Symbol(updateIds2, Decl(keyofAndIndexedAccess.ts, 489, 1))
1781+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 493, 20))
1782+
>x : Symbol(x, Decl(keyofAndIndexedAccess.ts, 493, 33))
1783+
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 493, 54))
1784+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 493, 20))
17581785

17591786
obj: T,
1760-
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 483, 74))
1761-
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 483, 20))
1787+
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 493, 74))
1788+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 493, 20))
17621789

17631790
key: K,
1764-
>key : Symbol(key, Decl(keyofAndIndexedAccess.ts, 484, 11))
1765-
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 483, 54))
1791+
>key : Symbol(key, Decl(keyofAndIndexedAccess.ts, 494, 11))
1792+
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 493, 54))
17661793

17671794
stringMap: { [oldId: string]: string }
1768-
>stringMap : Symbol(stringMap, Decl(keyofAndIndexedAccess.ts, 485, 11))
1769-
>oldId : Symbol(oldId, Decl(keyofAndIndexedAccess.ts, 486, 18))
1795+
>stringMap : Symbol(stringMap, Decl(keyofAndIndexedAccess.ts, 495, 11))
1796+
>oldId : Symbol(oldId, Decl(keyofAndIndexedAccess.ts, 496, 18))
17701797

17711798
) {
17721799
var x = obj[key];
1773-
>x : Symbol(x, Decl(keyofAndIndexedAccess.ts, 488, 7))
1774-
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 483, 74))
1775-
>key : Symbol(key, Decl(keyofAndIndexedAccess.ts, 484, 11))
1800+
>x : Symbol(x, Decl(keyofAndIndexedAccess.ts, 498, 7))
1801+
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 493, 74))
1802+
>key : Symbol(key, Decl(keyofAndIndexedAccess.ts, 494, 11))
17761803

17771804
stringMap[x]; // Should be OK.
1778-
>stringMap : Symbol(stringMap, Decl(keyofAndIndexedAccess.ts, 485, 11))
1779-
>x : Symbol(x, Decl(keyofAndIndexedAccess.ts, 488, 7))
1805+
>stringMap : Symbol(stringMap, Decl(keyofAndIndexedAccess.ts, 495, 11))
1806+
>x : Symbol(x, Decl(keyofAndIndexedAccess.ts, 498, 7))
17801807
}
17811808

tests/baselines/reference/keyofAndIndexedAccess.types

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,6 +2013,35 @@ function addToMyThingy<S extends KeyTypes>(key: S) {
20132013
>"a" : "a"
20142014
}
20152015

2016+
// Repro from #13102
2017+
2018+
type Handler<T> = {
2019+
>Handler : Handler<T>
2020+
>T : T
2021+
2022+
onChange: (name: keyof T) => void;
2023+
>onChange : (name: keyof T) => void
2024+
>name : keyof T
2025+
>T : T
2026+
2027+
};
2028+
2029+
function onChangeGenericFunction<T>(handler: Handler<T & {preset: number}>) {
2030+
>onChangeGenericFunction : <T>(handler: Handler<T & { preset: number; }>) => void
2031+
>T : T
2032+
>handler : Handler<T & { preset: number; }>
2033+
>Handler : Handler<T>
2034+
>T : T
2035+
>preset : number
2036+
2037+
handler.onChange('preset')
2038+
>handler.onChange('preset') : void
2039+
>handler.onChange : (name: keyof (T & { preset: number; })) => void
2040+
>handler : Handler<T & { preset: number; }>
2041+
>onChange : (name: keyof (T & { preset: number; })) => void
2042+
>'preset' : "preset"
2043+
}
2044+
20162045
// Repro from #13285
20172046

20182047
function updateIds<T extends Record<K, string>, K extends string>(

tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,16 @@ function addToMyThingy<S extends KeyTypes>(key: S) {
465465
MyThingy[key].push("a");
466466
}
467467

468+
// Repro from #13102
469+
470+
type Handler<T> = {
471+
onChange: (name: keyof T) => void;
472+
};
473+
474+
function onChangeGenericFunction<T>(handler: Handler<T & {preset: number}>) {
475+
handler.onChange('preset')
476+
}
477+
468478
// Repro from #13285
469479

470480
function updateIds<T extends Record<K, string>, K extends string>(

0 commit comments

Comments
 (0)