diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 588b5010b14..155f4d6c657 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12613,6 +12613,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 + 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) + )); + } } } } diff --git a/tests/baselines/reference/inferConditionalConstraintMappedMember.js b/tests/baselines/reference/inferConditionalConstraintMappedMember.js new file mode 100644 index 00000000000..8beb9791d26 --- /dev/null +++ b/tests/baselines/reference/inferConditionalConstraintMappedMember.js @@ -0,0 +1,21 @@ +//// [inferConditionalConstraintMappedMember.ts] +// Return keyof type without string index signature +type KeysWithoutStringIndex = + { [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 will always be a subset of keyof T, but is reported as unassignable +export type RemoveIdxSgn = Pick> + // ERROR: + // Type 'KeysWithoutStringIndex' 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' does not satisfy the constraint 'keyof T'. +// Type 'unknown' is not assignable to type 'keyof T'.(2344) diff --git a/tests/baselines/reference/inferConditionalConstraintMappedMember.symbols b/tests/baselines/reference/inferConditionalConstraintMappedMember.symbols new file mode 100644 index 00000000000..eb3385eb8a8 --- /dev/null +++ b/tests/baselines/reference/inferConditionalConstraintMappedMember.symbols @@ -0,0 +1,40 @@ +=== tests/cases/compiler/inferConditionalConstraintMappedMember.ts === +// Return keyof type without string index signature +type KeysWithoutStringIndex = +>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 will always be a subset of keyof T, but is reported as unassignable +export type RemoveIdxSgn = Pick> +>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' does not satisfy the constraint 'keyof T'. + // Type 'unknown' is not assignable to type 'keyof T'.(2344) diff --git a/tests/baselines/reference/inferConditionalConstraintMappedMember.types b/tests/baselines/reference/inferConditionalConstraintMappedMember.types new file mode 100644 index 00000000000..7732a8658a1 --- /dev/null +++ b/tests/baselines/reference/inferConditionalConstraintMappedMember.types @@ -0,0 +1,23 @@ +=== tests/cases/compiler/inferConditionalConstraintMappedMember.ts === +// Return keyof type without string index signature +type KeysWithoutStringIndex = +>KeysWithoutStringIndex : KeysWithoutStringIndex + + { [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 will always be a subset of keyof T, but is reported as unassignable +export type RemoveIdxSgn = Pick> +>RemoveIdxSgn : RemoveIdxSgn + + // ERROR: + // Type 'KeysWithoutStringIndex' does not satisfy the constraint 'keyof T'. + // Type 'unknown' is not assignable to type 'keyof T'.(2344) diff --git a/tests/cases/compiler/inferConditionalConstraintMappedMember.ts b/tests/cases/compiler/inferConditionalConstraintMappedMember.ts new file mode 100644 index 00000000000..5e95a0e439f --- /dev/null +++ b/tests/cases/compiler/inferConditionalConstraintMappedMember.ts @@ -0,0 +1,13 @@ +// Return keyof type without string index signature +type KeysWithoutStringIndex = + { [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 will always be a subset of keyof T, but is reported as unassignable +export type RemoveIdxSgn = Pick> + // ERROR: + // Type 'KeysWithoutStringIndex' does not satisfy the constraint 'keyof T'. + // Type 'unknown' is not assignable to type 'keyof T'.(2344) \ No newline at end of file