Skip to content

Commit

Permalink
Add missing mapped type indexed access constraint (#47370)
Browse files Browse the repository at this point in the history
* Type { [P in K]: E }[X] has constraint E with X substutited for P

* Add regression test

* Fix PragmaMap and ReadonlyPragmaMap declarations

* Explore additional constraint

* Revert previous change

* Add tests
  • Loading branch information
ahejlsberg committed Jan 11, 2022
1 parent 852b1c2 commit 4d6dd11
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 7 deletions.
29 changes: 22 additions & 7 deletions src/compiler/checker.ts
Expand Up @@ -11749,6 +11749,11 @@ namespace ts {
}

function getConstraintFromIndexedAccess(type: IndexedAccessType) {
if (isMappedTypeGenericIndexedAccess(type)) {
// For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic,
// we substitute an instantiation of E where P is replaced with X.
return substituteIndexedMappedType(type.objectType as MappedType, type.indexType);
}
const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType);
if (indexConstraint && indexConstraint !== type.indexType) {
const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.accessFlags);
Expand Down Expand Up @@ -11962,6 +11967,11 @@ namespace ts {
return constraint ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType;
}
if (t.flags & TypeFlags.IndexedAccess) {
if (isMappedTypeGenericIndexedAccess(t)) {
// For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic,
// we substitute an instantiation of E where P is replaced with X.
return getBaseConstraint(substituteIndexedMappedType((t as IndexedAccessType).objectType as MappedType, (t as IndexedAccessType).indexType));
}
const baseObjectType = getBaseConstraint((t as IndexedAccessType).objectType);
const baseIndexType = getBaseConstraint((t as IndexedAccessType).indexType);
const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t as IndexedAccessType).accessFlags);
Expand Down Expand Up @@ -12055,13 +12065,7 @@ namespace ts {
* type itself.
*/
function getApparentType(type: Type): Type {
// We obtain the base constraint for all instantiable types, except indexed access types of the form
// { [P in K]: E }[X], where K is non-generic and X is generic. For those types, we instead substitute an
// instantiation of E where P is replaced with X. We do this because getBaseConstraintOfType directly
// lowers to an instantiation where X's constraint is substituted for X, which isn't always desirable.
const t = !(type.flags & TypeFlags.Instantiable) ? type :
isMappedTypeGenericIndexedAccess(type) ? substituteIndexedMappedType((type as IndexedAccessType).objectType as MappedType, (type as IndexedAccessType).indexType) :
getBaseConstraintOfType(type) || unknownType;
const t = !(type.flags & TypeFlags.Instantiable) ? type : getBaseConstraintOfType(type) || unknownType;
return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) :
t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType) :
t.flags & TypeFlags.StringLike ? globalStringType :
Expand Down Expand Up @@ -19220,6 +19224,17 @@ namespace ts {
resetErrorInfo(saveErrorInfo);
return result;
}
if (isMappedTypeGenericIndexedAccess(source)) {
// For an indexed access type { [P in K]: E}[X], above we have already explored an instantiation of E with X
// substituted for P. We also want to explore type { [P in K]: E }[C], where C is the constraint of X.
const indexConstraint = getConstraintOfType((source as IndexedAccessType).indexType);
if (indexConstraint) {
if (result = isRelatedTo(getIndexedAccessType((source as IndexedAccessType).objectType, indexConstraint), target, RecursionFlags.Source, reportErrors)) {
resetErrorInfo(saveErrorInfo);
return result;
}
}
}
}
}
else if (source.flags & TypeFlags.Index) {
Expand Down
50 changes: 50 additions & 0 deletions tests/baselines/reference/correlatedUnions.js
Expand Up @@ -157,6 +157,30 @@ function ff1() {
const x1 = apply('sum', 1, 2)
const x2 = apply('concat', 'str1', 'str2', 'str3' )
}

// Repro from #47368

type ArgMap = { a: number, b: string };
type Func<K extends keyof ArgMap> = (x: ArgMap[K]) => void;
type Funcs = { [K in keyof ArgMap]: Func<K> };

function f1<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
funcs[key](arg);
}

function f2<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
const func = funcs[key]; // Type Funcs[K]
func(arg);
}

function f3<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
const func: Func<K> = funcs[key]; // Error, Funcs[K] not assignable to Func<K>
func(arg);
}

function f4<K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]) {
x = y;
}


//// [correlatedUnions.js]
Expand Down Expand Up @@ -244,6 +268,20 @@ function ff1() {
var x1 = apply('sum', 1, 2);
var x2 = apply('concat', 'str1', 'str2', 'str3');
}
function f1(funcs, key, arg) {
funcs[key](arg);
}
function f2(funcs, key, arg) {
var func = funcs[key]; // Type Funcs[K]
func(arg);
}
function f3(funcs, key, arg) {
var func = funcs[key]; // Error, Funcs[K] not assignable to Func<K>
func(arg);
}
function f4(x, y) {
x = y;
}


//// [correlatedUnions.d.ts]
Expand Down Expand Up @@ -348,3 +386,15 @@ declare const scrollEvent: {
readonly callback: (ev: Event) => void;
};
declare function ff1(): void;
declare type ArgMap = {
a: number;
b: string;
};
declare type Func<K extends keyof ArgMap> = (x: ArgMap[K]) => void;
declare type Funcs = {
[K in keyof ArgMap]: Func<K>;
};
declare function f1<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]): void;
declare function f2<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]): void;
declare function f3<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]): void;
declare function f4<K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]): void;
102 changes: 102 additions & 0 deletions tests/baselines/reference/correlatedUnions.symbols
Expand Up @@ -573,3 +573,105 @@ function ff1() {
>apply : Symbol(apply, Decl(correlatedUnions.ts, 150, 5))
}

// Repro from #47368

type ArgMap = { a: number, b: string };
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>a : Symbol(a, Decl(correlatedUnions.ts, 161, 15))
>b : Symbol(b, Decl(correlatedUnions.ts, 161, 26))

type Func<K extends keyof ArgMap> = (x: ArgMap[K]) => void;
>Func : Symbol(Func, Decl(correlatedUnions.ts, 161, 39))
>K : Symbol(K, Decl(correlatedUnions.ts, 162, 10))
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>x : Symbol(x, Decl(correlatedUnions.ts, 162, 37))
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>K : Symbol(K, Decl(correlatedUnions.ts, 162, 10))

type Funcs = { [K in keyof ArgMap]: Func<K> };
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
>K : Symbol(K, Decl(correlatedUnions.ts, 163, 16))
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>Func : Symbol(Func, Decl(correlatedUnions.ts, 161, 39))
>K : Symbol(K, Decl(correlatedUnions.ts, 163, 16))

function f1<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
>f1 : Symbol(f1, Decl(correlatedUnions.ts, 163, 46))
>K : Symbol(K, Decl(correlatedUnions.ts, 165, 12))
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 165, 36))
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
>key : Symbol(key, Decl(correlatedUnions.ts, 165, 49))
>K : Symbol(K, Decl(correlatedUnions.ts, 165, 12))
>arg : Symbol(arg, Decl(correlatedUnions.ts, 165, 57))
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>K : Symbol(K, Decl(correlatedUnions.ts, 165, 12))

funcs[key](arg);
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 165, 36))
>key : Symbol(key, Decl(correlatedUnions.ts, 165, 49))
>arg : Symbol(arg, Decl(correlatedUnions.ts, 165, 57))
}

function f2<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
>f2 : Symbol(f2, Decl(correlatedUnions.ts, 167, 1))
>K : Symbol(K, Decl(correlatedUnions.ts, 169, 12))
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 169, 36))
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
>key : Symbol(key, Decl(correlatedUnions.ts, 169, 49))
>K : Symbol(K, Decl(correlatedUnions.ts, 169, 12))
>arg : Symbol(arg, Decl(correlatedUnions.ts, 169, 57))
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>K : Symbol(K, Decl(correlatedUnions.ts, 169, 12))

const func = funcs[key]; // Type Funcs[K]
>func : Symbol(func, Decl(correlatedUnions.ts, 170, 9))
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 169, 36))
>key : Symbol(key, Decl(correlatedUnions.ts, 169, 49))

func(arg);
>func : Symbol(func, Decl(correlatedUnions.ts, 170, 9))
>arg : Symbol(arg, Decl(correlatedUnions.ts, 169, 57))
}

