I'm new with typescript and I kept getting the same "not assignable to parameter of type error" error, and, after some research, I solve it but I'm not proud of the solution.
I'm working with a simple class and to simplify I let only the attribute I'm working with.
interface Proficiencies {
armor: string[];
weapons: string[];
tools: string[];
savingThrows: number[];
skills: boolean[];
}
class Character {
public readonly proficiencies: Proficiencies = {
armor: [],
weapons: [],
tools: [],
savingThrows: [],
skills: []
}
// This is the code that have the error
public addProficiency<T extends keyof Proficiencies> (proficiencyType: T, proficiency: Proficiencies[T]) {
this.proficiencies[proficiencyType].push(...proficiency);
}
}
If I add 'as never[]' it works, but I wonder if there's any other way to make it transpile.
this.proficiencies[proficiencyType].push(...proficiency as never[]);
CodePudding user response:
First, The bool type is not a valid type in TypeScript. You should use the boolean type instead.
interface Proficiencies {
armor: string[];
weapons: string[];
tools: string[];
savingThrows: number[];
skills: boolean[]; <----
}
Second, the proficiency parameter type is not being inferred correctly by TypeScript, causing the error you're seeing. Please try like this:
public addProficiency<T extends keyof Proficiencies, U extends Proficiencies[T]>(
proficiencyType: T,
proficiency: U
) {
this.proficiencies[proficiencyType].push(...proficiency);
}
CodePudding user response:
Your mistake is here:
public readonly proficiencies: Proficiencies = {
armor: [],
weapons: [],
tools: [],
savingThrows: [],
skills: []
}
Without defining the array type, it by default will be never
. So when you tried to add a ...proficiency
to it, it was a type mismatch, and so it threw the error you saw.
Furthermore, you define proficiencies
as an object while you try to call the push
method which is available on arrays
!
Your code :
interface Proficiencies {
armor: string[];
weapons: string[];
tools: string[];
savingThrows: number[];
skills: boolean[];
}
class Character implements Proficiencies {
armor: string[] = [];
weapons: string[] = [];
tools: string[] = [];
savingThrows: number[] = [];
skills: boolean[] = [];
public readonly proficiencies: Proficiencies = {
armor: this.armor,
weapons: this.weapons,
tools: this.tools,
savingThrows: this.savingThrows,
skills: this.skills,
};
public addProficiency<T extends keyof Proficiencies>(
proficiencyType: T,
proficiency: Proficiencies[T]
) {
return [this.proficiencies, ...proficiency];
}
}
CodePudding user response:
The fundamental issue is that you haven't made it "explicit enough" that every property of Proficiencies
is an array of some type, where that type varies with the key. In addProficiency
, this.proficiencies[proficiencyType]
has type Proficiencies[T]
. When Typescript looks for a push
method on this object, it needs to simplify it to something it can do method lookup on. Right now, it can't think of anything better to do except union all the types of the fields of Proficiencies
and say it has a string[] | number[] | boolean[]
. Then Typescript knows this object must have a push
method, but it isn't sure if that push
takes ...items: string[]
or number[]
or boolean[]
, so plays it safe and says it takes ...items: never[]
(which means it isn't useful).
You need to give Typescript a way to say that Proficiencies[T]
is always Something<T>[]
. Then, without needing to simplify, Something<T>
, Typescript can say the push
method takes ...items: Something<T>[]
. Then, since this is now depending on T
, it is possible to call push
with a parameter of the right type.
You can do this by adding a helper type to map between keys and the element types of the arrays.
interface ProficiencyTypes {
armor: string
weapons: string
tools: string
savingThrows: number
skills: boolean
}
type MapArray<T> = {
[Key in keyof T]: T[Key][]
}
type Proficiencies = MapArray<ProficiencyTypes> // teach typescript "every key of Proficiencies is a key of ProficiencyTypes but with [] added to its type"
class Character {
public readonly proficiencies: Proficiencies = {
armor: [],
weapons: [],
tools: [],
savingThrows: [],
skills: []
}
public addProficiency<T extends keyof Proficiencies>(proficiencyType: T, proficiency: Proficiencies[T]) {
this.proficiencies[proficiencyType].push(...proficiency);
}
}
(Note that now you can't add non-array properties to Proficiencies
. That is exactly the freedom that Typescript wants/needs you to give up in order to ensure a function like addProficiency
makes sense.)