-
Notifications
You must be signed in to change notification settings - Fork 12.8k
bugfix: homomorphic mapped types when T is non-generic, solves 27995 #48433
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
base: main
Are you sure you want to change the base?
Changes from 4 commits
260708d
3b70763
3e203b1
294f5af
fbc586a
ab725d6
fd8cbf8
b0083f8
e9ebeaf
2387a6e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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), | ||
|
@@ -15848,6 +15859,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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you perhaps understand why this isn't sort of handled within There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So |
||
// 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 +35603,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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where does this typechecking for a homomorphic mapped type happen now? in the added There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So in case the returned type is a tuple type (which I now realize it may be a simpler condition here) we don't have a mapped type to check, one condition is implicitly checked, as the type can only be homomorphic if it has no nameType (the |
||
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)); | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
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 (1 errors) ==== | ||
type TupleOfNumbers = [1, 2] | ||
|
||
type HomomorphicType = { | ||
[K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` | ||
} | ||
|
||
const homomorphic: HomomorphicType = ['1', '2'] | ||
|
||
type GenericType<T> = { | ||
[K in keyof T]: [K, T[K]] | ||
} | ||
|
||
type HomomorphicInstantiation = { | ||
[K in keyof GenericType<['c', 'd', 'e']>]: 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']; | ||
|
||
interface Bar { | ||
a: string; | ||
b: number; | ||
} | ||
|
||
type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
//// [mappedTypeConcreteTupleHomomorphism.ts] | ||
type TupleOfNumbers = [1, 2] | ||
|
||
type HomomorphicType = { | ||
[K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` | ||
} | ||
|
||
const homomorphic: HomomorphicType = ['1', '2'] | ||
|
||
type GenericType<T> = { | ||
[K in keyof T]: [K, T[K]] | ||
} | ||
|
||
type HomomorphicInstantiation = { | ||
[K in keyof GenericType<['c', 'd', 'e']>]: 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']; | ||
|
||
interface Bar { | ||
a: string; | ||
b: number; | ||
} | ||
|
||
type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; | ||
|
||
|
||
//// [mappedTypeConcreteTupleHomomorphism.js] | ||
var homomorphic = ['1', '2']; | ||
var d = [1, 1, 1]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
=== tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts === | ||
type TupleOfNumbers = [1, 2] | ||
>TupleOfNumbers : Symbol(TupleOfNumbers, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 0)) | ||
|
||
type HomomorphicType = { | ||
>HomomorphicType : Symbol(HomomorphicType, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 28)) | ||
|
||
[K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` | ||
>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 3, 5)) | ||
>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 homomorphic: HomomorphicType = ['1', '2'] | ||
>homomorphic : Symbol(homomorphic, Decl(mappedTypeConcreteTupleHomomorphism.ts, 6, 5)) | ||
>HomomorphicType : Symbol(HomomorphicType, Decl(mappedTypeConcreteTupleHomomorphism.ts, 0, 28)) | ||
|
||
type GenericType<T> = { | ||
>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, 17)) | ||
>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 9, 5)) | ||
>T : Symbol(T, Decl(mappedTypeConcreteTupleHomomorphism.ts, 8, 17)) | ||
>K : Symbol(K, Decl(mappedTypeConcreteTupleHomomorphism.ts, 9, 5)) | ||
} | ||
|
||
type HomomorphicInstantiation = { | ||
>HomomorphicInstantiation : Symbol(HomomorphicInstantiation, Decl(mappedTypeConcreteTupleHomomorphism.ts, 10, 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)) | ||
} | ||
|
||
const d: HomomorphicInstantiation = [1, 1, 1] | ||
>d : Symbol(d, Decl(mappedTypeConcreteTupleHomomorphism.ts, 16, 5)) | ||
>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, 22, 1)) | ||
|
||
interface Bar { | ||
>Bar : Symbol(Bar, Decl(mappedTypeConcreteTupleHomomorphism.ts, 25, 22)) | ||
|
||
a: string; | ||
>a : Symbol(Bar.a, Decl(mappedTypeConcreteTupleHomomorphism.ts, 27, 15)) | ||
|
||
b: number; | ||
>b : Symbol(Bar.b, Decl(mappedTypeConcreteTupleHomomorphism.ts, 28, 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)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
=== tests/cases/compiler/mappedTypeConcreteTupleHomomorphism.ts === | ||
type TupleOfNumbers = [1, 2] | ||
>TupleOfNumbers : TupleOfNumbers | ||
|
||
type HomomorphicType = { | ||
>HomomorphicType : ["1", "2"] | ||
|
||
[K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` | ||
} | ||
|
||
const homomorphic: HomomorphicType = ['1', '2'] | ||
>homomorphic : ["1", "2"] | ||
>['1', '2'] : ["1", "2"] | ||
>'1' : "1" | ||
>'2' : "2" | ||
|
||
type GenericType<T> = { | ||
>GenericType : GenericType<T> | ||
|
||
[K in keyof T]: [K, T[K]] | ||
} | ||
|
||
type HomomorphicInstantiation = { | ||
>HomomorphicInstantiation : [1, 1, 1] | ||
|
||
[K in keyof GenericType<['c', 'd', 'e']>]: 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 | ||
|
||
interface Bar { | ||
a: string; | ||
>a : string | ||
|
||
b: number; | ||
>b : number | ||
} | ||
|
||
type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; | ||
>Baz : [string, number] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
type TupleOfNumbers = [1, 2] | ||
|
||
type HomomorphicType = { | ||
[K in keyof TupleOfNumbers]: `${TupleOfNumbers[K]}` | ||
} | ||
|
||
const homomorphic: HomomorphicType = ['1', '2'] | ||
|
||
type GenericType<T> = { | ||
[K in keyof T]: [K, T[K]] | ||
} | ||
|
||
type HomomorphicInstantiation = { | ||
[K in keyof GenericType<['c', 'd', 'e']>]: 1 | ||
} | ||
|
||
const d: HomomorphicInstantiation = [1, 1, 1] | ||
|
||
type TupleOfNumbersAndObjects = [1, 2, {}] | ||
|
||
type ShoulsErrorInAssignement = { | ||
mikearnaldi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
[K in keyof TupleOfNumbersAndObjects]: `${TupleOfNumbersAndObjects[K]}` | ||
} | ||
|
||
// repro from #27995 | ||
type Foo = ['a', 'b']; | ||
|
||
interface Bar { | ||
a: string; | ||
b: number; | ||
} | ||
|
||
type Baz = { [K in keyof Foo]: Bar[Foo[K]]; }; |
Uh oh!
There was an error while loading. Please reload this page.