3

I'm working on a piece of code in Typescript, and came across this below issue that I can't make much sense as to why it's happening.

The below code block triggers an Argument of type 'string | number' is not assignable to parameter of type 'never' error at compile.

interface A {
    x: number | string
}

const a1: A[] = [{x: 1}, {x: 'a'}]
const b: number[] | string[] = ['a', 'b']

const a2 = a1.filter(a => b.includes(a.x))

However; if I were to use (number | string)[] when declaring/assigning b, it works fine.

I checked the definition file for includes (Array<T>.includes(searchElement: never, ...), but that also didn't make sense because I thought that the generic T would encompass number[] | string[].

I would appreciate if someone could shine a light as to why the Array<T> doesn't cover number[] | string[] but covers (number | string)[].

2
  • 3
    number[] | string[] is either an array of all numbers or an array of all strings. (number | string)[] is an array of elements each of which may either be a number or a string. Any array that meets the first definition will meet the second, but the opposite is not true. Commented Nov 9, 2020 at 18:41
  • @jonrsharpe thanks for your reply. It seems that I made a typo in the code block. Sorry about that. The question and code should make a bit more sense now. So shouldn't a.x be comparable to each element of b? Because it's not like a single string to number comparison of which the result would always be false. Commented Nov 10, 2020 at 6:42

1 Answer 1

6

The type you set for b is number[] | string[]. This means that its type will be either an array of numbers (number[]) or an array of strings (string[]) - which exactly depends on the data you initialise the variable with.

When you do:

const b: number[] | string[] = ['a', 'b'];

You actually set its type as string[] - this variable b now has nothing to do with numbers and is simply an array of strings.

Then you go ahead and do:

const a2 = a1.filter(a => b.includes(a.x));

So, for each valuе of a1 - which may be a string or a number, you check whether it exists in the array b. And as explained above, b is an array of strings only. And you cannot pass a number | string to includes(), which for this array expects only a string argument (this array is Array<string>). Hence the compiler error.


Now, for the other case you suggest:

const b: (number | string)[] = ['a', 'b'];

This defines an array of the type number | string, that is, every one of its elements can be a different type - either a number or a string. So you can have, for example:

const b: (number | string)[] = [1, 'b'];

It just happens that in your definition, all your elements (the two of them) are the same type - string.

Now when you get to the call to includes(), the situation is very different. Since your array can now hold both numbers and strings, include() would happily work with both those types (this array is Array<number | string>), and a.x can be both types.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.