TypeScript Version: 3.5.0-dev.20190407 and 3.4 in TS playground
Search Terms: infer rest tuple
Code
type Action<T extends string = string> = { type: T };
declare function f<A extends Action>(...args: A[]): A[];
declare function g<A extends Action>(args: A[]): A[];
const A = "A";
const B = "B";
const C = "C";
declare const a: Action<typeof A>;
declare const b: Action<typeof B>;
declare const c: Action<typeof C>;
// Here b effects an error:
const r1 = f(a, b, c);
// Here r2 is inferred correctly:
const r2 = f(
{ type: A },
{ type: B },
{ type: C }
);
// Here r3 is inferred correctly:
const r3 = f(...[a, b, c]);
// Here r4 is inferred correctly:
const r4 = g([a, b, c]);
// Here r5 is inferred correctly:
const r5 = g([
{ type: A },
{ type: B },
{ type: C }
]);
Expected behavior:
In all of the calls to f and g in the example above, I would have expected no errors and the result to be inferred correctly.
Actual behavior:
Arguments passed to the function that accepts a rest parameter - f - work only if said arguments are literals or are spread from an array/tuple. If separate, declared variables are passed, an error is effected, stating that the arguments are expected to be of the same type.
Both variables and literals behave as expected with g - a function that accepts an array parameter, rather than a rest parameter.
Playground Link:
Related Issues: None found
This is an intentional difference.
Consider this code:
type Action<T extends string = string> = { type: T };
declare const a: Action<"A">, b: Action<"B">, c: Action<"C">;
declare function f<T extends Action>(...args: T[]): T[];
declare function g<T extends Action>(args: T[]): T[];
declare function h<T>(a: T, b: T, c: T): T[];
g([a, b, c]);
f(a, b, c); // ?
h(a, b, c);
g has to be OK because heterogeneous array literals are fine.
h should clearly be an error - otherwise generics don't mean anything (consider a function like insert<T>(arr: T[], el: T): void and calling insert([1, 2, 3], "4") - it'd be a disaster to infer T = string|number).
The question is whether it's more correct/consistent for f to behave like g or like h -- certainly you can have your own interpretation, but we chose that a rest arg array should behave more like an infinite set of formally-declared parameters than an array literal passed inline.
Thanks. BTW, the workaround appears to be this.
type Action<T extends string = string> = { type: T };
declare function f<A extends Action, R extends A[]>(...args: R): R;
const A = "A";
const B = "B";
const C = "C";
declare const a: Action<typeof A>;
declare const b: Action<typeof B>;
declare const c: Action<typeof C>;
const r1 = f(a, b, c);
The above workaround can be simplified; the A extends Action generic in the other workaround is unused, as A is always inferred to be of type Action<string>. Only the R generic is usefully inferred, so the function signature can be reduced to:
// ...
declare function f<R extends Action[]>(...args: R): R;
// ...
Most helpful comment
Thanks. BTW, the workaround appears to be this.