TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Cannot narrow arguments of overloaded function

Open otomad opened this issue 3 years ago • 4 comments

The API of a UI library uses overloaded functions to add controls, like this.

// *.d.ts
declare function add(type: "scrollbar", name: string): Scrollbar
declare function add(type: "slider", name: string): Slider
// ...

I want to encapsulate that function for ease of use.

Define a type to associate the control name with the control type.

type Control<T> = T extends "scrollbar" ? Scrollbar :
                  T extends "slider" ? Slider : never;

Other controls are temporarily ignored.

Then define a generic function.

function myAdd<T extends "scrollbar" | "slider">(type: T, name: string, /* other arguments */): Control<T> {
    // typeof type is "scrollbar" | "slider"
    const control = add(type, name); // Error description is below
                        ^^^^
    // and typeof control now is Scrollbar & Slider, expect Scrollbar | Slider
    return control as Control<T>;
}
No overload matches this call.
  Overload 1 of 2, '(type: "scrollbar", name: string): Scrollbar', gave the following error.
    Argument of type '"scrollbar" | "slider"' is not assignable to parameter of type '"scrollbar"'.
      Type '"slider"' is not assignable to type '"scrollbar"'.
  Overload 2 of 2, '(type: "slider", name: string): Slider', gave the following error.
    Argument of type '"scrollbar" | "slider"' is not assignable to parameter of type '"slider"'.
      Type '"scrollbar"' is not assignable to type '"slider"'.ts(2769)

I think I've written something incorrectly that makes it impossible to infer the type correctly. How should I modify it?

otomad avatar Oct 20 '22 12:10 otomad

This is working as intended. The overload only allows to either pass a "scrollbar" type, or a "slider" type. The generic version additionally allows to pass the "scrollbar" | "slider" type.

MartinJohns avatar Oct 20 '22 13:10 MartinJohns

On a separate note, please don't use the "types not correct with/in callback" issue template for issues unrelated to #9998. Use of that template is why your issues keep getting marked as duplicates.

fatcerberus avatar Oct 20 '22 14:10 fatcerberus

How can I overwrite it to make it work? Can I only narrow the type one by one?

if (type === "scrollbar")
    add(type, name);
else if (type === "slider")
    add(type, name);
else if // ...

On a separate note, please don't use the "types not correct with/in callback" issue template for issues unrelated to https://github.com/microsoft/TypeScript/issues/9998. Use of that template is why your issues keep getting marked as duplicates.

I see.

otomad avatar Oct 20 '22 14:10 otomad

It would be better to have the types like this:

type ControlTypes = {
    scrollbar: Scrollbar,
    slider: Slider
};

type Control<T extends keyof ControlTypes> = ControlTypes[T];

in which case add could be declared as:

declare function add<T extends keyof ControlTypes>(type: T, name: string): Control<T>

and then your generic implementation of myAdd would likely work more smoothly.

fatcerberus avatar Oct 20 '22 14:10 fatcerberus

Thanks a lot!

otomad avatar Oct 21 '22 03:10 otomad