Skip to content

Improve indexed access type relations #26698

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 3 commits into from
Aug 28, 2018
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: 16 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6899,10 +6899,15 @@ namespace ts {
}

function getConstraintOfIndexedAccess(type: IndexedAccessType) {
const objectType = getBaseConstraintOfType(type.objectType) || type.objectType;
const indexType = getBaseConstraintOfType(type.indexType) || type.indexType;
const constraint = !isGenericObjectType(objectType) && !isGenericIndexType(indexType) ? getIndexedAccessType(objectType, indexType, /*accessNode*/ undefined, errorType) : undefined;
return constraint && constraint !== errorType ? constraint : undefined;
const objectType = getConstraintOfType(type.objectType) || type.objectType;
if (objectType !== type.objectType) {
const constraint = getIndexedAccessType(objectType, type.indexType, /*accessNode*/ undefined, errorType);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this still result in a type we may need to get the constraint of if the constraint of tyoe.objectType is a generic mapped type (since an index on a mapped type is trivially bounded by the mapped type's template)? Or is getConstraintOfType not the variant that can do that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the constraint of type.objectType is a mapped type, we do indeed fetch that here and form a new indexed access. If type.objectType itself is a mapped type, the getConstraintOfType function does nothing, and we'll fall into the base constraint code below.

if (constraint && constraint !== errorType) {
return constraint;
}
}
const baseConstraint = getBaseConstraintOfType(type);
return baseConstraint && baseConstraint !== type ? baseConstraint : undefined;
}

function getDefaultConstraintOfConditionalType(type: ConditionalType) {
Expand Down Expand Up @@ -7074,9 +7079,6 @@ namespace ts {
if (t.flags & TypeFlags.Substitution) {
return getBaseConstraint((<SubstitutionType>t).substitute);
}
if (isGenericMappedType(t)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes the constraint of a generic mapped type itself, right? I guess that makes sense since indexed accesses work that way already.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. I'm not sure why this code was here, but it definitely isn't right. Removing it had no effect on baselines (including RWC baselines), but it fixes the second issue I found while exploring #26409.

return emptyObjectType;
}
return t;
}
}
Expand Down Expand Up @@ -11642,12 +11644,13 @@ namespace ts {
}
}
else if (target.flags & TypeFlags.IndexedAccess) {
// A type S is related to a type T[K] if S is related to C, where C is the
// constraint of T[K]
const constraint = getConstraintForRelation(target);
if (constraint) {
if (result = isRelatedTo(source, constraint, reportErrors)) {
return result;
// A type S is related to a type T[K] if S is related to C, where C is the base constraint of T[K]
if (relation !== identityRelation) {
const constraint = getBaseConstraintOfType(target);
if (constraint && constraint !== target) {
if (result = isRelatedTo(source, constraint, reportErrors)) {
return result;
}
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions tests/baselines/reference/keyofAndIndexedAccess.js
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,20 @@ function ff2<V extends string, T extends string>(dd: DictDict<V, T>, k1: V, k2:
const d: Dict<T> = dd[k1];
return d[k2];
}

// Repro from #26409

const cf1 = <T extends { [P in K]: string; } & { cool: string; }, K extends keyof T>(t: T, k: K) =>
{
const s: string = t[k];
t.cool;
};

const cf2 = <T extends { [P in K | "cool"]: string; }, K extends keyof T>(t: T, k: K) =>
{
const s: string = t[k];
t.cool;
};


//// [keyofAndIndexedAccess.js]
Expand Down Expand Up @@ -1078,6 +1092,15 @@ function ff2(dd, k1, k2) {
var d = dd[k1];
return d[k2];
}
// Repro from #26409
var cf1 = function (t, k) {
var s = t[k];
t.cool;
};
var cf2 = function (t, k) {
var s = t[k];
t.cool;
};


//// [keyofAndIndexedAccess.d.ts]
Expand Down Expand Up @@ -1413,3 +1436,7 @@ declare type DictDict<V extends string, T extends string> = {
};
declare function ff1<V extends string, T extends string>(dd: DictDict<V, T>, k1: V, k2: T): number;
declare function ff2<V extends string, T extends string>(dd: DictDict<V, T>, k1: V, k2: T): number;
declare const cf1: <T extends { [P in K]: string; } & {
cool: string;
}, K extends keyof T>(t: T, k: K) => void;
declare const cf2: <T extends { [P in K | "cool"]: string; }, K extends keyof T>(t: T, k: K) => void;
51 changes: 51 additions & 0 deletions tests/baselines/reference/keyofAndIndexedAccess.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -2319,3 +2319,54 @@ function ff2<V extends string, T extends string>(dd: DictDict<V, T>, k1: V, k2:
>k2 : Symbol(k2, Decl(keyofAndIndexedAccess.ts, 646, 75))
}

// Repro from #26409

const cf1 = <T extends { [P in K]: string; } & { cool: string; }, K extends keyof T>(t: T, k: K) =>
>cf1 : Symbol(cf1, Decl(keyofAndIndexedAccess.ts, 653, 5))
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 653, 13))
>P : Symbol(P, Decl(keyofAndIndexedAccess.ts, 653, 26))
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 653, 65))
>cool : Symbol(cool, Decl(keyofAndIndexedAccess.ts, 653, 48))
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 653, 65))
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 653, 13))
>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 653, 85))
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 653, 13))
>k : Symbol(k, Decl(keyofAndIndexedAccess.ts, 653, 90))
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 653, 65))
{
const s: string = t[k];
>s : Symbol(s, Decl(keyofAndIndexedAccess.ts, 655, 9))
>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 653, 85))
>k : Symbol(k, Decl(keyofAndIndexedAccess.ts, 653, 90))

