从包装函数推断包装器的类型

Infer the type of a wrapper from wrapped function

objective是从wrapped function推断出wrapper的类型,即return从参数类型推断出wrapper的类型

我无法通过 infer 实现:

function wrap<T extends ((...args: any[]) => any) & { flag?: string }>(wrapped: T extends infer R ? R : never): T {
        let wrapper = function () {
            // ...
            return wrapped(...arguments);
        } as T;

        wrapper.flag = wrapped.flag;

        return wrapper;
}

let wrapped = (a: number, b: number) => `${a}{b}`;

// wrapper is ((...args: any[]) => any) & { flag?: string }
// should be ((a: number, b: number) => string & { flag?: string }
let wrapper = wrap(wrapped);

// foo is any
// should be string
let foo = wrapper('invalid');

在这种情况下如何在不明确指定包装器类型的情况下完成此操作?

给定类型 T extends infer R ? R : never 的值,编译器几乎肯定无法推断出对 T 有用的任何内容(因为它倾向于推迟评估这种条件类型,直到为时已晚T 进行推断)。由于该类型本质上等同于 T,因此我们将在下文中使用 T

我认为 wrap() 签名和实现应该是这样的:

function wrap<T extends ((...args: any[]) => any)>(
  wrapped: T & { flag?: string }): T & { flag?: string } {
  let wrapper = function () {
    // ...
    return wrapped(...arguments);
  } as T & { flag?: string };

  wrapper.flag = wrapped.flag;

  return wrapper;
}

也就是说,我们将只使用通用参数 T 来表示函数类型,而将 {flag?: string} 作为单独的非通用对象类型保留 T 将相交。您需要这样做的原因与可选属性的可分配性规则有关:

在 TypeScript 中,对象类型 缺少 属性 被认为可分配给具有 可选 [=54] 的对象=] 任何类型。例如,类型 {foo: string} 可分配给类型 {foo: string, bar?: number}。如果你有一个通用类型 T extends {foo: string, bar?: number}T 可能是 {foo: string},完全没有 bar 属性。

在你的例子中,如果你有一个通用类型 T extends ((...args: any[])=>any) & {flag?: string},并传入一个没有已知 flag 属性 的函数作为 T 的推理站点,编译器将推断 T 只是没有 flag 属性 的函数类型。然后函数输出也没有flag属性,郁闷

为了确保编译器将函数的输入和输出解释为具有可选的 flag 属性,我们需要在输入和输出中显式包含它,并且不要让编译器有机会忘记它。

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

Playground link to code