Egor Konovalov
Egor Konovalov

Reputation: 77

Why does TypeScript mark the type of argument from function as 'never'

Here is the union type of two signatures for the filter function and the function itself.

type Filter = {
  (arr: string[], f: (item: string) => boolean): string[]
  (arr: number[], f: (item: number) => boolean): number[]
}

let filter: Filter = (arr, func) => {
    let result = []
    for (let i in arr) {
        let item = arr[i]
        if (func(item)) {
            result.push(item)
        }
    }
    return result
}

The compiler accepts this argument as a function of the union type:

enter image description here

However, inside the function it doesn't and marks the argument item as never

enter image description here

Upvotes: 1

Views: 333

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250036

The problem is that if a function is typed as ((item: string) => boolean) | ((item: number) => boolean) there is nothing we can call this function that would be safe for either function signature ex:

let fn: ((item: string) => boolean) | ((item: number) => boolean)
fn = Math.random() > 0.5 ? (i: string) => !!i.toUpperCase() : (i: number)=> !!i.toExponential()

fn(10) // Will fail if the function is the one that accepts strings
fn("") // Will fail if the function is the one that accepts numbers

Playground Link

This is why for a union of functions typescript will infer an intersection for parameters. For primitive types this will always reduce to never, but it is more useful for object types where we can create objects that satisfy the intersection of two other object types.

TS does the best it can to infer the types of the parameters, but in this case the inferred parameters while correct are not very useful.

The better solution would be to use an explicit generic signature in this case:

type Filter = {
  (arr: string[], f: (item: string) => boolean): string[]
  (arr: number[], f: (item: number) => boolean): number[]
}

let filter: Filter = <T extends string | number>(arr: T[], func: (item: T) => boolean): T[] => {
    let result: T[] = []
    for (let i in arr) {
        let item = arr[i]
        if (func(item)) {
            result.push(item)
        }
    }
    return result;
}

Upvotes: 5

Related Questions