function overload arguments narrowing
The example is probably the best illustration I can give... It's also much clearer (I think) that the description in text.
Currently, the types of the arguments of a function with overloads cannot be forwarded to another function with the same overloads (1) because the raw typing of the argument doesn't match any of the overloads. But, at the same time, this restriction is not carried out in the relationship between the arguments' types is not used once in the function's body.
We currently have to either assert types (using as) or do if (...) throw with conditions that will never be met runtime.
That requires to sacrifice either type safety or performance.
I've seen #13225. However, my suggestion is more regarding the arguments than the return type. It shouldn't impose any additional restrictions that currently exist (actually, remove some).
Moreover, one of the arguments that was given at the time ("Pass-through overloads are extremely common and would require writing "artificial" code to satisfy the checker") is not true anymore. (See point (1) in the example). I'm not sure if this particular point should be considered a bug in itself or not...
Having the type-system aware of the overloads when typing the arguments could actually restore this.
Simplify overloads implementation and make them more readable.
let baz: string;
function fn2(foo: 'a', bar: string): void;
function fn2(foo: 'b', bar: number): void;
function fn2(foo: 'a' | 'b', bar: string | number): void
{
fn(foo, bar); // (1) If this doesn't work, ...
}
function fn(foo: 'a', bar: string): void;
function fn(foo: 'b', bar: number): void;
function fn(foo: 'a' | 'b', bar: string | number): void
{
if (foo == 'a')
{
baz = bar; // (2) ... This should!
}
}
https://www.typescriptlang.org/play/index.html#src=let%20baz%3A%20string%3B%0D%0A%0D%0Afunction%20fn2(foo%3A%20'a'%2C%20bar%3A%20string)%3A%20void%3B%0D%0Afunction%20fn2(foo%3A%20'b'%2C%20bar%3A%20number)%3A%20void%3B%0D%0Afunction%20fn2(foo%3A%20'a'%20%7C%20'b'%2C%20bar%3A%20string%20%7C%20number)%3A%20void%0D%0A%7B%0D%0A%20%20%20%20fn(foo%2C%20bar)%3B%20%2F%2F%20(1)%20If%20this%20doesn't%20work%2C%20...%0D%0A%7D%0D%0A%0D%0Afunction%20fn(foo%3A%20'a'%2C%20bar%3A%20string)%3A%20void%3B%0D%0Afunction%20fn(foo%3A%20'b'%2C%20bar%3A%20number)%3A%20void%3B%0D%0Afunction%20fn(foo%3A%20'a'%20%7C%20'b'%2C%20bar%3A%20string%20%7C%20number)%3A%20void%0D%0A%7B%0D%0A%20%20%20%20if%20(foo%20%3D%3D%20'a')%0D%0A%20%20%20%20%7B%0D%0A%20%20%20%20%20%20%20%20baz%20%3D%20bar%3B%20%2F%2F%20(2)%20...%20This%20should!%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A
My suggestion meets these guidelines:
type Args<T> = T extends (...args: infer A) => any ? A : never
function createDecorator (fn: () => any): () => PropertyDecorator
function createDecorator <A1> (fn: (arg1: A1) => any): (arg1: A1) => PropertyDecorator
function createDecorator <A1, A2> (fn: (arg1: A1, arg2: A2) => any): (arg1: A1, arg2: A2) => PropertyDecorator
function createDecorator <A1, A2, A3> (fn: (arg1: A1, arg2: A2, arg3: A3) => any): (arg1: A1, arg2: A2, arg3: A3) => PropertyDecorator
function createDecorator <A1, A2, A3, A4> (fn: (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => any): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => PropertyDecorator
function createDecorator <A1, A2, A3, A4, A5> (fn: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => any): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => PropertyDecorator
function createDecorator <A1, A2, A3, A4, A5, A6> (fn: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, arg6: A6) => any): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, arg6: A6) => PropertyDecorator
function createDecorator <F extends Function> (fn: F): (...args: Args<F>) => PropertyDecorator {
return () => (target, propertyKey) => {
// todo:
}
}
function foo (a: number, b?: number): any
function foo (a: number, b: number, c?: string): any
function foo (a: number, b?: number, c?: string): any {
// do something...
}
const bar = createDecorator(foo)
bar(1, 2) // Error: Expected 1 arguments, but got 2.
@varHarrie: Although I agree your example is problematic as well, I believe it's a different one.
I seem to see some other issues as well:
As long as your using Args<T>, why still rely on multiple overloads of createDecorator?
The implementation type should be enough to derive everything you need (That's one of the strengths of inferring arguments...).
Your overloads for foo seem to be ambiguous as well: is foo(1, 2) a call to the first or the second overload? changing the first overload to foo(a: number): any should solve this.
Note that even with these corrections, an error still occurs, but the other way around.
Now, the first overload is discarded and only the first one is retained.
=> See Playground
I believe this is a different issue. Since here, some overloads seems to be completely discarded. While my issue is concerned with using only the implementation instead of the overloads.
My issue also seems to be mostly regarding handling of parameters' types within an overloaded method. While yours seems to have more to do with type handling at caller's site.
Yeah, it made some misunderstanding. I just want to indicate that both of overloads and Args<T> are defective.
Most helpful comment
@varHarrie: Although I agree your example is problematic as well, I believe it's a different one.
I seem to see some other issues as well:
As long as your using
Args<T>, why still rely on multiple overloads ofcreateDecorator?The implementation type should be enough to derive everything you need (That's one of the strengths of inferring arguments...).
Your overloads for
fooseem to be ambiguous as well: isfoo(1, 2)a call to the first or the second overload? changing the first overload tofoo(a: number): anyshould solve this.Note that even with these corrections, an error still occurs, but the other way around.
Now, the first overload is discarded and only the first one is retained.
=> See Playground
I believe this is a different issue. Since here, some overloads seems to be completely discarded. While my issue is concerned with using only the implementation instead of the overloads.
My issue also seems to be mostly regarding handling of parameters' types within an overloaded method. While yours seems to have more to do with type handling at caller's site.