-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Description
π Search Terms
- distributive inference
- distributed function
- as dist
- as distributed
β 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
Add dist
/ distributed
keyword to enable inference in a distributive manner.
Concept 1: Inline as dist
Handle the inference by distributing over all types used by the expression:
const transform = (val: [1, 1] | [2, 2]) => [val[0], val[1]] as dist;
typeof transform; // (val: [1, 1] | [2, 2]) => [1, 1] | [2, 2]
//
const add = (...[a, b]: [number, number] | [bigint, bigint]) => a + b as dist;
typeof add; // (...[a, b]: [number, number] | [bigint, bigint]) => number | bigint
Concept 2: Function-level dist
return type
Allow distribution across an entire function body:
const f = <T extends [number, number] | [bigint, bigint]>(...[a, b]: T): dist => {
const sum = a + b; // number | bigint
const arr = [a, b]; // [number, number] | [bigint, bigint];
return {
sum,
arr
} // {sum: number, arr: [number, number]} | {sum: bigint, arr: [bigint, bigint]}
}
const res = f(3,7); // {sum: number, arr: [number, number]}
const rez = f(3n,7n); // {sum: bigint, arr: [bigint, bigint]}
typeof f; // (<T extends [number, number]>(...[a, b]: T) => { sum: number; arr: [number, number]; }) & (<T extends [bigint, bigint]>(...[a, b]: T) => { sum: bigint; arr: [bigint, bigint]; })
Concept 1 could also be supported on function expressions with equivalent behavior to Concept 2:
const f2 = (<T extends [number, number] | [bigint, bigint]>(...[a, b]: T) => {/*code*/}) as dist; // Equivalent to "f"
Notes
These are early-stage concepts meant to illustrate the core idea. The specific syntax and behaviors should be refined during implementation.
Open questions: Interaction with as const
, generics, nested functions, opt-out mechanisms, etc. will need consideration.
Potential breaking change: Codebases with a type called dist
would conflict, though this would go against TypeScript's PascalCase naming convention for types.
π Motivating Example
Currently, code is inferred in a non-distributive manner:
Example inspired by geon
const transform = (val: [1, 1] | [2, 2]) => [val[0], val[1]] as const;
// Inferred return type: readonly [1 | 2, 1 | 2]
// Expected return type: readonly [1, 1] | readonly [2, 2]
This can be addressed by duplicating your code logic within a distributive context in the typesystem:
const transform = <T extends [1, 1] | [2, 2]>(val: T) => [val[0], val[1]] as T extends unknown
? [T[0], T[1]]
: never
;
const res = transform(undefined! as [1, 1] | [2, 2]); // [1, 1] | [2, 2]
const rez = transform([1, 1]); // [1, 1]
But this is very repetitive and awkward. Besides that, this only works if the automatic inference doesn't cause a conflict to begin with and can be simply translated into the type system:
Example inspired by Emilio Platzer and Mudloop
// @ts-expect-error: Operator '+' cannot be applied to types 'number | bigint' and 'number | bigint'.(2365)
const add = (...[a, b]: [number, number] | [bigint, bigint]) => a + b;
π» Use Cases
- What do you want to use this for?
Preserving precise type relationships when transforming union types, and enabling operations across union branches that are currently rejected by the type checker. - What shortcomings exist with current approaches?
Current workarounds require manually duplicating code logic in the type system using conditional types. This is verbose, error-prone, and fails when TypeScript rejects the runtime-aligned code itself - What workarounds are you using in the meantime?
Manually encoding distributive logic using conditional types