function f3<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
>f3 : Symbol(f3, Decl(correlatedUnions.ts, 172, 1))
>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12))
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 174, 36))
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
>key : Symbol(key, Decl(correlatedUnions.ts, 174, 49))
>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12))
>arg : Symbol(arg, Decl(correlatedUnions.ts, 174, 57))
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12))

const func: Func<K> = funcs[key]; // Error, Funcs[K] not assignable to Func<K>
>func : Symbol(func, Decl(correlatedUnions.ts, 175, 9))
>Func : Symbol(Func, Decl(correlatedUnions.ts, 161, 39))
>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12))
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 174, 36))
>key : Symbol(key, Decl(correlatedUnions.ts, 174, 49))

func(arg);
>func : Symbol(func, Decl(correlatedUnions.ts, 175, 9))
>arg : Symbol(arg, Decl(correlatedUnions.ts, 174, 57))
}

function f4<K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]) {
>f4 : Symbol(f4, Decl(correlatedUnions.ts, 177, 1))
>K : Symbol(K, Decl(correlatedUnions.ts, 179, 12))
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>x : Symbol(x, Decl(correlatedUnions.ts, 179, 36))
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
>y : Symbol(y, Decl(correlatedUnions.ts, 179, 59))
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
>K : Symbol(K, Decl(correlatedUnions.ts, 179, 12))

x = y;
>x : Symbol(x, Decl(correlatedUnions.ts, 179, 36))
>y : Symbol(y, Decl(correlatedUnions.ts, 179, 59))
}

75 changes: 75 additions & 0 deletions tests/baselines/reference/correlatedUnions.types
Expand Up @@ -549,3 +549,78 @@ function ff1() {
>'str3' : "str3"
}

// Repro from #47368

type ArgMap = { a: number, b: string };
>ArgMap : ArgMap
>a : number
>b : string

type Func<K extends keyof ArgMap> = (x: ArgMap[K]) => void;
>Func : Func<K>
>x : ArgMap[K]

type Funcs = { [K in keyof ArgMap]: Func<K> };
>Funcs : Funcs

function f1<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
>f1 : <K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) => void
>funcs : Funcs
>key : K
>arg : ArgMap[K]

funcs[key](arg);
>funcs[key](arg) : void
>funcs[key] : Funcs[K]
>funcs : Funcs
>key : K
>arg : ArgMap[K]
}

function f2<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
>f2 : <K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) => void
>funcs : Funcs
>key : K
>arg : ArgMap[K]

const func = funcs[key]; // Type Funcs[K]
>func : Funcs[K]
>funcs[key] : Funcs[K]
>funcs : Funcs
>key : K

func(arg);
>func(arg) : void
>func : Funcs[K]
>arg : ArgMap[K]
}

function f3<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
>f3 : <K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) => void
>funcs : Funcs
>key : K
>arg : ArgMap[K]

const func: Func<K> = funcs[key]; // Error, Funcs[K] not assignable to Func<K>
>func : Func<K>
>funcs[key] : Funcs[K]
>funcs : Funcs
>key : K

func(arg);
>func(arg) : void
>func : Func<K>
>arg : ArgMap[K]
}

function f4<K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]) {
>f4 : <K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]) => void
>x : Func<"b"> | Func<"a">
>y : Funcs[K]

x = y;
>x = y : Funcs[K]
>x : Func<"b"> | Func<"a">
>y : Funcs[K]
}

24 changes: 24 additions & 0 deletions tests/cases/compiler/correlatedUnions.ts
Expand Up @@ -159,3 +159,27 @@ function ff1() {
const x1 = apply('sum', 1, 2)
const x2 = apply('concat', 'str1', 'str2', 'str3' )
}

// Repro from #47368

type ArgMap = { a: number, b: string };
type Func<K extends keyof ArgMap> = (x: ArgMap[K]) => void;
type Funcs = { [K in keyof ArgMap]: Func<K> };

function f1<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
funcs[key](arg);
}

function f2<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
const func = funcs[key]; // Type Funcs[K]
func(arg);
}

function f3<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
const func: Func<K> = funcs[key]; // Error, Funcs[K] not assignable to Func<K>
func(arg);
}

function f4<K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]) {
x = y;
}

0 comments on commit 4d6dd11

Please sign in to comment.