为什么 TypeScript 在函数引用上选择不同的重载与 lambda-warpped 调用?

Why does TypeScript pick a different overload on function reference vs. a lambda-warpped call?

为什么 TS 选择最后一个重载变体,而不是最后一个情况中的中间 f(x: string): string 变体,它给出的类型是 (string | null)[] 而不是 string[]

(TypeScript 版本 4.4.4)

function f(x: null): null;
function f(x: string): string;
function f(x: string | null): string | null;

function f(x: string | null): string | null {
  if (x === null) return x;
  return `${x}`;
}

f(null); // null
f('foo'); // string
f(Math.random() > 0.5 ? 'bar' : null); // string | null

['a', 'b', 'c'].map(x => f(x)); // string[]
['a', 'b', 'c'].map(f); // (string | null)[]

这是 TypeScript 的设计限制。请参阅 microsoft/TypeScript#35501 以获得权​​威答案。

Overload resolution is only guaranteed to happen properly when you do not require any type inference to happen first. If you call the overloaded function directly with arguments of known types, that will work as expected. But any manipulation of the overloaded function signature where the compiler needs to infer types will use some heuristic to eagerly choose or synthesize a single call signature from the list of overloads without regard for which signature would actually be appropriate if called directly. This tends to be the last call signature (or maybe the first sometimes? according to this comment).

在你的例子中,array map() method needs to infer a generic type parameter U对应回调函数的return类型。编译器急切地选择了最后一个调用签名,看到它的 return 类型是 string | null,并随之而来:

['a', 'b', 'c'].map(f); // (string | null)[]

这个问题也出现在conditional type inference including the popular Parameters<T> and ReturnType<T> utility types:

type FParam = Parameters<typeof f>[0]; // type FParam = string | null
type FReturn = ReturnType<typeof f>; // type FReturn = string | null

对于您的示例,如果您想调用 map() 而不在 x => f(x) 中包装 f,您可以通过手动指定泛型类型参数来回避问题:

['a', 'b', 'c'].map<string>(f); // string[]

现在不需要类型推断了...编译器知道 Ustring,因此它需要将 f 解释为 [=24= 类型的值].在没有推理的情况下,重载决议在这里按预期发生,因此选择了第二个重载并且没有编译器错误发生。当然,由于您将 U 指定为 string,映射操作的 return 类型 string[].

Playground link to code