-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Incorrent return type inference when using infer with generic function #48820
Comments
Probably not the level of generality you were hoping for but -
|
@craigphicks thanks. But I don't think it can help me. I want to create map function for tuples type ApplyFn<Fn extends (item: any) => any, T> =
Fn extends (item: T) => infer R ? R : never
type Mapper<Tuple extends ReadonlyArray<any>, Fn extends (item: any) => any> = {
[Index in keyof Tuple]: ApplyFn<Fn, Tuple[Index]>
}
function tupleMap<T extends ReadonlyArray<any>,
Fn extends (item: any) => any>(a: T, fn: Fn): Mapper<T, Fn> {
return a.map(fn) as any
} |
function tupleMap<T extends unknown, F extends (item: T) => any>(
tuple: ReadonlyArray<T>,
f: F
): ReadonlyArray<ReturnType<F>> {
return tuple.map(f);
}
const map = tupleMap([1, 2, 3], (n) => ({ foo: n }));
const constMap = tupleMap([1, 2, 3] as const, (n) => ({ foo: n })); |
@harry0000 thanks, but I want it to work like this const tuple = ["foo", "bar"] as const
const convert = <T>(x: T): {item: T} => ({item: x})
const convertedTuple: [{item: "foo"}, {item: "bar"}] = tupleMap(tuple, convert) |
@user753 I recently answered the exact same question on Reddit, and my conclusion is no different than the answer to that question.
const tuple = ["foo", "bar"] as const;
type TupleMap<T extends ReadonlyArray<unknown>, R extends Array<unknown> = []> =
T extends readonly [infer TH, ...infer TT]
? TupleMap<TT, TH extends string | number ? [...R, { item: `${TH}` }] : R>
: R
type FooBar = TupleMap<typeof tuple>
// [{item: "foo"}, {item: "bar"}]
const convertedTuple: FooBar = [
{ item: tuple[0] },
{ item: tuple[1] }
];
const dont: FooBarItem = tuple.map(s => ({ item: s }));
// Type '{ item: "foo" | "bar"; }[]' is not assignable to type '[{ item: "foo"; }, { item: "bar"; }]'.
// Target requires 2 element(s) but source may have fewer.(2322) And TS does not have Higher Kinded Types, you cannot write as below: type Item<T extends string | number> = { item: `${T}` }
type TupleMap<T extends ReadonlyArray<unknown>, C extends Convert<_ extends string | number>, R extends Array<any> = []> =
T extends readonly [infer TH, ...infer TT]
? TupleMap<TT, TH extends string | number ? [...R, C<TH>] : R>
: R
type FooBarItem = TupleMap<typeof tuple, Item> type Fn<T extends string | number> = (item: T) => any
type ItemMapper<T extends string | number> = (item: T) => { item: `${T}` }
type TupleMap<T extends ReadonlyArray<unknown>, F extends Fn, R extends Array<any> = []> =
T extends readonly [infer TH, ...infer TT]
? TupleMap<TT, TH extends string | number ? [...R, ReturnType<F<TH>>] : R>
: R
type MappedFooBar = TupleMap<typeof tuple, ItemMapper> |
@harry0000 Could you explain why we need Higher Kinded Types in this case? type ApplyFn<Fn extends (item: any) => any, T> =
Fn extends (item: T) => infer R ? R : never
type Fn = <T>(item: T) => {foo: T}
type Y = ApplyFn<Fn, string> The compiler knows that |
This definition of https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints declare const f: Fn;
f();
// Expected 1 arguments, but got 0.(2554)
// const f: <unknown>(item: unknown) => {
// foo: unknown;
// }
type P = Parameters<Fn> // type P = [item: unknown]
type R = ReturnType<Fn> // type R = { foo: unknown; } What you are actually trying to do is pass type Fn = <T>(item: T) => { foo: T }
// Since Fn can be executed even if a string type is passed, this "inheritance relationship" is valid.
type Y = Fn extends (item: string) => infer R ? R : never;
// type Y = { foo: unknown; } Since the inheritance relation check only checks whether the inheritance relation is satisfied, in order to determine type Fn<T> = (item: T) => { foo: T } Edit: Why did you mistakenly think that you could pass any type to the type parameter of a generic function by checking the inheritance relationship with |
@harry0000 Thanks. Now I get it. The
And it is possible in some way to solve type ApplyFn<Fn extends (item: A) => R, T, A = any, R = any> =
Fn extends (item: T) => infer R ? R : never
type Y = ApplyFn<Fn<string>, string> Do you know by any chance if it is possible to write
I thought |
Unfortunately, no. If you use fp-ts, you can simply write the following, but as I mentioned before, the type information of the tuple order will be lost, so you will have to cast it with import * as ROA from "fp-ts/lib/ReadonlyArray";
const tuple = ["foo", "bar", "buz"] as const;
const identity = <T>(i: T) => i;
const ids = ROA.map(identity)(tuple);
// const ids: readonly ("foo" | "bar" | "buz")[]
const itemMapper = <T>(item: T) => ({ item });
const items = ROA.map(itemMapper)(tuple);
// const items: readonly {item: "foo" | "bar" | "buz"}[] Edit: Maybe I should have written this workaround from the beginning? Generic functions delay determining the type until the argument is passed, and currying can further delay that determination. const map: <A, B>(f: (a: A) => B) => (fa: ReadonlyArray<A>) => ReadonlyArray<B> = (f) => {
return (fa) => fa.map(f);
}
const items = map(itemMapper)(tuple);
// const items: readonly {item: "foo" | "bar" | "buz"}[]
const m = map(itemMapper);
// const m: <T>(fa: readonly T[]) => readonly { item: T; }[]
type P = Parameters<typeof m> // type P = [fa: readonly unknown[]]
type R = ReturnType<typeof m> // type R = readonly { item: unknown; }[] However, if no arguments have been passed yet (the type has not yet been determined: including references in the type definition), it will be |
Bug Report
type X is
string
type Y is
{foo: unknown}
but I have expected{foo: string}
🔎 Search Terms
infer generic function return type
⏯ Playground Link
Playground link with relevant code
Is there any workaround to solve this problem?
The text was updated successfully, but these errors were encountered: