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 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