t.cool;
>t.cool : Symbol(cool, Decl(keyofAndIndexedAccess.ts, 653, 48))
>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 653, 85))
>cool : Symbol(cool, Decl(keyofAndIndexedAccess.ts, 653, 48))

};

const cf2 = <T extends { [P in K | "cool"]: string; }, K extends keyof T>(t: T, k: K) =>
>cf2 : Symbol(cf2, Decl(keyofAndIndexedAccess.ts, 659, 5))
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 659, 13))
>P : Symbol(P, Decl(keyofAndIndexedAccess.ts, 659, 26))
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 659, 54))
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 659, 54))
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 659, 13))
>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 659, 74))
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 659, 13))
>k : Symbol(k, Decl(keyofAndIndexedAccess.ts, 659, 79))
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 659, 54))
{
const s: string = t[k];
>s : Symbol(s, Decl(keyofAndIndexedAccess.ts, 661, 9))
>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 659, 74))
>k : Symbol(k, Decl(keyofAndIndexedAccess.ts, 659, 79))

t.cool;
>t.cool : Symbol(cool)
>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 659, 74))
>cool : Symbol(cool)

};

41 changes: 41 additions & 0 deletions tests/baselines/reference/keyofAndIndexedAccess.types
Original file line number Diff line number Diff line change
Expand Up @@ -2184,3 +2184,44 @@ function ff2<V extends string, T extends string>(dd: DictDict<V, T>, k1: V, k2:
>k2 : T
}

// Repro from #26409

const cf1 = <T extends { [P in K]: string; } & { cool: string; }, K extends keyof T>(t: T, k: K) =>
>cf1 : <T extends { [P in K]: string; } & { cool: string; }, K extends keyof T>(t: T, k: K) => void
><T extends { [P in K]: string; } & { cool: string; }, K extends keyof T>(t: T, k: K) =>{ const s: string = t[k]; t.cool;} : <T extends { [P in K]: string; } & { cool: string; }, K extends keyof T>(t: T, k: K) => void
>cool : string
>t : T
>k : K
{
const s: string = t[k];
>s : string
>t[k] : T[K]
>t : T
>k : K

t.cool;
>t.cool : string
>t : T
>cool : string

};

const cf2 = <T extends { [P in K | "cool"]: string; }, K extends keyof T>(t: T, k: K) =>
>cf2 : <T extends { [P in K | "cool"]: string; }, K extends keyof T>(t: T, k: K) => void
><T extends { [P in K | "cool"]: string; }, K extends keyof T>(t: T, k: K) =>{ const s: string = t[k]; t.cool;} : <T extends { [P in K | "cool"]: string; }, K extends keyof T>(t: T, k: K) => void
>t : T
>k : K
{
const s: string = t[k];
>s : string
>t[k] : T[K]
>t : T
>k : K

t.cool;
>t.cool : string
>t : T
>cool : string

};

14 changes: 14 additions & 0 deletions tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,3 +651,17 @@ function ff2<V extends string, T extends string>(dd: DictDict<V, T>, k1: V, k2:
const d: Dict<T> = dd[k1];
return d[k2];
}

// Repro from #26409

const cf1 = <T extends { [P in K]: string; } & { cool: string; }, K extends keyof T>(t: T, k: K) =>
{
const s: string = t[k];
t.cool;
};

const cf2 = <T extends { [P in K | "cool"]: string; }, K extends keyof T>(t: T, k: K) =>
{
const s: string = t[k];
t.cool;
};