Home > database >  TypeScript ts(2322) error even after `instanceof` check
TypeScript ts(2322) error even after `instanceof` check

Time:10-19

I'm trying to write a function that returns a new instance of a type which is selected based on the type of the function's argument.

Here's the code:

class A {}
class A1 extends A {}
class A2 extends A {}

class B<TypeA extends A>
{
    a: TypeA;
    constructor(a: TypeA)
    {
        this.a = a;
    }
}
class B1 extends B<A1> {}
class B2 extends B<A2> {}

function createB<TypeA extends A>(a: TypeA): B<TypeA>
{
    if (a instanceof A1)
    {
        const a1: A1 = a;  // OK
        return new B1(a1); // Error - ts(2322): 'A1' is assignable
                           // to the constraint of type 'TypeA',
                           // but 'TypeA' could be instantiated
                           // with a different subtype of constraint 'A'
    }
    else if (a instanceof A2)
    {
        const a2: A2 = a;  // OK
        return new B2(a2); // Error - ts(2322): 'A2' is assignable
                           // to the constraint of type 'TypeA',
                           // but 'TypeA' could be instantiated
                           // with a different subtype of constraint 'A'
    }
    else
    {
        throw new Error();
    }
}

TypeScript Playground

TypeScript compiler says that "'TypeA' could be instantiated with a different subtype of constraint 'A'" but the a instanceof A1 check makes sure that it was instantiated with the A1 type and not with some other subtype of A. Is this a limitation of TypeScript or am I missing something?

CodePudding user response:

If later one creates a subtype A1B of A1, an instance of A1B will also be an instance of A1:

class A1B extends A1 {}
const a1b = new A1B()
console.log(a1b instanceof A1); // true

And if one calls createB(a1b), given the return type of createB, we expect the result to be a B<A1B>, whereas your implementation would return a B1 (i.e. B<A1>).

const result = createB(a1b);
//    ^? B<A1B>

That is what the error message means.


In your case, you could be more restrictive to what your function can output, e.g. using function overloads instead of generics:

function createB2(a: A1): B1;
function createB2(a: A2): B2;
function createB2(a: A1|A2): B<A1|A2> {
    if (a instanceof A1) {
        const a1: A1 = a;
        return new B1(a1); // Okay
    }
    else if (a instanceof A2) {
        const a2: A2 = a;
        return new B2(a2); // Okay
    }
    else {
        throw new Error();
    }
}

const result2 = createB2(a1b);
//    ^? B1

Playground Link

  • Related