Ben
Ben

Reputation: 3674

Typescipt Generics - return new instance with same type of generic parameter

Lets say I have an two classes that inherit from some interface: (stripped down for simplicity)

interface IVehicle {}

class Car implements IVehicle { 
    constructor(public color: string) {} 
}
class Helicopter implements IVehicle {}

I now have a function which given a instance (which type extends IVehicle) will return a new instance of the same type

function clone<T extends IVehicle>(element: T): T {
    if (element instanceof Car) {
        return new Car(element.color);
    }
    ...
    return element;
}

clone(new Helicopter())

However I get a error around return new Car(...) saying:

'Car' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'IVehicle'

Which seems untrue as the instanceof should asset that T is of subtype Car right?

After that I tried casting the return type to of type T with return new Car(element.color) as T;.

But this now produces this error:

Conversion of type 'Car' to type 'T' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

And includes the first error.

Has TypeScript caught a legitimate types issue or is this an error? Am I missing something from the type constraint? How can I fix this error?

Upvotes: 0

Views: 59

Answers (1)

Lesiak
Lesiak

Reputation: 26064

If you intend to clone instances of IVehicle, I think adding this operation to the interface itself is a good solution.

interface IVehicle<ActualType> {
    clone(): ActualType;
}

class Car implements IVehicle<Car> { 
    constructor(public color: string) {} 
    clone(): Car {
        return new Car(this.color);
    }
}

class Helicopter implements IVehicle<Helicopter> {
    clone(): Helicopter {
        return new Helicopter();
    }
}

const h = new Helicopter();
const hClone = h.clone();

Update

If you cannot modify source of Vehicles, and subclasses form a closed set, you can use method overloading

interface IVehicle {}

class Car implements IVehicle { 
    constructor(public color: string) {} 
}

class Helicopter implements IVehicle {}

function clone(element: Car): Car;
function clone(element: Helicopter): Helicopter;
function clone(element: IVehicle): IVehicle {
    if (element instanceof Car) {
        return new Car(element.color);
    } else if (element instanceof Helicopter) {
        return new Helicopter();
    } else {
        throw new Error("Unknown subclass of IVehicle passed to clone " + element)
    }  
}

const h = new Helicopter();
const h1 = clone(h);

Upvotes: 1

Related Questions