当通过泛型检索函数类型时,为什么打字稿期望'从不'作为函数参数?

时间:2019-04-08 11:49:38

标签: typescript

我目前正尝试从TS 2.6切换到3.4,并且遇到了奇怪的问题。以前可以使用,但是现在向我显示了一个编译器错误:

type MyNumberType = 'never' | 'gonna';
type MyStringType = 'give' | 'you';
type MyBooleanType = 'up' 

interface Bar {
    foo(key: MyNumberType): number;
    bar(key: MyStringType): string;
    baz(key: MyBooleanType): boolean;
}

function test<T extends keyof Bar>(bar: Bar, fn: T) {
    let arg: Parameters<Bar[T]>[0];
    bar[fn](arg); // error here
}

错误如下:

Argument of type 'Parameters<Bar[T]>' is not assignable to parameter of type 'never'.
Type 'unknown[]' is not assignable to type 'never'.
Type '[number] | [string] | [boolean]' is not assignable to type 'never'.
Type '[number]' is not assignable to type 'never'.

Typescript游乐场告诉我,该函数的预期参数为'never'类型: enter image description here

我完全不希望在这里出现错误。只有一个函数参数,并且该参数的类型是通过Parameters推断的。为什么函数期望never

2 个答案:

答案 0 :(得分:2)

问题在于编译器无法理解argbar[fn]correlated。它将它们都视为不相关的并集类型,因此当大多数组合不存在时,它们期望every combination of union constituents is possible

在TypeScript 3.2中,您刚刚收到一条错误消息,指出bar[fn]没有调用签名,因为它是具有不同参数的函数的并集。我怀疑该代码的任何版本都可以在TS2.6中使用;当然,带有Parameters<>的代码不在其中,因为直到TS2.8才引入条件类型。我试图以与TS2.6兼容的方式重新创建代码,例如

interface B {
    foo: MyNumberType,
    bar: MyStringType,
    baz:MyBooleanType
}

function test<T extends keyof Bar>(bar: Bar, fn: T) {
    let arg: B[T]=null!
    bar[fn](arg); // error here
}

tested in TS2.7,但仍会给出错误。因此,我将假定该代码从未真正起作用。

对于never问题:TypeScript 3.3引入了support for calling unions of functions,要求参数必须是函数联合中参数的 intersection 。在某些情况下,这是一种改进,但是在您的情况下,它希望参数是一堆不同的字符串文字的交集,该文字折叠为never。这基本上与以前以更混乱的方式表示的错误相同(“您不能称呼它”)。


处理此问题最直接的方法是使用type assertion,因为在这种情况下,您比编译器更聪明:

function test<T extends keyof Bar>(bar: Bar, fn: T) {
    let arg: Parameters<Bar[T]>[0] = null!; // give it some value
    // assert that bar[fn] takes a union of args and returns a union of returns
    (bar[fn] as (x: typeof arg) => ReturnType<Bar[T]>)(arg); // okay
}

类型断言是不安全的,这确实会让您说谎于编译器:

function evilTest<T extends keyof Bar>(bar: Bar, fn: T) {
    // assertion below is lying to the compiler
    (bar[fn] as (x: Parameters<Bar[T]>[0]) => ReturnType<Bar[T]>)("up"); // no error!
}

所以您应该小心。有一种方法可以对此进行完全类型安全的处理,从而迫使编译器对每种可能性进行代码流分析:

function manualTest<T extends keyof Bar>(bar: Bar, fn: T): ReturnType<Bar[T]>;
// unions can be narrowed, generics cannot
// see https://github.com/Microsoft/TypeScript/issues/13995
function manualTest(bar: Bar, fn: keyof Bar) {
    switch (fn) {
        case 'foo': {
            let arg: Parameters<Bar[typeof fn]>[0] = null!
            return bar[fn](arg);
        }
        case 'bar': {
            let arg: Parameters<Bar[typeof fn]>[0] = null!
            return bar[fn](arg);
        }
        case 'baz': {
            let arg: Parameters<Bar[typeof fn]>[0] = null!
            return bar[fn](arg);
        }
        default:
            return assertUnreachable(fn);
    }
}

但这太脆弱了(如果向Bar添加方法,则需要更改代码)和重复性(重复相同的子句),我通常更喜欢上面的类型断言。

好的,希望能有所帮助;祝你好运!

答案 1 :(得分:1)

The way barFn is declared is equivalent to:

type Foo = (key: number) => number;
type Bar = (key: string) => string;
type Baz = (key: boolean) => boolean;

function test1(barFn: Foo | Bar | Baz) {    
    barFn("a"); // error here
}

barFn's parameter will be an intersection of types rather than union. The type never is a bottom type and it happens because there is no intersection between number, string and boolean.

Ideally you want to declare barFn as an intersection of the three function types. e.g:

function test2(barFn: Foo & Bar & Baz) {    
    barFn("a"); // no error here
}