From 260708d07ebf58218f5020e9452740c00ae90dbc Mon Sep 17 00:00:00 2001 From: Michael Arnaldi Date: Sat, 26 Mar 2022 00:41:59 +0000 Subject: [PATCH 1/9] bugfix: homomorphic mapped types when T is non-generic, solves 27995 --- src/compiler/checker.ts | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6c956a9037887..85bb467fd5ce2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15848,6 +15848,19 @@ namespace ts { // Eagerly resolve the constraint type which forces an error if the constraint type circularly // references itself through one or more type aliases. getConstraintTypeFromMappedType(type); + // Detect if the mapped type should be homomorphic to a tuple by checking the declaration of the constraint if it contains a keyof over a tuple + if (!node.nameType && node.typeParameter.constraint && isTypeOperatorNode(node.typeParameter.constraint) && node.typeParameter.constraint.operator === SyntaxKind.KeyOfKeyword) { + const keyOfTarget = getTypeFromTypeNode(node.typeParameter.constraint.type); + if (isTupleType(keyOfTarget)) { + // Instantiate the mapped type over a tuple with an identity mapper + const instantiatedTupleMappedType = instantiateMappedTupleType( + keyOfTarget, + type, + makeFunctionTypeMapper(identity) + ); + links.resolvedType = instantiatedTupleMappedType; + } + } } return links.resolvedType; } @@ -35579,14 +35592,17 @@ namespace ts { reportImplicitAny(node, anyType); } - const type = getTypeFromMappedTypeNode(node) as MappedType; - const nameType = getNameTypeFromMappedType(type); - if (nameType) { - checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType); - } - else { - const constraintType = getConstraintTypeFromMappedType(type); - checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); + const type = getTypeFromMappedTypeNode(node); + // Continue to check if the type returned is a mapped type, that means it wasn't resolved to a homomorphic tuple type + if (type.flags & TypeFlags.Object && (type as ObjectType).objectFlags & ObjectFlags.Mapped) { + const nameType = getNameTypeFromMappedType(type as MappedType); + if (nameType) { + checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType); + } + else { + const constraintType = getConstraintTypeFromMappedType(type as MappedType); + checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); + } } } From 3b70763a8a02b8c2a57b35c9c5f5756b20a450c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 26 Mar 2022 09:59:38 +0100 Subject: [PATCH 2/9] Add tests for concrete mapped tuples --- ...edTypeConcreteTupleHomomorphism.errors.txt | 41 ++++++++++++ .../mappedTypeConcreteTupleHomomorphism.js | 33 ++++++++++ ...appedTypeConcreteTupleHomomorphism.symbols | 64 +++++++++++++++++++ .../mappedTypeConcreteTupleHomomorphism.types | 50 +++++++++++++++ .../mappedTypeConcreteTupleHomomorphism.ts | 27 ++++++++ 5 files changed, 215 insertions(+) create mode 100644 tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt create mode 100644 tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js create mode 100644 tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols create mode 100644 tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types create mode 100644 tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt new file mode 100644 index 0000000000000..96be0c104e935 --- /dev/null +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt @@ -0,0 +1,41 @@ +tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts(4,24): error TS2322: Type 'A[K]' is not assignable to type 'string | number | bigint | boolean'. + Type '2 | 1 | (() => string) | (() => string) | (() => 2 | 1) | ((...items: (2 | 1)[]) => number) | { (...items: ConcatArray<2 | 1>[]): (2 | 1)[]; (...items: (2 | 1 | ConcatArray<2 | 1>)[]): (2 | 1)[]; } | ((separator?: string) => string) | (() => (2 | 1)[]) | (() => 2 | 1) | ((start?: number, end?: number) => (2 | 1)[]) | ((compareFn?: (a: 2 | 1, b: 2 | 1) => number) => A) | { (start: number, deleteCount?: number): (2 | 1)[]; (start: number, deleteCount: number, ...items: (2 | 1)[]): (2 | 1)[]; } | ((...items: (2 | 1)[]) => number) | ((searchElement: 2 | 1, fromIndex?: number) => number) | ((searchElement: 2 | 1, fromIndex?: number) => number) | { (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any): boolean; } | ((predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any) => boolean) | ((callbackfn: (value: 2 | 1, index: number, array: (2 | 1)[]) => void, thisArg?: any) => void) | ((callbackfn: (value: 2 | 1, index: number, array: (2 | 1)[]) => U, thisArg?: any) => U[]) | { (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => value is S, thisArg?: any): S[]; (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any): (2 | 1)[]; } | { (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1): 2 | 1; (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1, initialValue: 2 | 1): 2 | 1; (callbackfn: (previousValue: U, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => U, initialValue: U): U; } | { (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1): 2 | 1; (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1, initialValue: 2 | 1): 2 | 1; (callbackfn: (previousValue: U, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => U, initialValue: U): U; }' is not assignable to type 'string | number | bigint | boolean'. + Type '() => string' is not assignable to type 'string | number | bigint | boolean'. +tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts(27,32): error TS2536: Type 'Foo[K]' cannot be used to index type 'Bar'. + + +==== tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts (2 errors) ==== + type A = [1, 2] + + type B = { + [K in keyof A]: `${A[K]}` + ~~~~ +!!! error TS2322: Type 'A[K]' is not assignable to type 'string | number | bigint | boolean'. +!!! error TS2322: Type '2 | 1 | (() => string) | (() => string) | (() => 2 | 1) | ((...items: (2 | 1)[]) => number) | { (...items: ConcatArray<2 | 1>[]): (2 | 1)[]; (...items: (2 | 1 | ConcatArray<2 | 1>)[]): (2 | 1)[]; } | ((separator?: string) => string) | (() => (2 | 1)[]) | (() => 2 | 1) | ((start?: number, end?: number) => (2 | 1)[]) | ((compareFn?: (a: 2 | 1, b: 2 | 1) => number) => A) | { (start: number, deleteCount?: number): (2 | 1)[]; (start: number, deleteCount: number, ...items: (2 | 1)[]): (2 | 1)[]; } | ((...items: (2 | 1)[]) => number) | ((searchElement: 2 | 1, fromIndex?: number) => number) | ((searchElement: 2 | 1, fromIndex?: number) => number) | { (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any): boolean; } | ((predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any) => boolean) | ((callbackfn: (value: 2 | 1, index: number, array: (2 | 1)[]) => void, thisArg?: any) => void) | ((callbackfn: (value: 2 | 1, index: number, array: (2 | 1)[]) => U, thisArg?: any) => U[]) | { (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => value is S, thisArg?: any): S[]; (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any): (2 | 1)[]; } | { (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1): 2 | 1; (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1, initialValue: 2 | 1): 2 | 1; (callbackfn: (previousValue: U, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => U, initialValue: U): U; } | { (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1): 2 | 1; (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1, initialValue: 2 | 1): 2 | 1; (callbackfn: (previousValue: U, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => U, initialValue: U): U; }' is not assignable to type 'string | number | bigint | boolean'. +!!! error TS2322: Type '() => string' is not assignable to type 'string | number | bigint | boolean'. + } + + const b: B = ['1', '2'] + + type C = { + [K in keyof T]: [K, T[K]] + } + + type D = { + [K in keyof C<['c', 'd', 'e']>]: 1 + } + + const d: D = [1, 1, 1] + + // repro from #27995 + type Foo = ['a', 'b']; + + interface Bar { + a: string; + b: number; + } + + type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; + ~~~~~~~~~~~ +!!! error TS2536: Type 'Foo[K]' cannot be used to index type 'Bar'. + \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js new file mode 100644 index 0000000000000..1cfcd88c0f8d8 --- /dev/null +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js @@ -0,0 +1,33 @@ +//// [mappedTypeConcreteTupleHomomorphism.ts] +type A = [1, 2] + +type B = { + [K in keyof A]: `${A[K]}` +} + +const b: B = ['1', '2'] + +type C = { + [K in keyof T]: [K, T[K]] +} + +type D = { + [K in keyof C<['c', 'd', 'e']>]: 1 +} + +const d: D = [1, 1, 1] + +// repro from #27995 +type Foo = ['a', 'b']; + +interface Bar { + a: string; + b: number; +} + +type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; + + +//// [mappedTypeConcreteTupleHomomorphism.js] +var b = ['1', '2']; +var d = [1, 1, 1]; diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols new file mode 100644 index 0000000000000..62cbb1e4183ff --- /dev/null +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols @@ -0,0 +1,64 @@ +=== tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts === +type A = [1, 2] +>A : Symbol(A, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) + +type B = { +>B : Symbol(B, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 15)) + + [K in keyof A]: `${A[K]}` +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 3, 5)) +>A : Symbol(A, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) +>A : Symbol(A, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 3, 5)) +} + +const b: B = ['1', '2'] +>b : Symbol(b, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 5)) +>B : Symbol(B, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 15)) + +type C = { +>C : Symbol(C, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 23)) +>T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 7)) + + [K in keyof T]: [K, T[K]] +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 9, 5)) +>T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 7)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 9, 5)) +>T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 7)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 9, 5)) +} + +type D = { +>D : Symbol(D, Decl(mappedTypeConcreteTupleHomomorphism.ts, 10, 1)) + + [K in keyof C<['c', 'd', 'e']>]: 1 +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 13, 5)) +>C : Symbol(C, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 23)) +} + +const d: D = [1, 1, 1] +>d : Symbol(d, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 5)) +>D : Symbol(D, Decl(mappedTypeConcreteTupleHomomorphism.ts, 10, 1)) + +// repro from #27995 +type Foo = ['a', 'b']; +>Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 22)) + +interface Bar { +>Bar : Symbol(Bar, Decl(mappedTypeConcreteTupleHomomorphism.ts, 19, 22)) + + a: string; +>a : Symbol(Bar.a, Decl(mappedTypeConcreteTupleHomomorphism.ts, 21, 15)) + + b: number; +>b : Symbol(Bar.b, Decl(mappedTypeConcreteTupleHomomorphism.ts, 22, 14)) +} + +type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; +>Baz : Symbol(Baz, Decl(mappedTypeConcreteTupleHomomorphism.ts, 24, 1)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 26, 14)) +>Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 22)) +>Bar : Symbol(Bar, Decl(mappedTypeConcreteTupleHomomorphism.ts, 19, 22)) +>Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 22)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 26, 14)) + diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types new file mode 100644 index 0000000000000..7337503a1d8e4 --- /dev/null +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types @@ -0,0 +1,50 @@ +=== tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts === +type A = [1, 2] +>A : A + +type B = { +>B : ["1", "2"] + + [K in keyof A]: `${A[K]}` +} + +const b: B = ['1', '2'] +>b : ["1", "2"] +>['1', '2'] : ["1", "2"] +>'1' : "1" +>'2' : "2" + +type C = { +>C : C + + [K in keyof T]: [K, T[K]] +} + +type D = { +>D : [1, 1, 1] + + [K in keyof C<['c', 'd', 'e']>]: 1 +} + +const d: D = [1, 1, 1] +>d : [1, 1, 1] +>[1, 1, 1] : [1, 1, 1] +>1 : 1 +>1 : 1 +>1 : 1 + +// repro from #27995 +type Foo = ['a', 'b']; +>Foo : Foo + +interface Bar { + a: string; +>a : string + + b: number; +>b : number +} + +type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; +>Baz : [string, number] + diff --git a/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts b/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts new file mode 100644 index 0000000000000..d6d780ad45ae3 --- /dev/null +++ b/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts @@ -0,0 +1,27 @@ +type A = [1, 2] + +type B = { + [K in keyof A]: `${A[K]}` +} + +const b: B = ['1', '2'] + +type C = { + [K in keyof T]: [K, T[K]] +} + +type D = { + [K in keyof C<['c', 'd', 'e']>]: 1 +} + +const d: D = [1, 1, 1] + +// repro from #27995 +type Foo = ['a', 'b']; + +interface Bar { + a: string; + b: number; +} + +type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; From 3e203b12a96e6fda96714cc278cabfc0819fa537 Mon Sep 17 00:00:00 2001 From: Michael Arnaldi Date: Sat, 26 Mar 2022 19:07:21 +0000 Subject: [PATCH 3/9] fix: improve parameter constraint in case it appears in a homomorphic mapped tuple type --- src/compiler/checker.ts | 11 +++ ...edTypeConcreteTupleHomomorphism.errors.txt | 43 ++++++----- .../mappedTypeConcreteTupleHomomorphism.js | 24 +++--- ...appedTypeConcreteTupleHomomorphism.symbols | 75 +++++++++++-------- .../mappedTypeConcreteTupleHomomorphism.types | 35 +++++---- .../mappedTypeConcreteTupleHomomorphism.ts | 22 ++++-- 6 files changed, 129 insertions(+), 81 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 85bb467fd5ce2..6542f6e01278a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13353,6 +13353,17 @@ namespace ts { typeParameter.constraint = getInferredTypeParameterConstraint(typeParameter) || noConstraintType; } else { + // Detect is the constraint is for a homomorphic mapped type to a tuple and in case return a literal union of the used tuple keys + if (constraintDeclaration.parent && constraintDeclaration.parent.parent && constraintDeclaration.parent.parent.kind === SyntaxKind.MappedType) { + const mappedTypeNode = constraintDeclaration.parent.parent as MappedTypeNode; + if (!mappedTypeNode.nameType && mappedTypeNode.typeParameter.constraint && isTypeOperatorNode(mappedTypeNode.typeParameter.constraint) && mappedTypeNode.typeParameter.constraint.operator === SyntaxKind.KeyOfKeyword) { + const keyOfTarget = getTypeFromTypeNode(mappedTypeNode.typeParameter.constraint.type); + if (isTupleType(keyOfTarget)) { + typeParameter.constraint = getUnionType(map(getTypeArguments(keyOfTarget), (_, i) => getStringLiteralType("" + i))); + return typeParameter.constraint; + } + } + } let type = getTypeFromTypeNode(constraintDeclaration); if (type.flags & TypeFlags.Any && !isErrorType(type)) { // Allow errorType to propegate to keep downstream errors suppressed // use keyofConstraintType as the base constraint for mapped type key constraints (unknown isn;t assignable to that, but `any` was), diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt index 96be0c104e935..7e31d9ef9e72f 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt @@ -1,31 +1,36 @@ -tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts(4,24): error TS2322: Type 'A[K]' is not assignable to type 'string | number | bigint | boolean'. - Type '2 | 1 | (() => string) | (() => string) | (() => 2 | 1) | ((...items: (2 | 1)[]) => number) | { (...items: ConcatArray<2 | 1>[]): (2 | 1)[]; (...items: (2 | 1 | ConcatArray<2 | 1>)[]): (2 | 1)[]; } | ((separator?: string) => string) | (() => (2 | 1)[]) | (() => 2 | 1) | ((start?: number, end?: number) => (2 | 1)[]) | ((compareFn?: (a: 2 | 1, b: 2 | 1) => number) => A) | { (start: number, deleteCount?: number): (2 | 1)[]; (start: number, deleteCount: number, ...items: (2 | 1)[]): (2 | 1)[]; } | ((...items: (2 | 1)[]) => number) | ((searchElement: 2 | 1, fromIndex?: number) => number) | ((searchElement: 2 | 1, fromIndex?: number) => number) | { (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any): boolean; } | ((predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any) => boolean) | ((callbackfn: (value: 2 | 1, index: number, array: (2 | 1)[]) => void, thisArg?: any) => void) | ((callbackfn: (value: 2 | 1, index: number, array: (2 | 1)[]) => U, thisArg?: any) => U[]) | { (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => value is S, thisArg?: any): S[]; (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any): (2 | 1)[]; } | { (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1): 2 | 1; (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1, initialValue: 2 | 1): 2 | 1; (callbackfn: (previousValue: U, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => U, initialValue: U): U; } | { (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1): 2 | 1; (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1, initialValue: 2 | 1): 2 | 1; (callbackfn: (previousValue: U, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => U, initialValue: U): U; }' is not assignable to type 'string | number | bigint | boolean'. - Type '() => string' is not assignable to type 'string | number | bigint | boolean'. -tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts(27,32): error TS2536: Type 'Foo[K]' cannot be used to index type 'Bar'. +tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts(22,47): error TS2322: Type 'TupleOfNumbersAndObjects[K]' is not assignable to type 'string | number | bigint | boolean'. + Type '{} | 2 | 1' is not assignable to type 'string | number | bigint | boolean'. + Type '{}' is not assignable to type 'string | number | bigint | boolean'. -==== tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts (2 errors) ==== - type A = [1, 2] - - type B = { - [K in keyof A]: `${A[K]}` - ~~~~ -!!! error TS2322: Type 'A[K]' is not assignable to type 'string | number | bigint | boolean'. -!!! error TS2322: Type '2 | 1 | (() => string) | (() => string) | (() => 2 | 1) | ((...items: (2 | 1)[]) => number) | { (...items: ConcatArray<2 | 1>[]): (2 | 1)[]; (...items: (2 | 1 | ConcatArray<2 | 1>)[]): (2 | 1)[]; } | ((separator?: string) => string) | (() => (2 | 1)[]) | (() => 2 | 1) | ((start?: number, end?: number) => (2 | 1)[]) | ((compareFn?: (a: 2 | 1, b: 2 | 1) => number) => A) | { (start: number, deleteCount?: number): (2 | 1)[]; (start: number, deleteCount: number, ...items: (2 | 1)[]): (2 | 1)[]; } | ((...items: (2 | 1)[]) => number) | ((searchElement: 2 | 1, fromIndex?: number) => number) | ((searchElement: 2 | 1, fromIndex?: number) => number) | { (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any): boolean; } | ((predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any) => boolean) | ((callbackfn: (value: 2 | 1, index: number, array: (2 | 1)[]) => void, thisArg?: any) => void) | ((callbackfn: (value: 2 | 1, index: number, array: (2 | 1)[]) => U, thisArg?: any) => U[]) | { (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => value is S, thisArg?: any): S[]; (predicate: (value: 2 | 1, index: number, array: (2 | 1)[]) => unknown, thisArg?: any): (2 | 1)[]; } | { (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1): 2 | 1; (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1, initialValue: 2 | 1): 2 | 1; (callbackfn: (previousValue: U, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => U, initialValue: U): U; } | { (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1): 2 | 1; (callbackfn: (previousValue: 2 | 1, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => 2 | 1, initialValue: 2 | 1): 2 | 1; (callbackfn: (previousValue: U, currentValue: 2 | 1, currentIndex: number, array: (2 | 1)[]) => U, initialValue: U): U; }' is not assignable to type 'string | number | bigint | boolean'. -!!! error TS2322: Type '() => string' is not assignable to type 'string | number | bigint | boolean'. +==== tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts (1 errors) ==== + type TupleOfNumbers = [1, 2] + + type HomomorphicType = { + [K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` } - const b: B = ['1', '2'] + const homomorphic: HomomorphicType = ['1', '2'] - type C = { + type GenericType = { [K in keyof T]: [K, T[K]] } - type D = { - [K in keyof C<['c', 'd', 'e']>]: 1 + type HomomorphicInstantiation = { + [K in keyof GenericType<['c', 'd', 'e']>]: 1 } - const d: D = [1, 1, 1] + const d: HomomorphicInstantiation = [1, 1, 1] + + type TupleOfNumbersAndObjects = [1, 2, {}] + + type ShoulsErrorInAssignement = { + [K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type 'TupleOfNumbersAndObjects[K]' is not assignable to type 'string | number | bigint | boolean'. +!!! error TS2322: Type '{} | 2 | 1' is not assignable to type 'string | number | bigint | boolean'. +!!! error TS2322: Type '{}' is not assignable to type 'string | number | bigint | boolean'. + } // repro from #27995 type Foo = ['a', 'b']; @@ -36,6 +41,4 @@ tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts(27,32): error TS2536 } type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; - ~~~~~~~~~~~ -!!! error TS2536: Type 'Foo[K]' cannot be used to index type 'Bar'. \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js index 1cfcd88c0f8d8..ec02eb6691b02 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js @@ -1,21 +1,27 @@ //// [mappedTypeConcreteTupleHomomorphism.ts] -type A = [1, 2] +type TupleOfNumbers = [1, 2] -type B = { - [K in keyof A]: `${A[K]}` +type HomomorphicType = { + [K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` } -const b: B = ['1', '2'] +const homomorphic: HomomorphicType = ['1', '2'] -type C = { +type GenericType = { [K in keyof T]: [K, T[K]] } -type D = { - [K in keyof C<['c', 'd', 'e']>]: 1 +type HomomorphicInstantiation = { + [K in keyof GenericType<['c', 'd', 'e']>]: 1 } -const d: D = [1, 1, 1] +const d: HomomorphicInstantiation = [1, 1, 1] + +type TupleOfNumbersAndObjects = [1, 2, {}] + +type ShoulsErrorInAssignement = { + [K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` +} // repro from #27995 type Foo = ['a', 'b']; @@ -29,5 +35,5 @@ type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; //// [mappedTypeConcreteTupleHomomorphism.js] -var b = ['1', '2']; +var homomorphic = ['1', '2']; var d = [1, 1, 1]; diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols index 62cbb1e4183ff..4193a36916007 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols @@ -1,64 +1,77 @@ === tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts === -type A = [1, 2] ->A : Symbol(A, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) +type TupleOfNumbers = [1, 2] +>TupleOfNumbers : Symbol(TupleOfNumbers, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) -type B = { ->B : Symbol(B, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 15)) +type HomomorphicType = { +>HomomorphicType : Symbol(HomomorphicType, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 28)) - [K in keyof A]: `${A[K]}` + [K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` >K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 3, 5)) ->A : Symbol(A, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) ->A : Symbol(A, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) +>TupleOfNumbers : Symbol(TupleOfNumbers, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) +>TupleOfNumbers : Symbol(TupleOfNumbers, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) >K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 3, 5)) } -const b: B = ['1', '2'] ->b : Symbol(b, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 5)) ->B : Symbol(B, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 15)) +const homomorphic: HomomorphicType = ['1', '2'] +>homomorphic : Symbol(homomorphic, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 5)) +>HomomorphicType : Symbol(HomomorphicType, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 28)) -type C = { ->C : Symbol(C, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 23)) ->T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 7)) +type GenericType = { +>GenericType : Symbol(GenericType, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 47)) +>T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 17)) [K in keyof T]: [K, T[K]] >K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 9, 5)) ->T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 7)) +>T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 17)) >K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 9, 5)) ->T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 7)) +>T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 17)) >K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 9, 5)) } -type D = { ->D : Symbol(D, Decl(mappedTypeConcreteTupleHomomorphism.ts, 10, 1)) +type HomomorphicInstantiation = { +>HomomorphicInstantiation : Symbol(HomomorphicInstantiation, Decl(mappedTypeConcreteTupleHomomorphism.ts, 10, 1)) - [K in keyof C<['c', 'd', 'e']>]: 1 + [K in keyof GenericType<['c', 'd', 'e']>]: 1 >K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 13, 5)) ->C : Symbol(C, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 23)) +>GenericType : Symbol(GenericType, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 47)) } -const d: D = [1, 1, 1] +const d: HomomorphicInstantiation = [1, 1, 1] >d : Symbol(d, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 5)) ->D : Symbol(D, Decl(mappedTypeConcreteTupleHomomorphism.ts, 10, 1)) +>HomomorphicInstantiation : Symbol(HomomorphicInstantiation, Decl(mappedTypeConcreteTupleHomomorphism.ts, 10, 1)) + +type TupleOfNumbersAndObjects = [1, 2, {}] +>TupleOfNumbersAndObjects : Symbol(TupleOfNumbersAndObjects, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 45)) + +type ShoulsErrorInAssignement = { +>ShoulsErrorInAssignement : Symbol(ShoulsErrorInAssignement, Decl(mappedTypeConcreteTupleHomomorphism.ts, 18, 42)) + + [K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 21, 5)) +>TupleOfNumbersAndObjects : Symbol(TupleOfNumbersAndObjects, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 45)) +>TupleOfNumbersAndObjects : Symbol(TupleOfNumbersAndObjects, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 45)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 21, 5)) +} // repro from #27995 type Foo = ['a', 'b']; ->Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 22)) +>Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 22, 1)) interface Bar { ->Bar : Symbol(Bar, Decl(mappedTypeConcreteTupleHomomorphism.ts, 19, 22)) +>Bar : Symbol(Bar, Decl(mappedTypeConcreteTupleHomomorphism.ts, 25, 22)) a: string; ->a : Symbol(Bar.a, Decl(mappedTypeConcreteTupleHomomorphism.ts, 21, 15)) +>a : Symbol(Bar.a, Decl(mappedTypeConcreteTupleHomomorphism.ts, 27, 15)) b: number; ->b : Symbol(Bar.b, Decl(mappedTypeConcreteTupleHomomorphism.ts, 22, 14)) +>b : Symbol(Bar.b, Decl(mappedTypeConcreteTupleHomomorphism.ts, 28, 14)) } type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; ->Baz : Symbol(Baz, Decl(mappedTypeConcreteTupleHomomorphism.ts, 24, 1)) ->K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 26, 14)) ->Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 22)) ->Bar : Symbol(Bar, Decl(mappedTypeConcreteTupleHomomorphism.ts, 19, 22)) ->Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 22)) ->K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 26, 14)) +>Baz : Symbol(Baz, Decl(mappedTypeConcreteTupleHomomorphism.ts, 30, 1)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 32, 14)) +>Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 22, 1)) +>Bar : Symbol(Bar, Decl(mappedTypeConcreteTupleHomomorphism.ts, 25, 22)) +>Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 22, 1)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 32, 14)) diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types index 7337503a1d8e4..e1f3542d0ae8c 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types @@ -1,38 +1,47 @@ === tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts === -type A = [1, 2] ->A : A +type TupleOfNumbers = [1, 2] +>TupleOfNumbers : TupleOfNumbers -type B = { ->B : ["1", "2"] +type HomomorphicType = { +>HomomorphicType : ["1", "2"] - [K in keyof A]: `${A[K]}` + [K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` } -const b: B = ['1', '2'] ->b : ["1", "2"] +const homomorphic: HomomorphicType = ['1', '2'] +>homomorphic : ["1", "2"] >['1', '2'] : ["1", "2"] >'1' : "1" >'2' : "2" -type C = { ->C : C +type GenericType = { +>GenericType : GenericType [K in keyof T]: [K, T[K]] } -type D = { ->D : [1, 1, 1] +type HomomorphicInstantiation = { +>HomomorphicInstantiation : [1, 1, 1] - [K in keyof C<['c', 'd', 'e']>]: 1 + [K in keyof GenericType<['c', 'd', 'e']>]: 1 } -const d: D = [1, 1, 1] +const d: HomomorphicInstantiation = [1, 1, 1] >d : [1, 1, 1] >[1, 1, 1] : [1, 1, 1] >1 : 1 >1 : 1 >1 : 1 +type TupleOfNumbersAndObjects = [1, 2, {}] +>TupleOfNumbersAndObjects : TupleOfNumbersAndObjects + +type ShoulsErrorInAssignement = { +>ShoulsErrorInAssignement : ["1", "2", string] + + [K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` +} + // repro from #27995 type Foo = ['a', 'b']; >Foo : Foo diff --git a/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts b/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts index d6d780ad45ae3..af3d0d4137573 100644 --- a/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts +++ b/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts @@ -1,20 +1,26 @@ -type A = [1, 2] +type TupleOfNumbers = [1, 2] -type B = { - [K in keyof A]: `${A[K]}` +type HomomorphicType = { + [K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` } -const b: B = ['1', '2'] +const homomorphic: HomomorphicType = ['1', '2'] -type C = { +type GenericType = { [K in keyof T]: [K, T[K]] } -type D = { - [K in keyof C<['c', 'd', 'e']>]: 1 +type HomomorphicInstantiation = { + [K in keyof GenericType<['c', 'd', 'e']>]: 1 } -const d: D = [1, 1, 1] +const d: HomomorphicInstantiation = [1, 1, 1] + +type TupleOfNumbersAndObjects = [1, 2, {}] + +type ShoulsErrorInAssignement = { + [K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` +} // repro from #27995 type Foo = ['a', 'b']; From 294f5afd215dc55fbea840d0140d2b4aa9f99ad9 Mon Sep 17 00:00:00 2001 From: Michael Arnaldi Date: Sat, 26 Mar 2022 19:23:37 +0000 Subject: [PATCH 4/9] chore: fix lint --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6542f6e01278a..404df1b768441 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13353,12 +13353,12 @@ namespace ts { typeParameter.constraint = getInferredTypeParameterConstraint(typeParameter) || noConstraintType; } else { - // Detect is the constraint is for a homomorphic mapped type to a tuple and in case return a literal union of the used tuple keys + // Detect is the constraint is for a homomorphic mapped type to a tuple and in case return a literal union of the used tuple keys if (constraintDeclaration.parent && constraintDeclaration.parent.parent && constraintDeclaration.parent.parent.kind === SyntaxKind.MappedType) { const mappedTypeNode = constraintDeclaration.parent.parent as MappedTypeNode; if (!mappedTypeNode.nameType && mappedTypeNode.typeParameter.constraint && isTypeOperatorNode(mappedTypeNode.typeParameter.constraint) && mappedTypeNode.typeParameter.constraint.operator === SyntaxKind.KeyOfKeyword) { const keyOfTarget = getTypeFromTypeNode(mappedTypeNode.typeParameter.constraint.type); - if (isTupleType(keyOfTarget)) { + if (isTupleType(keyOfTarget)) { typeParameter.constraint = getUnionType(map(getTypeArguments(keyOfTarget), (_, i) => getStringLiteralType("" + i))); return typeParameter.constraint; } From fbc586a583366bf0ac5774b0a4084d3b9804ace6 Mon Sep 17 00:00:00 2001 From: Michael Arnaldi Date: Sun, 27 Mar 2022 05:55:18 +0000 Subject: [PATCH 5/9] fix: fix typo in tests --- .../reference/mappedTypeConcreteTupleHomomorphism.errors.txt | 2 +- .../reference/mappedTypeConcreteTupleHomomorphism.js | 2 +- .../reference/mappedTypeConcreteTupleHomomorphism.symbols | 4 ++-- .../reference/mappedTypeConcreteTupleHomomorphism.types | 4 ++-- tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt index 7e31d9ef9e72f..3db494f88bbc9 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt @@ -24,7 +24,7 @@ tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts(22,47): error TS2322 type TupleOfNumbersAndObjects = [1, 2, {}] - type ShoulsErrorInAssignement = { + type ShouldErrorOnInterpolation = { [K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2322: Type 'TupleOfNumbersAndObjects[K]' is not assignable to type 'string | number | bigint | boolean'. diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js index ec02eb6691b02..b54d0b80fb6fe 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js @@ -19,7 +19,7 @@ const d: HomomorphicInstantiation = [1, 1, 1] type TupleOfNumbersAndObjects = [1, 2, {}] -type ShoulsErrorInAssignement = { +type ShouldErrorOnInterpolation = { [K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` } diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols index 4193a36916007..e65427c5a2b88 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols @@ -43,8 +43,8 @@ const d: HomomorphicInstantiation = [1, 1, 1] type TupleOfNumbersAndObjects = [1, 2, {}] >TupleOfNumbersAndObjects : Symbol(TupleOfNumbersAndObjects, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 45)) -type ShoulsErrorInAssignement = { ->ShoulsErrorInAssignement : Symbol(ShoulsErrorInAssignement, Decl(mappedTypeConcreteTupleHomomorphism.ts, 18, 42)) +type ShouldErrorOnInterpolation = { +>ShouldErrorOnInterpolation : Symbol(ShouldErrorOnInterpolation, Decl(mappedTypeConcreteTupleHomomorphism.ts, 18, 42)) [K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` >K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 21, 5)) diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types index e1f3542d0ae8c..bfd90b81b685e 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types @@ -36,8 +36,8 @@ const d: HomomorphicInstantiation = [1, 1, 1] type TupleOfNumbersAndObjects = [1, 2, {}] >TupleOfNumbersAndObjects : TupleOfNumbersAndObjects -type ShoulsErrorInAssignement = { ->ShoulsErrorInAssignement : ["1", "2", string] +type ShouldErrorOnInterpolation = { +>ShouldErrorOnInterpolation : ["1", "2", string] [K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` } diff --git a/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts b/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts index af3d0d4137573..6dc1827ff900b 100644 --- a/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts +++ b/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts @@ -18,7 +18,7 @@ const d: HomomorphicInstantiation = [1, 1, 1] type TupleOfNumbersAndObjects = [1, 2, {}] -type ShoulsErrorInAssignement = { +type ShouldErrorOnInterpolation = { [K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` } From ab725d6f0ff343c251d7d57db3e64b6789e65c57 Mon Sep 17 00:00:00 2001 From: Michael Arnaldi Date: Sun, 27 Mar 2022 06:26:20 +0000 Subject: [PATCH 6/9] chore: extract common logic --- src/compiler/checker.ts | 42 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 404df1b768441..ec068596303a6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13340,6 +13340,18 @@ namespace ts { return inferences && getIntersectionType(inferences); } + function getTupleConstraintFromMappedTypeNode(node: MappedTypeNode) { + if (!node.nameType && node.typeParameter.constraint && + isTypeOperatorNode(node.typeParameter.constraint) && + node.typeParameter.constraint.operator === SyntaxKind.KeyOfKeyword + ) { + const keyOfTarget = getTypeFromTypeNode(node.typeParameter.constraint.type); + if (isTupleType(keyOfTarget)) { + return keyOfTarget + } + } + } + /** This is a worker function. Use getConstraintOfTypeParameter which guards against circular constraints. */ function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type | undefined { if (!typeParameter.constraint) { @@ -13355,13 +13367,10 @@ namespace ts { else { // Detect is the constraint is for a homomorphic mapped type to a tuple and in case return a literal union of the used tuple keys if (constraintDeclaration.parent && constraintDeclaration.parent.parent && constraintDeclaration.parent.parent.kind === SyntaxKind.MappedType) { - const mappedTypeNode = constraintDeclaration.parent.parent as MappedTypeNode; - if (!mappedTypeNode.nameType && mappedTypeNode.typeParameter.constraint && isTypeOperatorNode(mappedTypeNode.typeParameter.constraint) && mappedTypeNode.typeParameter.constraint.operator === SyntaxKind.KeyOfKeyword) { - const keyOfTarget = getTypeFromTypeNode(mappedTypeNode.typeParameter.constraint.type); - if (isTupleType(keyOfTarget)) { - typeParameter.constraint = getUnionType(map(getTypeArguments(keyOfTarget), (_, i) => getStringLiteralType("" + i))); - return typeParameter.constraint; - } + const keyOfTarget = getTupleConstraintFromMappedTypeNode(constraintDeclaration.parent.parent as MappedTypeNode); + if (keyOfTarget) { + typeParameter.constraint = getUnionType(map(getTypeArguments(keyOfTarget), (_, i) => getStringLiteralType("" + i))); + return typeParameter.constraint; } } let type = getTypeFromTypeNode(constraintDeclaration); @@ -15860,17 +15869,14 @@ namespace ts { // references itself through one or more type aliases. getConstraintTypeFromMappedType(type); // Detect if the mapped type should be homomorphic to a tuple by checking the declaration of the constraint if it contains a keyof over a tuple - if (!node.nameType && node.typeParameter.constraint && isTypeOperatorNode(node.typeParameter.constraint) && node.typeParameter.constraint.operator === SyntaxKind.KeyOfKeyword) { - const keyOfTarget = getTypeFromTypeNode(node.typeParameter.constraint.type); - if (isTupleType(keyOfTarget)) { - // Instantiate the mapped type over a tuple with an identity mapper - const instantiatedTupleMappedType = instantiateMappedTupleType( - keyOfTarget, - type, - makeFunctionTypeMapper(identity) - ); - links.resolvedType = instantiatedTupleMappedType; - } + const keyOfTarget = getTupleConstraintFromMappedTypeNode(node); + if (keyOfTarget) { + // Instantiate the mapped type over a tuple with an identity mapper + links.resolvedType = instantiateMappedTupleType( + keyOfTarget, + type, + makeFunctionTypeMapper(identity) + ); } } return links.resolvedType; From fd8cbf8fdd78f6230d25b13b814bd6d0f65cd225 Mon Sep 17 00:00:00 2001 From: Michael Arnaldi Date: Sun, 27 Mar 2022 06:37:29 +0000 Subject: [PATCH 7/9] chore: fix lint --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ec068596303a6..ae0173660aa22 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13347,7 +13347,7 @@ namespace ts { ) { const keyOfTarget = getTypeFromTypeNode(node.typeParameter.constraint.type); if (isTupleType(keyOfTarget)) { - return keyOfTarget + return keyOfTarget; } } } From e9ebeaf160ef4d464a320b91af94c08936154856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 18 Sep 2023 11:34:13 +0200 Subject: [PATCH 8/9] Avoid creating a mapped type object altogether --- src/compiler/checker.ts | 144 +++++++++++++++------------ src/lib/es2015.symbol.wellknown.d.ts | 4 +- 2 files changed, 85 insertions(+), 63 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f3a89c9baf295..5ec6774023fef 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13720,10 +13720,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type.modifiersType; } + function getMappedTypeNodeModifiers(node: MappedTypeNode) { + return (node.readonlyToken ? node.readonlyToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeReadonly : MappedTypeModifiers.IncludeReadonly : 0) | + (node.questionToken ? node.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0); + } + function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers { - const declaration = type.declaration; - return (declaration.readonlyToken ? declaration.readonlyToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeReadonly : MappedTypeModifiers.IncludeReadonly : 0) | - (declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0); + return getMappedTypeNodeModifiers(type.declaration); } function getMappedTypeOptionality(type: MappedType): number { @@ -15459,18 +15462,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return inferences && getIntersectionType(inferences); } - function getTupleConstraintFromMappedTypeNode(node: MappedTypeNode) { - if (!node.nameType && node.typeParameter.constraint && - isTypeOperatorNode(node.typeParameter.constraint) && - node.typeParameter.constraint.operator === SyntaxKind.KeyOfKeyword - ) { - const keyOfTarget = getTypeFromTypeNode(node.typeParameter.constraint.type); - if (isTupleType(keyOfTarget)) { - return keyOfTarget; - } - } - } - /** This is a worker function. Use getConstraintOfTypeParameter which guards against circular constraints. */ function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type | undefined { if (!typeParameter.constraint) { @@ -15484,14 +15475,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { typeParameter.constraint = getInferredTypeParameterConstraint(typeParameter) || noConstraintType; } else { - // Detect is the constraint is for a homomorphic mapped type to a tuple and in case return a literal union of the used tuple keys - if (constraintDeclaration.parent && constraintDeclaration.parent.parent && constraintDeclaration.parent.parent.kind === SyntaxKind.MappedType) { - const keyOfTarget = getTupleConstraintFromMappedTypeNode(constraintDeclaration.parent.parent as MappedTypeNode); - if (keyOfTarget) { - typeParameter.constraint = getUnionType(map(getTypeArguments(keyOfTarget), (_, i) => getStringLiteralType("" + i))); - return typeParameter.constraint; - } - } let type = getTypeFromTypeNode(constraintDeclaration); if (type.flags & TypeFlags.Any && !isErrorType(type)) { // Allow errorType to propegate to keep downstream errors suppressed // use keyofConstraintType as the base constraint for mapped type key constraints (unknown isn;t assignable to that, but `any` was), @@ -15902,9 +15885,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Given a homomorphic mapped type { [K in keyof T]: XXX }, where T is constrained to an array or tuple type, in the // template type XXX, K has an added constraint of number | `${number}`. else if (type.flags & TypeFlags.TypeParameter && parent.kind === SyntaxKind.MappedType && node === (parent as MappedTypeNode).type) { - const typeFromTypeNode = getTypeFromTypeNode(parent as TypeNode); - if (getObjectFlags(typeFromTypeNode) & ObjectFlags.Mapped) { - const mappedType = typeFromTypeNode as MappedType; + const arrayOrTupleConstraint = getArrayOrTupleMappedTypeNodeConstraint(parent as MappedTypeNode); + if (arrayOrTupleConstraint) { + if (isTupleType(arrayOrTupleConstraint)) { + constraints = append(constraints, getUnionType(map(getTypeArguments(arrayOrTupleConstraint), (_, i) => getStringLiteralType("" + i)))); + } + else { + constraints = append(constraints, getUnionType([numberType, numericStringType])); + } + } + else { + const mappedType = getTypeFromTypeNode(parent as TypeNode) as MappedType; if (getTypeParameterFromMappedType(mappedType) === getActualTypeVariable(type)) { const typeParameter = getHomomorphicTypeVariable(mappedType); if (typeParameter) { @@ -18175,27 +18166,60 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return links.resolvedType; } + function getArrayOrTupleMappedTypeNodeConstraint(node: MappedTypeNode): TypeReference | undefined { + if ( + !node.nameType && node.typeParameter.constraint && + isTypeOperatorNode(node.typeParameter.constraint) && + node.typeParameter.constraint.operator === SyntaxKind.KeyOfKeyword + ) { + const constraint = getTypeFromTypeNode(node.typeParameter.constraint.type); + if (isArrayOrTupleType(constraint)) { + return constraint; + } + } + } + function getTypeFromMappedTypeNode(node: MappedTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { + // Eagerly resolve the constraint type which forces an error if the constraint type circularly + // references itself through one or more type aliases. + const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node.typeParameter)); + const constraintType = getConstraintOfTypeParameter(typeParameter); + + const arrayOrTupleContraint = getArrayOrTupleMappedTypeNodeConstraint(node); + if (arrayOrTupleContraint) { + if (!node.type) { + return errorType; + } + const modifiers = getMappedTypeNodeModifiers(node); + const templateType = addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true, !!(modifiers & MappedTypeModifiers.IncludeOptional)); + + if (isTupleType(arrayOrTupleContraint)) { + return links.resolvedType = instantiateMappedTupleType( + arrayOrTupleContraint, + modifiers, + typeParameter, + templateType, + /*mapper*/ undefined, + ); + } + + return links.resolvedType = instantiateMappedArrayType( + arrayOrTupleContraint, + modifiers, + typeParameter, + templateType, + /*mapper*/ undefined, + ); + } const type = createObjectType(ObjectFlags.Mapped, node.symbol) as MappedType; type.declaration = node; type.aliasSymbol = getAliasSymbolForTypeNode(node); type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol); + type.typeParameter = typeParameter; + type.constraintType = constraintType || errorType; links.resolvedType = type; - // Eagerly resolve the constraint type which forces an error if the constraint type circularly - // references itself through one or more type aliases. - getConstraintTypeFromMappedType(type); - // Detect if the mapped type should be homomorphic to a tuple by checking the declaration of the constraint if it contains a keyof over a tuple - const keyOfTarget = getTupleConstraintFromMappedTypeNode(node); - if (keyOfTarget) { - // Instantiate the mapped type over a tuple with an identity mapper - links.resolvedType = instantiateMappedTupleType( - keyOfTarget, - type, - makeFunctionTypeMapper(a => a, () => '(identity mapper)') - ); - } } return links.resolvedType; } @@ -19343,13 +19367,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 && (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, isArrayOrTupleType) ) { - return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper)); + return instantiateMappedArrayType(t, getMappedTypeModifiers(type), getTypeParameterFromMappedType(type), getTemplateTypeFromMappedType(type.target as MappedType || type), prependTypeMapping(typeVariable, t, mapper)); } if (isGenericTupleType(t)) { return instantiateMappedGenericTupleType(t, type, typeVariable, mapper); } if (isTupleType(t)) { - return instantiateMappedTupleType(t, type, prependTypeMapping(typeVariable, t, mapper)); + return instantiateMappedTupleType(t, getMappedTypeModifiers(type), getTypeParameterFromMappedType(type), getTemplateTypeFromMappedType(type.target as MappedType || type), prependTypeMapping(typeVariable, t, mapper)); } } return instantiateAnonymousType(type, prependTypeMapping(typeVariable, t, mapper)); @@ -19391,16 +19415,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return createTupleType(elementTypes, map(elementTypes, _ => ElementFlags.Variadic), newReadonly); } - function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) { - const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper); + function instantiateMappedArrayType(arrayType: Type, modifiers: MappedTypeModifiers, typeParameter: TypeParameter, templateType: Type, mapper: TypeMapper | undefined) { + const elementType = instantiateMappedTypeTemplate(modifiers, typeParameter, templateType, numberType, /*isOptional*/ true, mapper); return isErrorType(elementType) ? errorType : - createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType))); + createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), modifiers)); } - function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) { + function instantiateMappedTupleType(tupleType: TupleTypeReference, modifiers: MappedTypeModifiers, typeParameter: TypeParameter, templateType: Type, mapper: TypeMapper | undefined) { const elementFlags = tupleType.target.elementFlags; - const elementTypes = map(getElementTypes(tupleType), (_, i) => instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(elementFlags[i] & ElementFlags.Optional), mapper)); - const modifiers = getMappedTypeModifiers(mappedType); + const elementTypes = map(getElementTypes(tupleType), (_, i) => instantiateMappedTypeTemplate(modifiers, typeParameter, templateType, getStringLiteralType("" + i), !!(elementFlags[i] & ElementFlags.Optional), mapper)); const newTupleModifiers = modifiers & MappedTypeModifiers.IncludeOptional ? map(elementFlags, f => f & ElementFlags.Required ? ElementFlags.Optional : f) : modifiers & MappedTypeModifiers.ExcludeOptional ? map(elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : elementFlags; @@ -19409,10 +19432,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { createTupleType(elementTypes, newTupleModifiers, newReadonly, tupleType.target.labeledElementDeclarations); } - function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) { - const templateMapper = appendTypeMapping(mapper, getTypeParameterFromMappedType(type), key); - const propType = instantiateType(getTemplateTypeFromMappedType(type.target as MappedType || type), templateMapper); - const modifiers = getMappedTypeModifiers(type); + function instantiateMappedTypeTemplate(modifiers: MappedTypeModifiers, typeParameter: TypeParameter, templateType: Type, key: Type, isOptional: boolean, mapper: TypeMapper | undefined) { + const templateMapper = appendTypeMapping(mapper, typeParameter, key); + const propType = instantiateType(templateType, templateMapper); return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) : strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : propType; @@ -39649,16 +39671,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const type = getTypeFromMappedTypeNode(node) as MappedType; - // Continue to check if the type returned is a mapped type, that means it wasn't resolved to a homomorphic tuple type - if (type.flags & TypeFlags.Object && (type as ObjectType).objectFlags & ObjectFlags.Mapped) { - const nameType = getNameTypeFromMappedType(type); - if (nameType) { - checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType); - } - else { - const constraintType = getConstraintTypeFromMappedType(type); - checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); - } + if (!(getObjectFlags(type) & ObjectFlags.Mapped)) { + return; + } + const nameType = getNameTypeFromMappedType(type); + if (nameType) { + checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType); + } + else { + const constraintType = getConstraintTypeFromMappedType(type); + checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); } } diff --git a/src/lib/es2015.symbol.wellknown.d.ts b/src/lib/es2015.symbol.wellknown.d.ts index cf6ccef52b89f..6212e12c0d647 100644 --- a/src/lib/es2015.symbol.wellknown.d.ts +++ b/src/lib/es2015.symbol.wellknown.d.ts @@ -77,7 +77,7 @@ interface Array { * when they will be absent when used in a 'with' statement. */ readonly [Symbol.unscopables]: { - [K in keyof any[]]?: boolean; + [K in keyof any[] as K]?: boolean; }; } @@ -87,7 +87,7 @@ interface ReadonlyArray { * when they will be absent when used in a 'with' statement. */ readonly [Symbol.unscopables]: { - [K in keyof readonly any[]]?: boolean; + [K in keyof readonly any[] as K]?: boolean; }; } From 2387a6e3b346f2bd289470558e1201b53f3e3ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 18 Sep 2023 13:06:23 +0200 Subject: [PATCH 9/9] Avoid the introduced check to be syntactical --- src/compiler/checker.ts | 56 +++++++------- ...edTypeConcreteTupleHomomorphism.errors.txt | 9 ++- .../mappedTypeConcreteTupleHomomorphism.js | 8 +- ...appedTypeConcreteTupleHomomorphism.symbols | 76 +++++++++++-------- .../mappedTypeConcreteTupleHomomorphism.types | 15 +++- .../mappedTypeConcreteTupleHomomorphism.ts | 7 +- 6 files changed, 108 insertions(+), 63 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5ec6774023fef..1558dcd716ae4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15884,22 +15884,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } // Given a homomorphic mapped type { [K in keyof T]: XXX }, where T is constrained to an array or tuple type, in the // template type XXX, K has an added constraint of number | `${number}`. - else if (type.flags & TypeFlags.TypeParameter && parent.kind === SyntaxKind.MappedType && node === (parent as MappedTypeNode).type) { - const arrayOrTupleConstraint = getArrayOrTupleMappedTypeNodeConstraint(parent as MappedTypeNode); - if (arrayOrTupleConstraint) { - if (isTupleType(arrayOrTupleConstraint)) { - constraints = append(constraints, getUnionType(map(getTypeArguments(arrayOrTupleConstraint), (_, i) => getStringLiteralType("" + i)))); + else if (type.flags & TypeFlags.TypeParameter && parent.kind === SyntaxKind.MappedType && node === (parent as MappedTypeNode).type && !(parent as MappedTypeNode).nameType) { + const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration((parent as MappedTypeNode).typeParameter)); + const constraintType = getConstraintOfTypeParameter(typeParameter) || errorType; + const arrayOrTuple = getArrayOrTupleOriginIndexType(constraintType); + if (arrayOrTuple) { + if (isTupleType(arrayOrTuple)) { + constraints = append(constraints, getUnionType(map(getTypeArguments(arrayOrTuple), (_, i) => getStringLiteralType("" + i)))); } else { constraints = append(constraints, getUnionType([numberType, numericStringType])); } } else { - const mappedType = getTypeFromTypeNode(parent as TypeNode) as MappedType; - if (getTypeParameterFromMappedType(mappedType) === getActualTypeVariable(type)) { - const typeParameter = getHomomorphicTypeVariable(mappedType); - if (typeParameter) { - const constraint = getConstraintOfTypeParameter(typeParameter); + if (typeParameter === getActualTypeVariable(type)) { + const typeVariable = constraintType.flags & TypeFlags.Index && getActualTypeVariable((constraintType as IndexType).type); + const homomorphicTypeVariable = typeVariable && typeVariable.flags & TypeFlags.TypeParameter ? typeVariable as TypeParameter : undefined; + if (homomorphicTypeVariable) { + const constraint = getConstraintOfTypeParameter(homomorphicTypeVariable); if (constraint && everyType(constraint, isArrayOrTupleType)) { constraints = append(constraints, getUnionType([numberType, numericStringType])); } @@ -18166,17 +18168,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return links.resolvedType; } - function getArrayOrTupleMappedTypeNodeConstraint(node: MappedTypeNode): TypeReference | undefined { - if ( - !node.nameType && node.typeParameter.constraint && - isTypeOperatorNode(node.typeParameter.constraint) && - node.typeParameter.constraint.operator === SyntaxKind.KeyOfKeyword - ) { - const constraint = getTypeFromTypeNode(node.typeParameter.constraint.type); - if (isArrayOrTupleType(constraint)) { - return constraint; - } + function getArrayOrTupleOriginIndexType(type: Type) { + if (!(type.flags & TypeFlags.Union)) { + return; } + const origin = (type as UnionType).origin; + if (!origin || !(origin.flags & TypeFlags.Index)) { + return; + } + const originType = (origin as IndexType).type; + return isArrayOrTupleType(originType) ? originType : undefined; } function getTypeFromMappedTypeNode(node: MappedTypeNode): Type { @@ -18185,19 +18186,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Eagerly resolve the constraint type which forces an error if the constraint type circularly // references itself through one or more type aliases. const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node.typeParameter)); - const constraintType = getConstraintOfTypeParameter(typeParameter); - - const arrayOrTupleContraint = getArrayOrTupleMappedTypeNodeConstraint(node); - if (arrayOrTupleContraint) { + const constraintType = getConstraintOfTypeParameter(typeParameter) || errorType; + const arrayOrTuple = !node.nameType && getArrayOrTupleOriginIndexType(constraintType); + if (arrayOrTuple) { if (!node.type) { return errorType; } const modifiers = getMappedTypeNodeModifiers(node); const templateType = addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true, !!(modifiers & MappedTypeModifiers.IncludeOptional)); - if (isTupleType(arrayOrTupleContraint)) { + if (isTupleType(arrayOrTuple)) { return links.resolvedType = instantiateMappedTupleType( - arrayOrTupleContraint, + arrayOrTuple, modifiers, typeParameter, templateType, @@ -18206,7 +18206,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return links.resolvedType = instantiateMappedArrayType( - arrayOrTupleContraint, + arrayOrTuple, modifiers, typeParameter, templateType, @@ -18218,7 +18218,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { type.aliasSymbol = getAliasSymbolForTypeNode(node); type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol); type.typeParameter = typeParameter; - type.constraintType = constraintType || errorType; + type.constraintType = constraintType; links.resolvedType = type; } return links.resolvedType; diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt index f7a43b794f823..ac1457c07d724 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.errors.txt @@ -1,4 +1,4 @@ -mappedTypeConcreteTupleHomomorphism.ts(22,47): error TS2322: Type 'TupleOfNumbersAndObjects[K]' is not assignable to type 'string | number | bigint | boolean'. +mappedTypeConcreteTupleHomomorphism.ts(27,47): error TS2322: Type 'TupleOfNumbersAndObjects[K]' is not assignable to type 'string | number | bigint | boolean'. Type '{} | 2 | 1' is not assignable to type 'string | number | bigint | boolean'. Type '{}' is not assignable to type 'string | number | bigint | boolean'. @@ -9,9 +9,14 @@ mappedTypeConcreteTupleHomomorphism.ts(22,47): error TS2322: Type 'TupleOfNumber type HomomorphicType = { [K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` } - const homomorphic: HomomorphicType = ['1', '2'] + type TupleOfNumbersKeys = keyof TupleOfNumbers + type HomomorphicType2 = { + [K in TupleOfNumbersKeys]: `${TupleOfNumbers[K]}` + } + const homomorphic2: HomomorphicType2 = ['1', '2'] + type GenericType = { [K in keyof T]: [K, T[K]] } diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js index ebcd1d6800341..f2a8b89a4a260 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.js @@ -6,9 +6,14 @@ type TupleOfNumbers = [1, 2] type HomomorphicType = { [K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` } - const homomorphic: HomomorphicType = ['1', '2'] +type TupleOfNumbersKeys = keyof TupleOfNumbers +type HomomorphicType2 = { + [K in TupleOfNumbersKeys]: `${TupleOfNumbers[K]}` +} +const homomorphic2: HomomorphicType2 = ['1', '2'] + type GenericType = { [K in keyof T]: [K, T[K]] } @@ -38,4 +43,5 @@ type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; //// [mappedTypeConcreteTupleHomomorphism.js] var homomorphic = ['1', '2']; +var homomorphic2 = ['1', '2']; var d = [1, 1, 1]; diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols index 98d87f678372f..1f906e384d1c6 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.symbols @@ -13,67 +13,83 @@ type HomomorphicType = { >TupleOfNumbers : Symbol(TupleOfNumbers, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) >K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 3, 5)) } - const homomorphic: HomomorphicType = ['1', '2'] ->homomorphic : Symbol(homomorphic, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 5)) +>homomorphic : Symbol(homomorphic, Decl(mappedTypeConcreteTupleHomomorphism.ts, 5, 5)) >HomomorphicType : Symbol(HomomorphicType, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 28)) -type GenericType = { ->GenericType : Symbol(GenericType, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 47)) ->T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 17)) +type TupleOfNumbersKeys = keyof TupleOfNumbers +>TupleOfNumbersKeys : Symbol(TupleOfNumbersKeys, Decl(mappedTypeConcreteTupleHomomorphism.ts, 5, 47)) +>TupleOfNumbers : Symbol(TupleOfNumbers, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) - [K in keyof T]: [K, T[K]] ->K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 9, 5)) ->T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 17)) +type HomomorphicType2 = { +>HomomorphicType2 : Symbol(HomomorphicType2, Decl(mappedTypeConcreteTupleHomomorphism.ts, 7, 46)) + + [K in TupleOfNumbersKeys]: `${TupleOfNumbers[K]}` >K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 9, 5)) ->T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 17)) +>TupleOfNumbersKeys : Symbol(TupleOfNumbersKeys, Decl(mappedTypeConcreteTupleHomomorphism.ts, 5, 47)) +>TupleOfNumbers : Symbol(TupleOfNumbers, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) >K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 9, 5)) } +const homomorphic2: HomomorphicType2 = ['1', '2'] +>homomorphic2 : Symbol(homomorphic2, Decl(mappedTypeConcreteTupleHomomorphism.ts, 11, 5)) +>HomomorphicType2 : Symbol(HomomorphicType2, Decl(mappedTypeConcreteTupleHomomorphism.ts, 7, 46)) + +type GenericType = { +>GenericType : Symbol(GenericType, Decl(mappedTypeConcreteTupleHomomorphism.ts, 11, 49)) +>T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 13, 17)) + + [K in keyof T]: [K, T[K]] +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 14, 5)) +>T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 13, 17)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 14, 5)) +>T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 13, 17)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 14, 5)) +} type HomomorphicInstantiation = { ->HomomorphicInstantiation : Symbol(HomomorphicInstantiation, Decl(mappedTypeConcreteTupleHomomorphism.ts, 10, 1)) +>HomomorphicInstantiation : Symbol(HomomorphicInstantiation, Decl(mappedTypeConcreteTupleHomomorphism.ts, 15, 1)) [K in keyof GenericType<['c', 'd', 'e']>]: 1 ->K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 13, 5)) ->GenericType : Symbol(GenericType, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 47)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 18, 5)) +>GenericType : Symbol(GenericType, Decl(mappedTypeConcreteTupleHomomorphism.ts, 11, 49)) } const d: HomomorphicInstantiation = [1, 1, 1] ->d : Symbol(d, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 5)) ->HomomorphicInstantiation : Symbol(HomomorphicInstantiation, Decl(mappedTypeConcreteTupleHomomorphism.ts, 10, 1)) +>d : Symbol(d, Decl(mappedTypeConcreteTupleHomomorphism.ts, 21, 5)) +>HomomorphicInstantiation : Symbol(HomomorphicInstantiation, Decl(mappedTypeConcreteTupleHomomorphism.ts, 15, 1)) type TupleOfNumbersAndObjects = [1, 2, {}] ->TupleOfNumbersAndObjects : Symbol(TupleOfNumbersAndObjects, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 45)) +>TupleOfNumbersAndObjects : Symbol(TupleOfNumbersAndObjects, Decl(mappedTypeConcreteTupleHomomorphism.ts, 21, 45)) type ShouldErrorOnInterpolation = { ->ShouldErrorOnInterpolation : Symbol(ShouldErrorOnInterpolation, Decl(mappedTypeConcreteTupleHomomorphism.ts, 18, 42)) +>ShouldErrorOnInterpolation : Symbol(ShouldErrorOnInterpolation, Decl(mappedTypeConcreteTupleHomomorphism.ts, 23, 42)) [K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` ->K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 21, 5)) ->TupleOfNumbersAndObjects : Symbol(TupleOfNumbersAndObjects, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 45)) ->TupleOfNumbersAndObjects : Symbol(TupleOfNumbersAndObjects, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 45)) ->K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 21, 5)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 26, 5)) +>TupleOfNumbersAndObjects : Symbol(TupleOfNumbersAndObjects, Decl(mappedTypeConcreteTupleHomomorphism.ts, 21, 45)) +>TupleOfNumbersAndObjects : Symbol(TupleOfNumbersAndObjects, Decl(mappedTypeConcreteTupleHomomorphism.ts, 21, 45)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 26, 5)) } // repro from #27995 type Foo = ['a', 'b']; ->Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 22, 1)) +>Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 27, 1)) interface Bar { ->Bar : Symbol(Bar, Decl(mappedTypeConcreteTupleHomomorphism.ts, 25, 22)) +>Bar : Symbol(Bar, Decl(mappedTypeConcreteTupleHomomorphism.ts, 30, 22)) a: string; ->a : Symbol(Bar.a, Decl(mappedTypeConcreteTupleHomomorphism.ts, 27, 15)) +>a : Symbol(Bar.a, Decl(mappedTypeConcreteTupleHomomorphism.ts, 32, 15)) b: number; ->b : Symbol(Bar.b, Decl(mappedTypeConcreteTupleHomomorphism.ts, 28, 14)) +>b : Symbol(Bar.b, Decl(mappedTypeConcreteTupleHomomorphism.ts, 33, 14)) } type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; ->Baz : Symbol(Baz, Decl(mappedTypeConcreteTupleHomomorphism.ts, 30, 1)) ->K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 32, 14)) ->Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 22, 1)) ->Bar : Symbol(Bar, Decl(mappedTypeConcreteTupleHomomorphism.ts, 25, 22)) ->Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 22, 1)) ->K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 32, 14)) +>Baz : Symbol(Baz, Decl(mappedTypeConcreteTupleHomomorphism.ts, 35, 1)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 37, 14)) +>Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 27, 1)) +>Bar : Symbol(Bar, Decl(mappedTypeConcreteTupleHomomorphism.ts, 30, 22)) +>Foo : Symbol(Foo, Decl(mappedTypeConcreteTupleHomomorphism.ts, 27, 1)) +>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 37, 14)) diff --git a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types index e61ef9de911fe..7be536633f665 100644 --- a/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types +++ b/tests/baselines/reference/mappedTypeConcreteTupleHomomorphism.types @@ -9,13 +9,26 @@ type HomomorphicType = { [K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` } - const homomorphic: HomomorphicType = ['1', '2'] >homomorphic : ["1", "2"] >['1', '2'] : ["1", "2"] >'1' : "1" >'2' : "2" +type TupleOfNumbersKeys = keyof TupleOfNumbers +>TupleOfNumbersKeys : keyof TupleOfNumbers + +type HomomorphicType2 = { +>HomomorphicType2 : ["1", "2"] + + [K in TupleOfNumbersKeys]: `${TupleOfNumbers[K]}` +} +const homomorphic2: HomomorphicType2 = ['1', '2'] +>homomorphic2 : ["1", "2"] +>['1', '2'] : ["1", "2"] +>'1' : "1" +>'2' : "2" + type GenericType = { >GenericType : GenericType diff --git a/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts b/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts index 6dc1827ff900b..843e20436d6d5 100644 --- a/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts +++ b/tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts @@ -3,9 +3,14 @@ type TupleOfNumbers = [1, 2] type HomomorphicType = { [K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` } - const homomorphic: HomomorphicType = ['1', '2'] +type TupleOfNumbersKeys = keyof TupleOfNumbers +type HomomorphicType2 = { + [K in TupleOfNumbersKeys]: `${TupleOfNumbers[K]}` +} +const homomorphic2: HomomorphicType2 = ['1', '2'] + type GenericType = { [K in keyof T]: [K, T[K]] }