-
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
Allow binding generic functions to a given type #37181
Comments
Grab a big coffee and read #6606 for some history on this. One nit is that |
Thanks for the pointer. The linked proposal was rejected because it was too complex, and many use cases it was originally thought for can now be solved using other features like indexed and conditional types. It's still not possible to infer the type of all possible expressions. This proposal would solve the former, but not the latter. I don't know the code base, but I imagine this proposal would be relatively easy to implement, at least when compared to #6606.
When I wrote To elaborate, let's reconsider the
I guess what I'm trying to say is that As I said, this is really just taking what already happens in calls like
That's another interesting possibility I haven't thought about. It relies on type-level function calls being implemented, and uses typeof to lift the resulting expression to the type level. The advantage of such a type-call syntax would be that you don't need On the other hand, you have to provide types for all arguments even if the return type doesn't depend on them. Contrived example: async function boxAllWhenAlso<T>(vals: Promise<T[]>, promise: Promise<any>) {
await promise;
return (await vals).map(box);
} To access the inferred return type
When overload resolution is out of play, I would like to come back to I understand that
|
type ReturnTypeMapped<T, U> = T extends (value: U) => infer V ? V : never;
type Test2 = ReturnTypeMapped<<T>(value: T) => { value: T }, string>; // type Test2 = `type Test2 = { value: unknown; }` This is a Am I correct in understanding that if the above sample works, we probably wouldn't even need extra syntax as it gives us that extra step to grab a return type given an input? |
Sorry for the double email, but I'd like to add another sample with expected behavior type ReturnTypeMapped<T, U> = T extends (value: U) => infer V ? V : never;
type Lambda = <T>(value: T) => { value: T };
type Test1<T> = ReturnTypeMapped<Lambda, T>;
// Expected: `type Test1<T> = ReturnTypeMapped<<T>(value: T) => { value: T }, T>;`
// In other words, it doesn't try to collapse the type yet here.
type Test2 = ReturnTypeMapped<Lambda, string>;
// Expected: `type Test2 = { value: string; }`
type Test3 = Test1<string>
// Expected: `type Test2 = { value: string; }` |
so where your example works for return type @SimonMeskens i do not thing it works for other types of infer whereas I do believe the proposed solution would fix my request which is to use the returned type from Essentially, the goal imo would be to have: function box<T>(value: T) {
return { value };
} typeof box = <T>(value: T) => { value: T } then we can derive a new type by using typeof box<string> = <string>(value: string) => { value: string } or in my more complex example: type ForgotPasswordData = {
readonly query: 'forgot';
};
type ConfirmEmailData = {
readonly query: 'confirm';
code: string;
}
const presets = {
forgotPassword(data: ForgotPasswordData): any { },
confirmEmail(data: ConfirmEmailData): any { }
} as const
type Presets = typeof presets
declare function sendEmailTemplate<P extends keyof Presets>(
preset: P,
data: Parameters<Presets[P]>[0],
): Promise<ReturnType<Presets[P]>>
type GetDataTypeForPreset<
P extends Parameters<typeof sendEmailTemplate>[0],
F = (typeof sendEmailTemplate)<P>
> = F extends (preset: P, data: infer R) => any ? R : never; means that we can tell Typescript what the generic will be for the function so it can tell us what another argument (or the return type) will be in turn |
You seem to be asking for a completely different thing. I suggest opening a new issue. |
Not sure how it is any different? The only difference is the desire to be able to use it to infer other values which may have their values determined by the generic - but it seems it would be the same feature which allows for both unless im completely misunderstanding. In my case my function essentially turns into: declare function sendEmailTemplate<'forgotPassword'>(
preset: 'forgotPassword',
data: ForgotPasswordData,
): Promise<any>
declare function sendEmailTemplate<'confirmEmail'>(
preset: 'confirmEmail',
data: ConfirmEmailData,
): Promise<any> So using type GetDataTypeForPreset<
P extends Parameters<typeof sendEmailTemplate>[0],
F = typeof sendEmailTemplate<P>
> = F extends (preset: any, data: infer R) => any ? R : never;
type ConfirmDataType = GetDataTypeForPreset<'confirmEmail'>
// ===
type GetDataTypeForPreset<
P = 'confirmEmail'
F = <'confirmEmail'>(
preset: 'confirmEmail',
data: ConfirmEmailData,
): Promise<any>
> = F extends (preset: any, data: infer R) => any ? R : never; |
I'm not entirely sure what you are proposing, since you seem to keep changing your posts. |
Just added a drill down didn't change any of the actual content |
Encountered this issue while trying to solve a problem concerning type A<T> = (value: Promise<T>) => {value: T};
type B = <T>(value: Promise<T>) => {value: T}; Currently Most use cases i got into are: export function createSomething(paramOne: Promise<string>, paramsTwo: Promise<boolean>) {
// Return some big complex object i dont want to create an interface for...
}
type Something = ReturnType<typeof createSomething>; // Works great!
export function createSomething2<T extends string>(paramOne: Promise<T>, paramsTwo: Promise<boolean>) {
// Return some big complex object i dont want to create an interface for using T...
}
type Something2 = ReturnType<typeof createSomething2>; // Doesnt work :(
// What i actually need
type Something2<T extends string> = ReturnType<(typeof createSomething2)(T)>; |
function x<T>(a: T): {x: T} {}
type y = typeof x
// Today: type y = <T>(a: T) => void
// Proposed: type y<T> = (a: T) => void
type z<T> = typeof x
// Today: type z<T> = <T>(a: T) => void
// Proposed: no change The generics "moved" from the right side to the left side (if possible, aka no overload). type w = ReturnType<y<number>>
|
@Jack-Works I think your approach isn't quite working. Your Also, while With my proposal implemented, you could achieve the wanted behavior as follows: type y = typeof x
// Today: type y = <T>(a: T) => void
// Proposed: no change
type z<T> = typeof x<T>
// Today: syntax error
// Proposed: type y<T> = (a: T) => void Note that the function type of |
@bradennapier You're right, you show how this feature would be useful for infering parameter types as well, not only return types. This is another key difference between this proposal and the |
Any news concerning this proposal? |
@RyanCavanaugh I read most of #6606, |
I just created a (duplicate) ticket where I proposed the alternate syntax of Also noted that this could enable us to define/apply the generic as well. e.g.
This would make F strictly typed to "hello". An interesting additional benefit of this is that we could partially apply the generics for a kind of "generic type-currying" |
I am really waiting for this... A big limitation in my project |
I was trying to find a way to connect generic type with parent type, but with no luck type Callable<R, G> = <A extends G>(...args: any[]) => R;
type GenericReturnType<G, F> = F extends Callable<infer A, infer G> ? A : never; It's still resolved to Leaving here for other folks |
Just wanted to chime in and add a use case that my team ran into. When we compose React components together we infer component props so that we don't have to import them and/or change them whenever component APIs change. For example: type Props = ComponentProps<typeof 'button'>
function Button(props: Props) {
return <button {...props} />
} In this case, // Component1.tsx
type Component1Props<T> = {
value: T
}
function Component1<T>(props: Component1Props<T>) {
// implementation
}
// Component2.tsx
import Component1 from './path/to/Component1'
type Component2Props<T> = ComponentProps<typeof Component1> // uhhh, where do we put `T`?
function Component2<T>(props: Component2Props<T>) {
return <Component1 {...props} />
} This example might seem contrived because I've tried to keep it generic and simple, but in practice this happens with form components that are generic on their props. (i.e., there's So ideally we'd be able to do this: // Component2.tsx
type Component1Props<T> = (typeof Component1)(T) extends (props: infer P) => JSX.Element ? P : never
type Component2Props<T> = Component1Props<T> |
any update on this? |
@RyanCavanaugh Is there any chance to revisit the syntax you proposed? |
I'm not sure whether my proposal was fully clear, and since it seems we're talking syntax again, I thought I'd give some more explanations: The syntax I'm proposing is just Such an expression would yield You could think of it as casting const identity = <T>(arg: T) => arg; // has type <T>(arg: T) => T
const stringIdentity = identity<string>; // has type (arg: string) => string
const stringIdentity2 = identity as (arg: string) => string; // equivalent but ugly Thus, the syntax does not involve Actually, it's not really "new" syntax, but the existing syntax for calling generic functions, like Maybe it's just me, but to me this really seems like the most natural way to enable this feature in TypeScript. In fact, before creating this proposal, I tried the proposed syntax and I wouldn't have been surprised if it would have worked. |
So, in the end, are you suggesting to add a utility type? Why not just use anonymous functions for this? type Identity<T> = (a: T) => T
const myFunction: Identity<string> = (a) => "" // (a: string) => string |
@DavideValdo Not really. I'm suggesting to add a way to concretize the type of a generic function. The example with const identity = <T>(arg: T) => arg;
const stringIdentity: (arg: string) => string = identity; Better use cases are the one provided in the linked issue by @bradennapier, or the one in my proposal about discriminated unions. In general, it would be useful to help TypeScript infer the correct type without providing it explicitly. I agree that this is probably not needed very often, but when it is, it would very useful. |
a.k.a template specialization as known in C++ |
Any progress? |
+1 |
2 similar comments
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Some interesting workarounds are listed here: https://stackoverflow.com/questions/50321419/typescript-returntype-of-generic-function But they're not pretty |
This feature is now implemented in #47607. |
Can we update the tags here to reflect that it was accepted? |
Search Terms
generic function, parametric function, function type parameter, function type argument
The problem is closely related to #26043, but the proposed solution is different.
Suggestion
Currently, there is no way of explicitly providing type arguments to generic functions. For example, consider the following function:
TypeScript correctly infers the return type
{ value: T }
. However, it's not possible to derive the return type for a particularT
, saynumber
, at least not without ugly workarounds such as creating new functions just to fix the type arguments:So I suggest to provide a syntax that allows to do just that: fix type arguments of generic functions:
This syntax resembles the existing one for providing type arguments when calling a function:
With this proposal implemented, interpreting such a call could be split into two parts:
First fix the type argument of box to
number
, which yields a function that takes a number.Second, apply that function to
2
.Hence, it could also be written with parenthesis:
(box<number>)(2)
.I wouldn't suggest doing that in practice, but it shows that this proposal increases composability of generic functions.
Use Cases
I tried to come up with a way to implement discriminated unions more succintly. The idea is to start with factory functions and then combine the return types:
With this approach, you would get convenient factory functions for building instances, without duplicating the object structure.
But it doesn't work, because it relies on the new syntax I just proposed.
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: