Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does an overloaded function declaration sometimes force useless type narrowing?

Tags:

typescript

Given the following implementation of an overloaded function statement:

function foo(options: "a"): "a";
function foo(options: "b"): "b";
function foo(options: "a" | "b"): "a" | "b" {
    switch (options) {
        case "a":
            return `a`;
        case "b":
        default:
            return `b`;
    }
}

I can call the function like this:

// this works
const url1 = foo("a");
const url2 = foo("b");

However, I get a type error if I call the foo function when being called with a value of union type "a" | "b":

// function call (inside of wrapped function)
type Options = "a" | "b";
const wrapper = (options: Options) => {
    // function overloading forces the caller to narrow the type
    const url = foo(options); // Error: Type '"a"' is not assignable to type '"b"'.
}

I can fix this via

function foo(options: "a"): "a";
function foo(options: "b"): "b";
function foo(options: "a" | "b"): "a" | "b" {
    switch (options) {
        case "a":
            return "a";
        case "b":
        default:
            return "b";
    }
}

type Options = "a" | "b";
const wrapper = (options: Options) => {
    // Solution: Calling function 'foo' in the exact same way
    const url = options === "a" ? foo(options) : foo(options);
}

Question: Why does TypeScript force me to narrow the options value since it has no implication on how I call foo?

I always call foo as foo(options). If the type signatures of the overloads differ in a greater way, for example, if some overloads contain optional parameter values, TS should of course prompt me to narrow the type. But shouldn't it infer that it's not necessary in a scenario as this one?

TypeScript Playground of my question

like image 958
Andru Avatar asked Oct 14 '25 19:10

Andru


1 Answers

function foo(options: "a"): "a";
function foo(options: "b"): "b";
function foo(options: "a" | "b"): "a" | "b" {

The last line is the start of the implementation of the function, but it's not part of the type that's visible to the external world. So with the code you wrote, passing in something of type "a" | "b" isn't actually allowed. Just "a" or "b" on their own.

The error message probably included the following text, which tries to point out the problem, though it may be hard to understand what it means if you've never seen it before:

The call would have succeeded against this implementation, but implementation signatures of overloads are not externally visible.

The fix is to add one more overload to the definition:

function foo(options: "a"): "a";
function foo(options: "b"): "b";
function foo(options: "a" | "b"): "a" | "b";
function foo(options: "a" | "b"): "a" | "b" {
like image 147
Nicholas Tower Avatar answered Oct 17 '25 10:10

Nicholas Tower