Skip to content
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

Fix2 get constraint of indexed access #17912

Merged
merged 12 commits into from
Jan 10, 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
38 changes: 27 additions & 11 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6351,7 +6351,7 @@ namespace ts {
return getObjectFlags(type) & ObjectFlags.Mapped && !!(<MappedType>type).declaration.questionToken;
}

function isGenericMappedType(type: Type) {
function isGenericMappedType(type: Type): type is MappedType {
return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(<MappedType>type));
}

Expand Down Expand Up @@ -6463,12 +6463,17 @@ namespace ts {
}

function getConstraintOfIndexedAccess(type: IndexedAccessType) {
const transformed = getTransformedIndexedAccessType(type);
const transformed = getSimplifiedIndexedAccessType(type);
if (transformed) {
return transformed;
}
const baseObjectType = getBaseConstraintOfType(type.objectType);
const baseIndexType = getBaseConstraintOfType(type.indexType);
if (baseIndexType === stringType && !getIndexInfoOfType(baseObjectType || type.objectType, IndexKind.String)) {
// getIndexedAccessType returns `any` for X[string] where X doesn't have an index signature.
// to avoid this, return `undefined`.
return undefined;
}
return baseObjectType || baseIndexType ? getIndexedAccessType(baseObjectType || type.objectType, baseIndexType || type.indexType) : undefined;
}

Expand Down Expand Up @@ -6518,8 +6523,9 @@ namespace ts {
function computeBaseConstraint(t: Type): Type {
if (t.flags & TypeFlags.TypeParameter) {
const constraint = getConstraintFromTypeParameter(<TypeParameter>t);
return (<TypeParameter>t).isThisType ? constraint :
constraint ? getBaseConstraint(constraint) : undefined;
return (t as TypeParameter).isThisType || !constraint ?
constraint :
getBaseConstraint(constraint);
}
if (t.flags & TypeFlags.UnionOrIntersection) {
const types = (<UnionOrIntersectionType>t).types;
Expand All @@ -6538,7 +6544,7 @@ namespace ts {
return stringType;
}
if (t.flags & TypeFlags.IndexedAccess) {
const transformed = getTransformedIndexedAccessType(<IndexedAccessType>t);
const transformed = getSimplifiedIndexedAccessType(<IndexedAccessType>t);
if (transformed) {
return getBaseConstraint(transformed);
}
Expand Down Expand Up @@ -8350,7 +8356,7 @@ namespace ts {

// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
// undefined if no transformation is possible.
function getTransformedIndexedAccessType(type: IndexedAccessType): Type {
function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type {
const objectType = type.objectType;
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
// more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a
Expand All @@ -8376,14 +8382,24 @@ namespace ts {
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
// construct the type Box<T[X]>.
if (isGenericMappedType(objectType)) {
const mapper = createTypeMapper([getTypeParameterFromMappedType(<MappedType>objectType)], [type.indexType]);
const objectTypeMapper = (<MappedType>objectType).mapper;
const templateMapper = objectTypeMapper ? combineTypeMappers(objectTypeMapper, mapper) : mapper;
return instantiateType(getTemplateTypeFromMappedType(<MappedType>objectType), templateMapper);
return substituteIndexedMappedType(objectType, type);
}
if (objectType.flags & TypeFlags.TypeParameter) {
const constraint = getConstraintFromTypeParameter(objectType as TypeParameter);
if (constraint && isGenericMappedType(constraint)) {
return substituteIndexedMappedType(constraint, type);
}
}
return undefined;
}

function substituteIndexedMappedType(objectType: MappedType, type: IndexedAccessType) {
const mapper = createTypeMapper([getTypeParameterFromMappedType(<MappedType>objectType)], [type.indexType]);
const objectTypeMapper = (<MappedType>objectType).mapper;
const templateMapper = objectTypeMapper ? combineTypeMappers(objectTypeMapper, mapper) : mapper;
return instantiateType(getTemplateTypeFromMappedType(<MappedType>objectType), templateMapper);
}

function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type {
// If the index type is generic, or if the object type is generic and doesn't originate in an expression,
// we are performing a higher-order index access where we cannot meaningfully access the properties of the
Expand Down Expand Up @@ -10059,7 +10075,7 @@ namespace ts {
}
else if (target.flags & TypeFlags.IndexedAccess) {
// A type S is related to a type T[K] if S is related to A[K], where K is string-like and
// A is the apparent type of S.
// A is the apparent type of T.
const constraint = getConstraintOfIndexedAccess(<IndexedAccessType>target);
if (constraint) {
if (result = isRelatedTo(source, constraint, reportErrors)) {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,7 @@ namespace ts {
export function sum<T extends Record<K, number>, K extends string>(array: ReadonlyArray<T>, prop: K): number {
let result = 0;
for (const v of array) {
// Note: we need the following type assertion because of GH #17069
// TODO: Remove the following type assertion once the fix for #17069 is merged
result += v[prop] as number;
}
return result;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//// [additionOperatorWithConstrainedTypeParameter.ts]
// test for #17069
function sum<T extends Record<K, number>, K extends string>(n: number, v: T, k: K) {
n = n + v[k];
n += v[k]; // += should work the same way
}
function realSum<T extends Record<K, number>, K extends string>(n: number, vs: T[], k: K) {
for (const v of vs) {
n = n + v[k];
n += v[k];
}
}


//// [additionOperatorWithConstrainedTypeParameter.js]
// test for #17069
function sum(n, v, k) {
n = n + v[k];
n += v[k]; // += should work the same way
}
function realSum(n, vs, k) {
for (var _i = 0, vs_1 = vs; _i < vs_1.length; _i++) {
var v = vs_1[_i];
n = n + v[k];
n += v[k];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
=== tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithConstrainedTypeParameter.ts ===
// test for #17069
function sum<T extends Record<K, number>, K extends string>(n: number, v: T, k: K) {
>sum : Symbol(sum, Decl(additionOperatorWithConstrainedTypeParameter.ts, 0, 0))
>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 13))
>Record : Symbol(Record, Decl(lib.d.ts, --, --))
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 41))
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 41))
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60))
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 70))
>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 13))
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 76))
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 41))

