Skip to content

Add missing mapped type indexed access constraint #47370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11754,6 +11754,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 @@ -11967,6 +11972,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 @@ -12060,13 +12070,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 @@ -19225,6 +19229,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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;
}