Description
π Search Terms
"reverse mapped types", "intersection"
β Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
I'd like to see reverse mapped types capabilities enhanced to support an intersection constraint, providing users with the ability to enable EPC (Excess Property Checking) on type parameters inference sites.
Reverse mapped types are a powerful tool. Currently TypeScript is able to reverse three kind of mapped types:
- a homomorphic mapped type
{ [P in keyof T]: X }
- a mapped type
{ [P in K]: X }
, whereK
is a type parameter - a mapped type with an union constraint, as long as the union contains one of the constraints of the previous cases
This feature request asks support for a fourth case: a mapped type with an intersection constraint, as long as the intersection contains one of the constraints of the previous cases.
While the union constraint could be seen as a way to force the presence of some properties, the intersection one could be seen as a way to prevent the presence of extra properties.
May be closed by #55811.
π Motivating Example
In this example, taken from #12936, the type parameter U
is the one that gets inferred by reversing the mapped type that has an intersection constraint: keyof U & keyof T
. It should be inferred as { x: 1; y: "y"; z: string }
or, even better, as { x: 1; y: "y"; }
(with z
stripped away because it wouldn't get through the application of the mapped type anyway). Either way, the application of the mapped type on the just inferred U
imposes the absence of extra properties with respect to the ones declared in T = {x: number, y: string}
.
const checkType = <T>() => <U extends T>(value: { [K in keyof U & keyof T]: U[K] }) => value;
const checked = checkType<{x: number, y: string}>()({
x: 1,
y: "y",
z: "z", // <-- Object literal may only specify known properties, and 'z' does not exist in type '{ x: 1; y: "y"; }'.
});
π» Use Cases
-
What do you want to use this for?
It's a way to enable EPC on type parameters when it's really needed. Motivating comment. -
What shortcomings exist with current approaches/workarounds?
One of the main workaround is the use of type functions like the following:
type NoExtra<T> = {
[K in keyof T]: K extends keyof MyRefType ? T[K] : never
}
But then you get error messages like Type 'number' is not assignable to type 'never'
that are bad for DX, expecially if compared with EPC ones.
What's good about this proposal, and the related PR, is the fact that it lets the user enable EPC on type parameters without fundamentally change how TS treats type parameters inference. It's just a nice side effect of enhancing reverse mapped types' capabilities.