n = n + v[k];
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60))
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60))
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 70))
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 76))

n += v[k]; // += should work the same way
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60))
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 70))
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 76))
}
function realSum<T extends Record<K, number>, K extends string>(n: number, vs: T[], k: K) {
>realSum : Symbol(realSum, Decl(additionOperatorWithConstrainedTypeParameter.ts, 4, 1))
>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 17))
>Record : Symbol(Record, Decl(lib.d.ts, --, --))
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 45))
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 45))
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64))
>vs : Symbol(vs, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 74))
>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 17))
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 83))
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 45))

for (const v of vs) {
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 6, 14))
>vs : Symbol(vs, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 74))

n = n + v[k];
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64))
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64))
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 6, 14))
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 83))

n += v[k];
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64))
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 6, 14))
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 83))
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
=== tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithConstrainedTypeParameter.ts ===
// test for #17069
function sum<T extends Record<K, number>, K extends string>(n: number, v: T, k: K) {
>sum : <T extends Record<K, number>, K extends string>(n: number, v: T, k: K) => void
>T : T
>Record : Record<K, T>
>K : K
>K : K
>n : number
>v : T
>T : T
>k : K
>K : K

n = n + v[k];
>n = n + v[k] : number
>n : number
>n + v[k] : number
>n : number
>v[k] : T[K]
>v : T
>k : K

n += v[k]; // += should work the same way
>n += v[k] : number
>n : number
>v[k] : T[K]
>v : T
>k : K
}
function realSum<T extends Record<K, number>, K extends string>(n: number, vs: T[], k: K) {
>realSum : <T extends Record<K, number>, K extends string>(n: number, vs: T[], k: K) => void
>T : T
>Record : Record<K, T>
>K : K
>K : K
>n : number
>vs : T[]
>T : T
>k : K
>K : K

for (const v of vs) {
>v : T
>vs : T[]

n = n + v[k];
>n = n + v[k] : number
>n : number
>n + v[k] : number
>n : number
>v[k] : T[K]
>v : T
>k : K

n += v[k];
>n += v[k] : number
>n : number
>v[k] : T[K]
>v : T
>k : K
}
}

