-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Add infer T
constraint inference rule matching up mapped type templates across check/extends types
#43649
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
Add infer T
constraint inference rule matching up mapped type templates across check/extends types
#43649
Changes from all commits
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 |
---|---|---|
|
@@ -12585,6 +12585,19 @@ namespace ts { | |
else if (grandParent.kind === SyntaxKind.TypeParameter && grandParent.parent.kind === SyntaxKind.MappedType) { | ||
inferences = append(inferences, keyofConstraintType); | ||
} | ||
// When an 'infer T' declaration is the template of a mapped type, and that mapped type if the extends | ||
// clause of a conditional whose check type is also a mapped type, give it the constraint of the template | ||
// of the check type's mapped type | ||
Comment on lines
+12589
to
+12590
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.
I think this phrasing is ambiguous, and I want to nitpick it just to figure out whether I’m understanding the goal. It could be interpreted in one of two ways:
Reading the code and the test case, I think what you mean is (2), because given type KeysWithoutStringIndex<T> =
{ [K in keyof T]: string extends K ? never : K } extends { [_ in keyof T]: infer U }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ “template of the check type’s mapped type”
? U
: never I don’t know what it means for 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. Yes on both fronts. That constraint will then later be executed and become a single type. |
||
else if (grandParent.kind === SyntaxKind.MappedType && (grandParent as MappedTypeNode).type && | ||
skipParentheses((grandParent as MappedTypeNode).type!) === declaration.parent && grandParent.parent.kind === SyntaxKind.ConditionalType && | ||
(grandParent.parent as ConditionalTypeNode).extendsType === grandParent && (grandParent.parent as ConditionalTypeNode).checkType.kind === SyntaxKind.MappedType && | ||
((grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode).type) { | ||
const checkMappedType = (grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode; | ||
const nodeType = getTypeFromTypeNode(checkMappedType.type!); | ||
inferences = append(inferences, instantiateType(nodeType, | ||
makeUnaryTypeMapper(getDeclaredTypeOfTypeParameter(getSymbolOfNode(checkMappedType.typeParameter)), checkMappedType.typeParameter.constraint ? getTypeFromTypeNode(checkMappedType.typeParameter.constraint) : keyofConstraintType) | ||
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. What would it mean for 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, we issue an error on it, but it's when you wrote a mapped type like |
||
)); | ||
} | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
//// [inferConditionalConstraintMappedMember.ts] | ||
// Return keyof type without string index signature | ||
type KeysWithoutStringIndex<T> = | ||
{ [K in keyof T]: string extends K ? never : K } extends { [_ in keyof T]: infer U } | ||
? U | ||
: never | ||
|
||
// Only "foo" | "bar" as expected, [string] index signature removed | ||
type test = KeysWithoutStringIndex<{ [index: string]: string; foo: string; bar: 'baz' }> | ||
// KeysWithoutStringIndex<T> will always be a subset of keyof T, but is reported as unassignable | ||
export type RemoveIdxSgn<T> = Pick<T, KeysWithoutStringIndex<T>> | ||
// ERROR: | ||
// Type 'KeysWithoutStringIndex<T>' does not satisfy the constraint 'keyof T'. | ||
// Type 'unknown' is not assignable to type 'keyof T'.(2344) | ||
|
||
//// [inferConditionalConstraintMappedMember.js] | ||
"use strict"; | ||
exports.__esModule = true; | ||
// ERROR: | ||
// Type 'KeysWithoutStringIndex<T>' does not satisfy the constraint 'keyof T'. | ||
// Type 'unknown' is not assignable to type 'keyof T'.(2344) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
=== tests/cases/compiler/inferConditionalConstraintMappedMember.ts === | ||
// Return keyof type without string index signature | ||
type KeysWithoutStringIndex<T> = | ||
>KeysWithoutStringIndex : Symbol(KeysWithoutStringIndex, Decl(inferConditionalConstraintMappedMember.ts, 0, 0)) | ||
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 1, 28)) | ||
|
||
{ [K in keyof T]: string extends K ? never : K } extends { [_ in keyof T]: infer U } | ||
>K : Symbol(K, Decl(inferConditionalConstraintMappedMember.ts, 2, 7)) | ||
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 1, 28)) | ||
>K : Symbol(K, Decl(inferConditionalConstraintMappedMember.ts, 2, 7)) | ||
>K : Symbol(K, Decl(inferConditionalConstraintMappedMember.ts, 2, 7)) | ||
>_ : Symbol(_, Decl(inferConditionalConstraintMappedMember.ts, 2, 64)) | ||
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 1, 28)) | ||
>U : Symbol(U, Decl(inferConditionalConstraintMappedMember.ts, 2, 84)) | ||
|
||
? U | ||
>U : Symbol(U, Decl(inferConditionalConstraintMappedMember.ts, 2, 84)) | ||
|
||
: never | ||
|
||
// Only "foo" | "bar" as expected, [string] index signature removed | ||
type test = KeysWithoutStringIndex<{ [index: string]: string; foo: string; bar: 'baz' }> | ||
>test : Symbol(test, Decl(inferConditionalConstraintMappedMember.ts, 4, 11)) | ||
>KeysWithoutStringIndex : Symbol(KeysWithoutStringIndex, Decl(inferConditionalConstraintMappedMember.ts, 0, 0)) | ||
>index : Symbol(index, Decl(inferConditionalConstraintMappedMember.ts, 7, 38)) | ||
>foo : Symbol(foo, Decl(inferConditionalConstraintMappedMember.ts, 7, 61)) | ||
>bar : Symbol(bar, Decl(inferConditionalConstraintMappedMember.ts, 7, 74)) | ||
|
||
// KeysWithoutStringIndex<T> will always be a subset of keyof T, but is reported as unassignable | ||
export type RemoveIdxSgn<T> = Pick<T, KeysWithoutStringIndex<T>> | ||
>RemoveIdxSgn : Symbol(RemoveIdxSgn, Decl(inferConditionalConstraintMappedMember.ts, 7, 88)) | ||
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 9, 25)) | ||
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) | ||
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 9, 25)) | ||
>KeysWithoutStringIndex : Symbol(KeysWithoutStringIndex, Decl(inferConditionalConstraintMappedMember.ts, 0, 0)) | ||
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 9, 25)) | ||
|
||
// ERROR: | ||
// Type 'KeysWithoutStringIndex<T>' does not satisfy the constraint 'keyof T'. | ||
// Type 'unknown' is not assignable to type 'keyof T'.(2344) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
=== tests/cases/compiler/inferConditionalConstraintMappedMember.ts === | ||
// Return keyof type without string index signature | ||
type KeysWithoutStringIndex<T> = | ||
>KeysWithoutStringIndex : KeysWithoutStringIndex<T> | ||
|
||
{ [K in keyof T]: string extends K ? never : K } extends { [_ in keyof T]: infer U } | ||
? U | ||
: never | ||
|
||
// Only "foo" | "bar" as expected, [string] index signature removed | ||
type test = KeysWithoutStringIndex<{ [index: string]: string; foo: string; bar: 'baz' }> | ||
>test : never | ||
>index : string | ||
>foo : string | ||
>bar : "baz" | ||
|
||
// KeysWithoutStringIndex<T> will always be a subset of keyof T, but is reported as unassignable | ||
export type RemoveIdxSgn<T> = Pick<T, KeysWithoutStringIndex<T>> | ||
>RemoveIdxSgn : RemoveIdxSgn<T> | ||
|
||
// ERROR: | ||
// Type 'KeysWithoutStringIndex<T>' does not satisfy the constraint 'keyof T'. | ||
// Type 'unknown' is not assignable to type 'keyof T'.(2344) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Return keyof type without string index signature | ||
type KeysWithoutStringIndex<T> = | ||
{ [K in keyof T]: string extends K ? never : K } extends { [_ in keyof T]: infer U } | ||
? U | ||
: never | ||
|
||
// Only "foo" | "bar" as expected, [string] index signature removed | ||
type test = KeysWithoutStringIndex<{ [index: string]: string; foo: string; bar: 'baz' }> | ||
// KeysWithoutStringIndex<T> will always be a subset of keyof T, but is reported as unassignable | ||
export type RemoveIdxSgn<T> = Pick<T, KeysWithoutStringIndex<T>> | ||
// ERROR: | ||
// Type 'KeysWithoutStringIndex<T>' does not satisfy the constraint 'keyof T'. | ||
// Type 'unknown' is not assignable to type 'keyof T'.(2344) |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.