Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent inference from a rest parameter #30824

Closed
cartant opened this issue Apr 9, 2019 · 3 comments
Closed

Inconsistent inference from a rest parameter #30824

cartant opened this issue Apr 9, 2019 · 3 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@cartant
Copy link

cartant commented Apr 9, 2019

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:

Here is the playground link

Related Issues: None found

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Apr 9, 2019
@RyanCavanaugh
Copy link
Member

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.

@cartant
Copy link
Author

cartant commented Apr 10, 2019

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);

@tjjfvi
Copy link
Contributor

tjjfvi commented Jun 25, 2020

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;
// ...

Playground Link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants