Home > database >  Generic Function uses Array push causes error: Type 'string' is not assignable to type �
Generic Function uses Array push causes error: Type 'string' is not assignable to type �

Time:10-17

In this code the type of array is already defined but Typescript fires an error for the array push, saying that:

Argument of type 'string | number' is not assignable to parameter of type 'never'. Type 'string' is not assignable to type 'never'.

type Characters = {
    letters: string[];
    numbers: number[];
}

const characters :Characters = {
    letters:  ['a', 'b',' c' ],
    numbers: [1,2,3],
}

type AddCharacter = <K extends keyof Characters, V extends Characters[K][number]>
(characterType:K, character:V) => void;

const addCharacter  :AddCharacter = (characterType, character) =>{
   characters[characterType].push(character) //Type 'string' is not assignable to type 'never'
}

addCharacter('letters', 'd') //ok
addCharacter('numbers', 4) //ok
addCharacter('letters', 4) //error as expected beacause 4 is not a string
addCharacter('numbers', 'd') //error as expected beacause d is not a number
addCharacter('words', 'test' )  //error as expected because 'words' is not a key of 'Characters'

I cannot figure out why it showing error since the type seem to be correct (got perfect type checking calling addCharacter). How can I get rid of the error and getting it to work nicely with typescript? Thanks for any help! Playground

CodePudding user response:

If you insert the following line into the body of addCharacter,

let x = characters[characterType][1]

You'll see that Typescript infers the type of x to be string | number, which implies that characters[characterType] is string[] | number[].

That would explain why, if you inspect the type of the called push method, the items parameter has type never[],

(method) Array<T>.push(items: never[]): number

because T is string[] | number[], not just string[] or just number[]. This causes the type of items to be never[], which in turn results in the error message you see.

You can fix the issue by changing addCharacter to:

const addCharacter:AddCharacter = <K extends keyof Characters, V extends Characters[K][number]>(characterType: K, character: V) => {
    (characters[characterType] as V[]).push(character)
}

This forces the type of characters[characterType] to be the dynamically inferred type of V based on the type of K. I don't know why Typescript doesn't do this automatically. Perhaps a Typescript guru can explain. Or perhaps we should file a Typescript Issue.

  • Related