Description
openedon Jul 16, 2024
π Search Terms
Conditional type inference narrow
β 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 isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
A conditional type should be narrowed when possible.
β
When calling foo
in the example below, types are as expected - We always require the x
property to be defined in the object payload, and when we specify mode Y
, we also require the y
property to be defined
Within the foo
function In the example below, the point
type is completely useless
π΄ I expect the point type to be narrowed to Pick<TPoint, 'x' | 'y'> = { x: number; y: number }
when we know that mode: 'Y'
, but I get a type error when I try to access any property of the object:
Property 'y' does not exist on type 'Pick<TPoint, TMode extends "Y" ? "x" | "y" : "x">'.(2339)
π΄ I expect x
to always be accessible from the point, but I get a type error when I try to access the property of the object:
Property 'x' does not exist on type 'Pick<TPoint, TMode extends "Y" ? "x" | "y" : "x">'.(2339)
π Motivating Example
We would now be able to write functions where two or more parameters depend on each other:
type TPoint = { x: number; y: number; z: number };
const foo = <TMode extends 'X' | 'Y'>(
mode: TMode,
point: Pick<TPoint, TMode extends 'Y' ? 'x' | 'y' : 'x'>,
) => {
if (mode === 'Y') {
return point.x + point.y;
}
return point.x;
};
foo('X', { x: 1 });
// @ts-expect-error: Object literal may only specify known properties, and 'y' does not exist in type 'Pick<TPoint, "x">'.(2353)
foo('X', { x: 1, y: 2 });
// @ts-expect-error: Property 'y' is missing in type '{ x: number; }' but required in type 'Pick<TPoint, "x" | "y">'.(2345)
foo('Y', { x: 1 });
foo('Y', { x: 1, y: 2 });
π» Use Cases
-
What do you want to use this for?
When I have a function where the type of two parameters depend on each other. -
What shortcomings exist with current approaches?
Using a combination of generics and conditional types, we are able to give the function a very nice type signature from the outside, but within the body of the function, the types are practically unusable. -
What workarounds are you using in the meantime?
// @ts-ignore
everywhere
Alternatively wrap the parameters of the function in a single object payload