推断时泛型类型丢失

Generics type loss while infering

我在向交集类型添加 属性 时遇到一个奇怪的问题,通用参数丢失。考虑以下因素:

type Service = Record<string, any>;
type Dependencies = Record<string, Service>;
type Parameters = Record<string, any>;
type HandlerFunction<D, P, R> = (d: D, p: P) => R;

type ServiceInitializer<D extends Dependencies, S = any> = {
  type: string; // ----<HERE>----
} & ((dependencies?: D) => Promise<S>);

function handler<D extends Dependencies, P extends Parameters, R>(
  handlerFunction: HandlerFunction<D, P, R>,
  name?: string,
  dependencies?: string[],
): ServiceInitializer<D, Promise<(p: P) => R>>;

const x = handler(<T>(d: any, { a }: { a: T }): T => a);

如果我删除标有 ----<HERE>---- 标志的行,我最终会得到以下类型的 x 常量,它保​​留了通用类型:

const x: <T>(dependencies?: any) => Promise<Promise<(p: {
    a: T;
}) => T>>

但是如果我离开那条线,我就松了它,Tunkown 取代:

const x: ServiceInitializer<Record<string, Record<string, any>>, Promise<(p: Record<string, any>) => unknown>>

有没有办法通过仍然允许混合函数和对象属性来保留泛型类型?

我认为这只是对 higher order type inference from generic functions introduced in TypeScript 3.4, as implemented by microsoft/TypeScript#30215 支持的限制。在 TypeScript 3.4 之前,编译器在推断参数或 return 类型时 总是 丢失泛型函数类型,方法是指定类型参数及其约束。因此 <T>(x: T)=>T 类型的函数将“折叠”为 (x: unknown)=>unknown,导致 [unknown] 的参数元组和 unknown.[=26 的 return 类型=]

TypeScript 3.4 添加了支持,因此 有时 可以保留泛型类型参数,但这只发生在非常特殊的情况下:

When an argument expression in a function call is of a generic function type, the type parameters of that function type are propagated onto the result type of the call if:

  • the called function is a generic function that returns a function type with a single call signature,
  • that single call signature doesn't itself introduce type parameters, and
  • in the left-to-right processing of the function call arguments, no inferences have been made for any of the type parameters referenced in the contextual type for the argument expression.

在您的情况下,您显然 运行 与第一个(粗体)要点冲突。仅当被调用函数 (handler()) 具有 return 类型且其本身是具有“单一调用签名”的函数类型时,才会保留类型参数。

如果您检查代码(this file 的第 20041 行附近的某处,它太大 link 无法直接在 GitHub 中),您会看到考虑的内容“单一呼叫签名”非常严格:

// If type has a single call signature and no other members, return that signature.
// Otherwise, return undefined.
function getSingleCallSignature(type: Type): Signature | undefined { ... }

看到了吗? “没有其他成员”。由于 handler() 的 return 类型是 ServiceInitializer<D, Promise<(p: P) => R>>,因此在您不向其添加 type 成员的版本中,这仅算作“单一调用签名”。一旦你添加它,它就不能成为“单一调用签名”,你就会得到 pre-3.4 的行为。

只是为了让不使用您的特定代码的人清楚,最小示例如下所示:

declare function f<A extends any[], R>(f: (...a: A) => R): ((...a: A) => R);
const g = f(<T>(x: T) => x) // const g: <T>(x: T) => T

declare function h<A extends any[], R>(f: (...a: A) => R): ((...a: A) => R) & { x: 0 };
const i = h(<T>(x: T) => x) // const i: ((x: any) => any) & { x: 0; }

函数 f() return 是一个“单一调用签名”,因此传递给它的任何通用函数在 return 上保持通用。但是函数 h() return 是一个带有额外成员的函数,因此传递给它的任何泛型函数在 return.

时都变成非泛型的

我不知道是否有解决方法;我还没有找到一个。对高阶类型推断的支持相当脆弱,所以如果这是不可能的,我也不会感到惊讶。

Playground link to code