-
Notifications
You must be signed in to change notification settings - Fork 12.8k
proper overloading for arrow functions #47669
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
Comments
This immediately runs into difficulty because how do you check the body?
|
how do overloads work on normal functions? can it just do it the same way? |
In the normal function case, there's a separate implementation signature which is not exposed to callers, and is allowed to be very weakly typed. |
That's true @nmain, but the compiler does still check if the overloads are compatible with the implementation signature. If I read it right, the suggestion is, there would be dedicated syntax for an overload type like in the example given: const foo: overload {
(value: number): number
(value: string): string
} = someFunction; then the compiler could just use the overload compatibility check in lieu of an assignability check. |
an idea i came up with to solve the issue without adding any additional syntax is to improve the logic used to infer the type of the implementation when its types are omitted. for example: const foo: {
(value: number): number
(value: string): string
} = (value) => value currently the value is inferred as obviously this could get messy with some more complicated overloads though: const foo: {
(value: number, value2: string): number
(value: string): string
} = (value1, value2) => value in this case perhaps there could be a built in interface Overload<Parameters extends unknown[], Return> {
parameters: Parameters
return: Return
} then the impl can be inferred as something like <O extends Overload<[number, string], number> | Overload<[string], string>>(
...args: O['parameters']
) => O['return'] (though while playing around with this approach i ran into other issues such as #48345 and #46076) |
@fatcerberus @RyanCavanaugh Would this syntax have any glaring issues? function foo(value: number): number;
function foo(value: string): string;
function foo(value: string|number): number|string { return value }
// idea
const foo2: (value: number) => number;
const foo2: (value: string) => string;
const foo2: (value: string|number): number|string = (value: string | number): number|string => value |
Probably the biggest issue is that re-declaring constants isn't valid js. The example provided in the OP is viable, though. |
@erquhart The existing function overload syntax in TypeScript declares functions without a body, which isn't valid syntax either. Invalid JS syntax is not a limitation. |
imo the example in the OP is better because you don't have to duplicate the function name nor do you have to add types to the implementation signature |
Yeah, fair - I was more thinking of js support for declaring functions that have already been declared, and doing so with constants feels a bridge further. But yeah, not materially different since neither is really valid, agreed. Your syntax is honestly preferable if using |
Regardless of the syntax for arrow function overload, I can see no reason not to use the same algorithm which is used for a function overload declaration. It's true that the function declaration algorithm would be better if the return values in the implementation were type checked against the signatures in context. We can even simulate it in user code: function foo3(a: number): "num";
function foo3(a: string): "str";
function foo3(a: bigint): never;
function foo3(a: number | string | bigint): "num"|"str";
function foo3(a: number | string | bigint): "num"|"str" {
if (typeof a === "string"){
const ret = "str";
// Conceptual simulation of type checking
// Unfortunately we cannot write typeof foo3(a) in user code,
// but that lookup can be done in typescript internals.
// Obviously would not deliberately enter infinite loop.
const testret = foo3(a);
ret satisfies typeof testret; // OK
return ret;
}
else if (typeof a === "number"){
const ret = "num";
const testret = foo3(a);
ret satisfies typeof testret; // OK
return ret;
}
else if (typeof a === "bigint"){
const ret = "num";
const testret = foo3(a);
ret satisfies typeof testret; // error
// ~~~~~~~~~
//Type 'string' does not satisfy the expected type 'never'.(1360)
return ret;
}
throw undefined; // a==="bigint" should take this branch.
} The obvious solution then is to unify the treatment of function declarations and assignment of arrow functions to overloads types, Setting the return type of the implementation with the intersection of return types just breaks the existing type checking (which although not perfect is still better than nothing). Also, the implementation signature could be reduced to parameter names only:
because it can be derived from the overloads:
|
Suggestion
π Search Terms
arrow function overload
β Viability Checklist
My suggestion meets these guidelines:
β Suggestion
it seems that the syntax for overloading arrow functions are more like a side effect of existing functionality (function interfaces/intersections), rather than an intentional language feature. as a result, it doesn't really work properly in most cases - see #33482
it would be nice if there was actual syntax for arrow function overloading like there is for normal functions
π Motivating Example
this is the most basic example of an overload i can think of, yet it doesn't work:
as mentioned on #33482 (comment), function interfaces have a much broader purpose than overloads, meaning it has to protect against a potentially incorrect assignment in other scenarios. but here, that's obviously not what we want
perhaps a function overload type that looks something like:
π» Use Cases
makes it easier to avoid using old function syntax
The text was updated successfully, but these errors were encountered: