Skip to content

Commit 8ea4ec9

Browse files
authored
Add infer T constraint inference rule matching up mapped type templates across check/extends types (microsoft#43649)
1 parent a433c3c commit 8ea4ec9

File tree

5 files changed

+110
-0
lines changed

5 files changed

+110
-0
lines changed

src/compiler/checker.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12613,6 +12613,19 @@ namespace ts {
1261312613
else if (grandParent.kind === SyntaxKind.TypeParameter && grandParent.parent.kind === SyntaxKind.MappedType) {
1261412614
inferences = append(inferences, keyofConstraintType);
1261512615
}
12616+
// When an 'infer T' declaration is the template of a mapped type, and that mapped type if the extends
12617+
// clause of a conditional whose check type is also a mapped type, give it the constraint of the template
12618+
// of the check type's mapped type
12619+
else if (grandParent.kind === SyntaxKind.MappedType && (grandParent as MappedTypeNode).type &&
12620+
skipParentheses((grandParent as MappedTypeNode).type!) === declaration.parent && grandParent.parent.kind === SyntaxKind.ConditionalType &&
12621+
(grandParent.parent as ConditionalTypeNode).extendsType === grandParent && (grandParent.parent as ConditionalTypeNode).checkType.kind === SyntaxKind.MappedType &&
12622+
((grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode).type) {
12623+
const checkMappedType = (grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode;
12624+
const nodeType = getTypeFromTypeNode(checkMappedType.type!);
12625+
inferences = append(inferences, instantiateType(nodeType,
12626+
makeUnaryTypeMapper(getDeclaredTypeOfTypeParameter(getSymbolOfNode(checkMappedType.typeParameter)), checkMappedType.typeParameter.constraint ? getTypeFromTypeNode(checkMappedType.typeParameter.constraint) : keyofConstraintType)
12627+
));
12628+
}
1261612629
}
1261712630
}
1261812631
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [inferConditionalConstraintMappedMember.ts]
2+
// Return keyof type without string index signature
3+
type KeysWithoutStringIndex<T> =
4+
{ [K in keyof T]: string extends K ? never : K } extends { [_ in keyof T]: infer U }
5+
? U
6+
: never
7+
8+
// Only "foo" | "bar" as expected, [string] index signature removed
9+
type test = KeysWithoutStringIndex<{ [index: string]: string; foo: string; bar: 'baz' }>
10+
// KeysWithoutStringIndex<T> will always be a subset of keyof T, but is reported as unassignable
11+
export type RemoveIdxSgn<T> = Pick<T, KeysWithoutStringIndex<T>>
12+
// ERROR:
13+
// Type 'KeysWithoutStringIndex<T>' does not satisfy the constraint 'keyof T'.
14+
// Type 'unknown' is not assignable to type 'keyof T'.(2344)
15+
16+
//// [inferConditionalConstraintMappedMember.js]
17+
"use strict";
18+
exports.__esModule = true;
19+
// ERROR:
20+
// Type 'KeysWithoutStringIndex<T>' does not satisfy the constraint 'keyof T'.
21+
// Type 'unknown' is not assignable to type 'keyof T'.(2344)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
=== tests/cases/compiler/inferConditionalConstraintMappedMember.ts ===
2+
// Return keyof type without string index signature
3+
type KeysWithoutStringIndex<T> =
4+
>KeysWithoutStringIndex : Symbol(KeysWithoutStringIndex, Decl(inferConditionalConstraintMappedMember.ts, 0, 0))
5+
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 1, 28))
6+
7+
{ [K in keyof T]: string extends K ? never : K } extends { [_ in keyof T]: infer U }
8+
>K : Symbol(K, Decl(inferConditionalConstraintMappedMember.ts, 2, 7))
9+
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 1, 28))
10+
>K : Symbol(K, Decl(inferConditionalConstraintMappedMember.ts, 2, 7))
11+
>K : Symbol(K, Decl(inferConditionalConstraintMappedMember.ts, 2, 7))
12+
>_ : Symbol(_, Decl(inferConditionalConstraintMappedMember.ts, 2, 64))
13+
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 1, 28))
14+
>U : Symbol(U, Decl(inferConditionalConstraintMappedMember.ts, 2, 84))
15+
16+
? U
17+
>U : Symbol(U, Decl(inferConditionalConstraintMappedMember.ts, 2, 84))
18+
19+
: never
20+
21+
// Only "foo" | "bar" as expected, [string] index signature removed
22+
type test = KeysWithoutStringIndex<{ [index: string]: string; foo: string; bar: 'baz' }>
23+
>test : Symbol(test, Decl(inferConditionalConstraintMappedMember.ts, 4, 11))
24+
>KeysWithoutStringIndex : Symbol(KeysWithoutStringIndex, Decl(inferConditionalConstraintMappedMember.ts, 0, 0))
25+
>index : Symbol(index, Decl(inferConditionalConstraintMappedMember.ts, 7, 38))
26+
>foo : Symbol(foo, Decl(inferConditionalConstraintMappedMember.ts, 7, 61))
27+
>bar : Symbol(bar, Decl(inferConditionalConstraintMappedMember.ts, 7, 74))
28+
29+
// KeysWithoutStringIndex<T> will always be a subset of keyof T, but is reported as unassignable
30+
export type RemoveIdxSgn<T> = Pick<T, KeysWithoutStringIndex<T>>
31+
>RemoveIdxSgn : Symbol(RemoveIdxSgn, Decl(inferConditionalConstraintMappedMember.ts, 7, 88))
32+
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 9, 25))
33+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
34+
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 9, 25))
35+
>KeysWithoutStringIndex : Symbol(KeysWithoutStringIndex, Decl(inferConditionalConstraintMappedMember.ts, 0, 0))
36+
>T : Symbol(T, Decl(inferConditionalConstraintMappedMember.ts, 9, 25))
37+
38+
// ERROR:
39+
// Type 'KeysWithoutStringIndex<T>' does not satisfy the constraint 'keyof T'.
40+
// Type 'unknown' is not assignable to type 'keyof T'.(2344)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=== tests/cases/compiler/inferConditionalConstraintMappedMember.ts ===
2+
// Return keyof type without string index signature
3+
type KeysWithoutStringIndex<T> =
4+
>KeysWithoutStringIndex : KeysWithoutStringIndex<T>
5+
6+
{ [K in keyof T]: string extends K ? never : K } extends { [_ in keyof T]: infer U }
7+
? U
8+
: never
9+
10+
// Only "foo" | "bar" as expected, [string] index signature removed
11+
type test = KeysWithoutStringIndex<{ [index: string]: string; foo: string; bar: 'baz' }>
12+
>test : never
13+
>index : string
14+
>foo : string
15+
>bar : "baz"
16+
17+
// KeysWithoutStringIndex<T> will always be a subset of keyof T, but is reported as unassignable
18+
export type RemoveIdxSgn<T> = Pick<T, KeysWithoutStringIndex<T>>
19+
>RemoveIdxSgn : RemoveIdxSgn<T>
20+
21+
// ERROR:
22+
// Type 'KeysWithoutStringIndex<T>' does not satisfy the constraint 'keyof T'.
23+
// Type 'unknown' is not assignable to type 'keyof T'.(2344)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Return keyof type without string index signature
2+
type KeysWithoutStringIndex<T> =
3+
{ [K in keyof T]: string extends K ? never : K } extends { [_ in keyof T]: infer U }
4+
? U
5+
: never
6+
7+
// Only "foo" | "bar" as expected, [string] index signature removed
8+
type test = KeysWithoutStringIndex<{ [index: string]: string; foo: string; bar: 'baz' }>
9+
// KeysWithoutStringIndex<T> will always be a subset of keyof T, but is reported as unassignable
10+
export type RemoveIdxSgn<T> = Pick<T, KeysWithoutStringIndex<T>>
11+
// ERROR:
12+
// Type 'KeysWithoutStringIndex<T>' does not satisfy the constraint 'keyof T'.
13+
// Type 'unknown' is not assignable to type 'keyof T'.(2344)

0 commit comments

Comments
 (0)