ivaigult
ivaigult

Reputation: 6687

How to call an overloaded function from a generic

Let I have two interfaces that have a few fields in common and another interfaces that generalizes them:

interface IFirst {
    common: "A" | "B";
    private_0: string;
}
interface ISecond {
    common: "C" | "D";
    private_1: string;
}
interface ICommon {
    common: string;
    private_0?: string;
    private_1?: string;
}

Now, I want to write a function that prints an instance of these interfaces. I decided to use overloading:

function printElement(element: IFirst) : void;
function printElement(element: ISecond): void;

function printElement(element: ICommon) : void {
    console.log(element.common);
    if (element.private_0)
        console.log(element.private_0);
    if (element.private_1)
        console.log(element.private_1);
}

Then I want to write a function that prints an array of them:

function printAll<ElementType extends ICommon>(array: ElementType[]) {
    for (const element of array)
        printElement(element)
}

However, this doesn't work:

No overload matches this call.   Overload 1 of 2, '(element: IFirst): void', gave the following error.
    Argument of type 'ElementType' is not assignable to parameter of type 'IFirst'.
      Type 'ICommon' is not assignable to type 'IFirst'.
        Types of property 'common' are incompatible.
          Type 'string' is not assignable to type '"A" | "B"'.   Overload 2 of 2, '(element: ISecond): void', gave the following error.
    Argument of type 'ElementType' is not assignable to parameter of type 'ISecond'.
      Type 'ICommon' is not assignable to type 'ISecond'.
        Types of property 'common' are incompatible.
          Type 'string' is not assignable to type '"C" | "D"'.(2769)

Because ElementType is considered as ICommon instance. The compiler tries to do a backwards conversion from ICommon to IFirst, for example, and it is obviously illegal. How do I make this function type safe then?

Upvotes: 0

Views: 37

Answers (1)

DomAyre
DomAyre

Reputation: 858

You can achieve the kind of thing you're looking for with something like this:

interface IFirst {
    common: "A" | "B";
    private_0: string;
}

interface ISecond {
    common: "C" | "D";
    private_1: string;
}

type ICommon = IFirst | ISecond;

function printElement(element: ICommon) : void {
    console.log(element.common);
    if ("private_0" in element) {
        console.log(element.private_0);
    }
    if ("private_1" in element) {
        console.log(element.private_1);
    }
}

function printAll(array: ICommon[]) {
    for (const element of array) {
        printElement(element);
    }
}

And you can improve the type checking with proper functions as described in Interface type check with Typescript

Upvotes: 1

Related Questions