20 changes: 18 additions & 2 deletions tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(76,5): error
Type 'T' is not assignable to type 'T & U'.
Type 'T' is not assignable to type 'U'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(84,9): error TS2322: Type 'keyof T' is not assignable to type 'K'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(85,9): error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'.


==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (25 errors) ====
==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (27 errors) ====
class Shape {
name: string;
width: number;
Expand Down Expand Up @@ -162,4 +164,18 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error
~~
!!! error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'.
k2 = k1;
}
}

// Repro from #17166
function f3<T, K extends keyof T>(obj: T, k: K, value: T[K]): void {
for (let key in obj) {
k = key // error, keyof T =/=> K
~
!!! error TS2322: Type 'keyof T' is not assignable to type 'K'.
value = obj[key]; // error, T[keyof T] =/=> T[K]
~~~~~
!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'.
}
}


19 changes: 18 additions & 1 deletion tests/baselines/reference/keyofAndIndexedAccessErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,17 @@ function f20<T, U>(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) {
o2 = o1; // Error
k1 = k2; // Error
k2 = k1;
}
}

// Repro from #17166
function f3<T, K extends keyof T>(obj: T, k: K, value: T[K]): void {
for (let key in obj) {
k = key // error, keyof T =/=> K
value = obj[key]; // error, T[keyof T] =/=> T[K]
}
}



//// [keyofAndIndexedAccessErrors.js]
var Shape = /** @class */ (function () {
Expand Down Expand Up @@ -109,3 +119,10 @@ function f20(k1, k2, o1, o2) {
k1 = k2; // Error
k2 = k1;
}
// Repro from #17166
function f3(obj, k, value) {
for (var key in obj) {
k = key; // error, keyof T =/=> K
value = obj[key]; // error, T[keyof T] =/=> T[K]
}
}
31 changes: 31 additions & 0 deletions tests/baselines/reference/keyofAndIndexedAccessErrors.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,34 @@ function f20<T, U>(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) {
>k2 : Symbol(k2, Decl(keyofAndIndexedAccessErrors.ts, 69, 37))
>k1 : Symbol(k1, Decl(keyofAndIndexedAccessErrors.ts, 69, 19))
}

// Repro from #17166
function f3<T, K extends keyof T>(obj: T, k: K, value: T[K]): void {
>f3 : Symbol(f3, Decl(keyofAndIndexedAccessErrors.ts, 78, 1))
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12))
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14))
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12))
>obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34))
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12))
>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 81, 41))
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14))
>value : Symbol(value, Decl(keyofAndIndexedAccessErrors.ts, 81, 47))
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12))
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14))

for (let key in obj) {
>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12))
>obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34))

k = key // error, keyof T =/=> K
>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 81, 41))
>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12))

value = obj[key]; // error, T[keyof T] =/=> T[K]
>value : Symbol(value, Decl(keyofAndIndexedAccessErrors.ts, 81, 47))
>obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34))
>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12))
}
}


34 changes: 34 additions & 0 deletions tests/baselines/reference/keyofAndIndexedAccessErrors.types
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,37 @@ function f20<T, U>(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) {
>k2 : keyof (T & U)
>k1 : keyof (T | U)
}

// Repro from #17166
function f3<T, K extends keyof T>(obj: T, k: K, value: T[K]): void {
>f3 : <T, K extends keyof T>(obj: T, k: K, value: T[K]) => void
>T : T
>K : K
>T : T
>obj : T
>T : T
>k : K
>K : K
>value : T[K]
>T : T
>K : K

for (let key in obj) {
>key : keyof T
>obj : T

k = key // error, keyof T =/=> K
>k = key : keyof T
>k : K
>key : keyof T

value = obj[key]; // error, T[keyof T] =/=> T[K]
>value = obj[key] : T[keyof T]
>value : T[K]
>obj[key] : T[keyof T]
>obj : T
>key : keyof T
}
}


Loading