-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
When trying to use mapped tuples as rest parameters error 'A rest parameter must be of an array type' given #29919
Comments
Having the same problem - prepared a simpler repro (with artificial code ofc). Real world use case would be to cover reselect's createSelector API in generic manner - https://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc |
I also get this issues here:
|
I'd be interested to hear why this is marked as a suggestion rather than a bug. As far as I see it you can usually use a tuple type as a function parameter but in this scenario (and other similarly complicated scenarios) it doesn't work. Can anyone explain why it doesn't work in this case? I have tested my mapped type with function parameters and on it's own it works fine as a rest param:
|
Problem still exists in Typescript 3.7.2. Looks like a bug. Simple reproduction: Playground If you mouse over
While this is compatible in many ways, it is ultimately not a tuple anymore, so when you try to use it as a rest parameter it complains as such. |
@OxleyS Actually the original problem was solved, if you go through a generic mapping type the result will be a tuple: type P = [string, number];
type M<T> = { [K in keyof T]: T[K] };
const f1 = (...params: P) => params[0]; // OK
const f2 = (...params: M<P>) => params[0]; // OK Not sure why mapping directly over a tuple is not supported though. |
I was about to raise a new issue, but it sounds like the same issue as described in the last comments? Playground // For some reason a separate helper works as expected, remapping just the tuple items.
type MapParamsHelper<A> = { [K in keyof A]: string };
type MapParams<F extends (...args: number[]) => void> = MapParamsHelper<Parameters<F>>;
let remap: MapParams<(a: number, b: number) => void> = ['a', 'b']; // OK
let x: number = remap.length; // OK
// But inlining same type breaks and iterates over all keys including Array.prototype methods:
type MapParams2<F extends (...args: number[]) => void> = { [K in keyof Parameters<F>]: string };
let remap2: MapParams2<(a: number, b: number) => void> = ['a', 'b']; // fails, because this is now an object and not a tuple
let y: number = remap2.length; // fails, because `length` was remapped to `string` here @RyanCavanaugh this is marked as a "suggestion" but I think it's actually a bug. |
I don't think it's really solved, I think it's just inconsistent in its behavior. Here's another example that uses a generic mapping type on a tuple and gives the same error: Playground I think the common ground between all the examples that people have posted as not working is the use of a type such as |
@OxleyS I don't think that's the case; at least it doesn't explain why a workaround with an intermediate generic type works (see my example). |
Same error when using builtin function foo<F extends (...args: any[]) => any>(fn: F, ...args: Partial<Parameters<F>>) { } Error is there, though actual type is resolved properly. Playground |
Here is another failure type ProxyParameters<T extends (...args: any) => any> = {
[K in keyof Parameters<T>]: Parameters<T>[K]
}
const f1 = (foo: string): string => foo
// Works fine:
const f2 = (...args: Parameters<typeof f1>): string => args[0]
// Error:
const f3 = (...args: ProxyParameters<typeof f1>): string => args[0] |
The issue still exists on typescript 4.5.4. In my case applying generics directly on inferred parameters instead of type PromisifyTuple<T extends readonly unknown[] | []> = { [P in keyof T]: Promise<T[P]> }
// it's really an tuple
type test = PromisifyTuple<[string, number, {}, Map<string, number>]>
// will raise ts2370
type Transform1<T extends (...args: any[]) => any> = (...args: PromisifyTuple<Parameters<T>>) => ReturnType<T>
// ok if PromisifyTuple is immediately applied on inferred parameters
type PromisifiedArguments<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? PromisifyTuple<P> : never;
type Transform2<T extends (...args: any[]) => any> = (...args: PromisifiedArguments<T>) => ReturnType<T>
// a is assignable to b and b is assignable to a, they computes to the same type
type TestFunction = (a: string, b: number) => object
let a: PromisifiedArguments<TestFunction> | undefined
let b: PromisifyTuple<Parameters<TestFunction>> | undefined
a = b;
b = a; |
Same issue here... Mapped tuple type on type MapTupleToObject<T extends any[]> = {[P in keyof T]: {value: T[P]}};
const testOne = <T extends any[]>(tuple: MapTupleToObject<T>) => {
// WORKS!
const a: any[] = tuple;
}
const testTwo = <T extends new (...args: any[]) => any>(tuple: MapTupleToObject<ConstructorParameters<T>>) => {
// DOESN'T WORKS!
const a: any[] = tuple;
}
const testThree = <T extends new (...args: any[]) => any>(tuple: ConstructorParameters<T>) => {
// WORKS!
const a: any[] = tuple;
} |
Same issue when trying to spread a mapped tuple into another array type:
However it does work by passing it through a Definitely seems like an inconsistency that could be fixed. |
Use-case Not sure if it helps, but I can provide an explicit use-case for this functionality: mapping an array passed to a function. Currently, due to this not existing, the only workaround to map arrays passed to functions is to make like 20 overloads. type Cast<U> = { value: U };
function Y<U1> (args: [U1]): [Cast<U1>];
function Y<U1, U2> (args: [U1, U2])]: [Cast<U1>, Cast<U2>];
function Y<U1, U2, U3> (args: [U1, U2, U3]): [Cast<U1>, Cast<U2>, Cast<U3>];
function Y<U1, U2, U3, U4> (args: [U1, U2, U3, U4]): [Cast<U1>, Cast<U2>, Cast<U3>, Cast<U4>];
function Y<U1, U2, U3, U4, U5> (args: [U1, U2, U3, U4, U5]): [Cast<U1>, Cast<U2>, Cast<U3>, Cast<U4>, Cast<U5>];
function Y (args: unknown[]): Cast<unknown>[] {
return args.map(x => ({ value: x }));
} This is exactly the hack I made in TypeDI to type-check service dependencies. The obvious problem is that it's... well, it's horrendous. Sooooo I reverted it, but I'm thinking of undoing that revert as this issue doesn't seem to be gaining any attention. Once this is implemented, I could simplify the above to... type Cast<U> = { value: U };
type CastMany<UArgs extends any[]> = {
[key in keyof UArgs]: Cast<UArgs[key]>;
}
function Y<UArgs extends any[]> (args: UArgs): CastMany<UArgs> {
return args.map(x => ({ value: x }));
} |
FYI: This seems to have been secretly fixed in the current version of TypeScript. |
I bisected this particular change that @freshgum-bubbles mentioned to this diff and further down to my own PR: #49947 ...and now I realized that this PR was referencing this exact test case 😅 it's just that it didn't fix this issue as a whole |
I'm also running into this problem, and here's an example of just how broken this currently is: Works fine: type Rec = Record<string, string[]>;
type M<T> = { [K in keyof T]: T[K] };
function foo<E extends Rec, Key extends keyof Rec>(
e: E,
key: Key,
...args: M<E[Key]>
) {} Now add a constraints to type Rec = Record<string, string[]>;
type M<T extends string[]> = { [K in keyof T]: T[K] };
function foo<E extends Rec, Key extends keyof Rec>(
e: E,
key: Key,
...args: M<E[Key]> // A rest parameter must be of an array type
) {} even though it would seem it's even more obvious that the mapped type is an array in this second version! |
I also encountered this issue. type MyGeneric<T> = { value: T };
type WrapInMyGeneric<T extends any[]> = {
[P in keyof T]: MyGeneric<T[P]>;
};
// ✅ Use a tuple
declare function f1(...args: WrapInMyGeneric<[string, number]>): void;
// ✅ Use any array
declare function f2(...args: WrapInMyGeneric<any[]>): void;
// ✅ Use the parameters of a function
declare function f3(...args: WrapInMyGeneric<Parameters<(a: string, b: number) => void>>): void;
// ❌ Use the parameters of a generic function
declare function f4<F extends (...args: any[]) => any>(...args: WrapInMyGeneric<Parameters<F>>): void; The funny thing is that if I replace my generic type with the type itself, it works: type MyGeneric<T> = T; |
TypeScript Version: 3.2
Search Terms: mapped tuples rest
Code
Expected behavior:
I should be able to use a mapped tuple as a rest param in a function
Actual behavior:
I get the error
A rest parameter must be of an array type.
Playground Link:
Link
The text was updated successfully, but these errors were encountered: