有没有办法提示 TS 泛型参数的出现用于泛型推理?

Is there a way to hint TS which occurrence of generic parameter to use for generic inference?

有没有办法提示 TS 泛型参数的出现用于泛型推理?

type Handler = <T>( // <-- if T is unspecified in method definition and call how can i tell ts to infer it to Ta from Params<Ta> and not Tb of the input?
  func: (params: Params<Ta>) => any,
  input: Tb
) => string;

代码示例

type Params<T = Record<string, unknown>> = {
  p: T;
  // other metadata
}

type Handler = <T>( // <-- if T is unspecified how can i tell ts to infer it from Params<T>?
  func: (params: Params<T>) => any,
  input: T
) => string;

type Config = {
  handler: Handler
}

const c: Config = {
  handler: (params, input) => 'ok' // <-- no possibility to pass/state the generic parameter here
}


type AB = {a:number, b:number}
const myHandler = (params: Params<AB>) => params.p.a + params.p.b;

c.handler(myHandler, { a:1, b:2, c:"unexpected" }) // <-- T is inferred to { a:1, b:2, c:"unexpected" }
c.handler<AB>(myHandler, { a:1, b:2, c:"unexpected" }) // <-- this is desired behaviour, can it be done without stating the T = AB explicitly?

ts playground link

可以通过改变类型来实现:

type Params<T = Record<string, unknown>> = {
  p: T;
  // other metadata
}

type ParamsExtract<T extends (params: Params<any>) => any> = T extends (params: Params<infer U>) => any ? U : never;

type Handler = <Fn extends (params: Params<any>) => any>( // <-- if T is unspecified how can i tell ts to infer it from Params<T>?
  func: Fn,
  input: ParamsExtract<Fn>
) => string;

type Config = {
  handler: Handler
}

const c: Config = {
  handler: (params, input) => 'ok' // <-- no possibility to pass/state the generic parameter here
}


type AB = {a:number, b:number}
const myHandler = (params: Params<AB>) => params.p.a + params.p.b;

c.handler(myHandler, { a:1, b:2, c:"unexpected" }) // error

因此泛型成为函数类型,并且从中提取参数。参见 here

您正在寻找 microsoft/TypeScript#14829 请求的功能,“非推理类型参数用法”:

Often with generics, there will be some locations where a type parameter should be inferrable from usage, and other places where the type parameter should only be used to enforce typechecking. This comes up in a variety of contexts.

那里的提议是想出一些 NoInfer<T> 语法,这等同于 T 除了编译器会 推断 T 从那个位置。这是您要求的另一面,但它们都将实现相同的目标: input 属性 类型上的 NoInfer<T> 就像“PleaseInfer<T> " 在 func() 方法的输入上。


虽然 NoInfer<T> 没有 官方 版本,但 GitHub 问题中提到的一些实现适用于某些用例。 One I sometimes recommend 是:

type NoInfer<T> = [T][T extends any ? 0 : never];

这是通过利用编译器 延迟 评估 distributive conditional types 当检查的类型是未解析的泛型时。它无法“看到”NoInfer<T> 将计算为 T,直到 T 是某个特定的已解析类型,例如在推断出 T 之后。


有了这些,如果我将您的示例代码更改为:

type Handler = <T>(
  func: (params: Params<T>) => any,
  input: NoInfer<T>
) => string;

那么您的示例将按预期工作:

const c: Config = {
  handler: (func, input) => func({ p: input }) // okay,
}
   
type AB = { a: number, b: number }
const myHandler = (params: Params<AB>) => params.p.a + params.p.b;

c.handler(myHandler, { a: 1, b: 2, c: "unexpected" }); // error!
// ------------------------------> ~~~~~~~~~~~~~~~
// Object literal may only specify known properties, 
// and 'c' does not exist in type 'AB'

Playground link to code