Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method argument and return types override in typescript

I wan't to describe an abstract method in abstract class which can take number or string and also return number or string; I'm using | symbol to tell method that it's arguments and return types may vary from string to number.
Then I'm creating two classes b and c which are extended from abstract class a and trying to override method test() with no argument and return type variation.
Next, I'm declaring variable x which type could be similar to b or c class, and I'm creating instace of one of those classes depending on random statement.
And finally I'm trying to call test() method, but TS compiler giving me error described below.

abstract class a {
    abstract test(x: string | number): string | number;
}

class b extends a {
    test(x: number): number {
        return x;
    }
}


class c extends a {
    test(x: string): string {
        return x;
    }
}

let x: b | c;


if (Math.random() > 0.5) {
    x = new b()
} else {
    x = new c()
};

x.test(1)

Here is error from TS compiler:

Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => number) | ((x: string) => string)' has no compatible call signatures. (property) test: ((x: number) => number) | ((x: string) => string)

Perhaps I'm using wrong aproach or I misunderstood TS documentation, if so - could you please point me out better way of my goal.
Sorry for poor class names and absence of any "fiddle" - I coudn't find any js playground websites which highlights TS compiler error, so I recommend official TS Playground

like image 772
eko24ive Avatar asked Apr 08 '26 07:04

eko24ive


1 Answers

When you create an instance of the class in a if block then the typescript compiler is unable to figure out which type x will be. That is OK, but the problem is that you then try to call the test function with a number and that is only possible when the type is b. Since the compiler think there is a possibility that x is of type c, you get an error.

You need to assure the compiler that when you call test then the function you call will always match the parameters provided.

You can either:

  1. Change the call signatures so that both accept any type, this way it does not matter to the compiler which of the methods are called:

    class b extends a {
        test(x: any) {
            return x;
        }
    }
    
    class c extends a {
        test(x : any) {
            return x;
        }  
    }
    
  2. Call the method within the if block:

    if (Math.random() > 0.5) {
        x = new b();
        x.test(1);
    } else {
        x = new c();
        x.test('1');
    }
    
  3. Typeguard you method call:

    if (x instanceof b)
        x.test(1);
    else if(x instanceof c)
        x.test('1');
    

Check out the handbook on Union types and typeguards: https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types.

EDIT: The suggestion for you so that you dont have to typeguard you type on every call, would be to have the type checking done in the methods themselves. The downside of this is that will be able to call the method with an incorrect parameter without getting a warning from the compiler. Here is an example of how that could look:

abstract class a {
    protected abstract internal(x: any): any;

    public test(x: string | number): string | number {
        return this.internal(x);
    }
}

class b extends a {
    protected internal(x) {
        if (typeof x === "number") 
            return x;
        else
            throw new Error("Invalid argument");
    }
}


class c extends a {
    protected internal(x) {
        if (typeof x === "string")
            return x;
        else
            throw new Error("Invalid argument");
    }
}

let x: b | c;


if (Math.random() > 0.5) {
    x = new b();
} else {
    x = new c();
}

x.test(1);
like image 161
hagner Avatar answered Apr 09 '26 21:04

hagner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!