Issue
It is a common understanding that a subtype can be assigned to a supertype, while the reverse is not true. The code snippet below illustrates this concept:
class A extends Array<string> {
public myProp!: string
}
// Assigning a subtype to its supertype is permissible
declare const a1: A
const array1: Array<string> = a1
// Attempting to assign a supertype to one of its subtypes results in an error
declare const array2: Array<string>;
const a2: A = array2
In the provided code, TItems
represents a subtype of Array<string>
, and the type of []
evaluates to never[]
.
When casting it as [] as Array<string>
, trying to assign the supertype (Array<string>
) to the subtype TItems
becomes invalid.
A similar issue arises when casting it as [] as TItems
. This form of typecasting is incorrect for the same reason.
Resolution
To rectify this error, a safer approach would be:
class MyClass<TItems extends Array<string>> {
public items: TItems;
constructor() {
this.items = [] as unknown as TItems;
}
}
This method, however, may lead to runtime errors due to its lack of "safe" typecasting.
To prevent potential runtime issues, a more reliable solution involves initializing the property items
with the constructor of the class TItems
or a function returning TItems
, instead of using = []
. Two alternate approaches are demonstrated below:
// If TItems is expected to be a class,
// provide the constructor as a parameter to the class constructor
class MyClass<TItems extends Array<string>> {
public items: TItems;
constructor(ctor: new () => TItems) {
this.items = new ctor();
}
}
class MyArray extends Array<string> {
private myProp!: string
}
const myClassVar = new MyClass(MyArray)
// If TItems is simply a type,
// pass a function creating an object of `TItems` as a parameter
class MyClass<TItems extends Array<string>> {
public items: TItems;
constructor(fn: () => TItems) {
this.items = fn();
}
}
declare function createObject(): Array<string> & { myProp: string }
const myClassVar = new MyClass(createObject)