Matt
Matt

Reputation: 39

Type 'never[]' is not assignable to type '...'

Type 'never[]' is not assignable to type '...'

I am unable to initialize the property items on my class due to this random error. I figured extending the generic class type to Array<string> would ensure that the type is always a string array?

class MyClass<TItems extends Array<string>> {
    constructor() {
        this.items = [];
    }

    public items: TItems;
}

Gives me the error:

Type 'string' is not assignable to type 'TItems'. 'string' is assignable to the constraint of type 'TItems', but 'TItems' could be instantiated with a different subtype of constraint 'string'.ts(2322)

Upvotes: 3

Views: 15193

Answers (1)

Shivam Singla
Shivam Singla

Reputation: 2201

Problem

You might be familiar of the fact that a subtype can be assigned to a supertype but vice-versa is not true. So, in the following code and the inline explanation-

class A extends Array<string> {
  public myProp!: string
}

// it is ok to assign a subtype to its supertype
// because subtype has atleast all those props/methods
// that a supertype has
declare const a1: A
const array1: Array<string> = a1

// it is an error to assign supertype to one of its subtype
// (until they are structurally same)
// because the supertype (array2) may have some missing props/methods
// (myProp in this case) that its subtype has.
declare const array2: Array<string>
const a2: A = array2

Playground

In your code, TItems is a subtype of Array<string>, and type of [] is never[].

If you had typecast-ed it with [] as Array<string>, the supertype (Array<string>) could not be assigned to subtype TItems. Playground

If you had typecast-ed it with [] as TItems, the typecasting is itself erroneous for the very same reason. Playground

Solution

The error could be get ridden of the error with typecasting as-

class MyClass<TItems extends Array<string>> {
    public items: TItems;

    constructor() {
        this.items = [] as unknown as TItems;
    }
}

Playground

But this may result in runtime errors because it's not a "safe" typecasting.

To avoid runtime errors, the correct way is to initialise the prop items with the constructor of the class TItems or function that returns TItems instead of = []. This will eliminate the type errors and also will ensure that there will be no runtime errors. Both ways are demonstrated-

// if a passed TItems is supposed to class
// we pass that constructor of the class 
// constructor of `MyClass`
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)

Playground

// if a passed TItems is supposed to be just a type
// we pass a function that will create that object of `TItems`
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)

Playground

Upvotes: 2

Related Questions