-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # 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: