属性名称的类型定义,用于返回void的函数。

huangapple go评论61阅读模式
英文:

Type definition for property names of function returning void

问题

I'm currently writing a small RPC library for a project I'm working on. To ensure some level of correctness I'm using typescript to do some typechecking. I got the initial implementation working and while doing some refactoring I ran into a problem with some of the conditional types.

The idea is to have an RPC service definition along the lines of the interface below and be able to separate calls that return a value (call) to those that don't (notify).

export default interface ExampleService {
  add(a: number, b: number): number;
  sub(a: number, b: number): number;
  log(message: string): void;
}

Up to now I was returning Promises and it was working fine, but switching the return type to literal/void the types are no longer correct

type CallMethods<T> = {
  [K in keyof T]: T[K] extends (...args: any[]) => void ? never : K;
}[keyof T];

type NotifyMethods<T> = {
  [K in keyof T]: T[K] extends (...args: any[]) => void ? K : never;
}[keyof T];

I'm expecting CallMethods<ExampleServer> to be "add" | "sub" and NotifyMethods<ExampleService> to be "log."

But with this type definition, I'm getting CallMethods<ExampleService> as "never" and NotifyMethods<ExampleService> as "add" | "sub" | "notify"...

My assumption is that it has to do with substitutability as explained in https://github.com/microsoft/TypeScript/wiki/FAQ#why-are-functions-returning-non-void-assignable-to-function-returning-void

英文:

I'm currently writing a small RPC library for a project I'm working on. To ensure some level of correctness I'm using typescript to do some typechecking. I got the initial implementation working and while doing some refactoring I ran into a problem with some of the conditional types.

The idea is to have an RPC service definition along the lines of the interface below and be able to separate calls that return a value (call) to those that don't (notify).

export default interface ExampleService {
  add(a: number, b: number): number;
  sub(a: number, b: number): number;
  log(message: string): void;
}

Up to now I was returning Promises and it was working fine, but switching the return type to literal/void the types are no longer correct

type CallMethods&lt;T&gt; = {
  [K in keyof T]: T[K] extends (...args: any[]) =&gt; void ? never : K;
}[keyof T];

type NotifyMethods&lt;T&gt; = {
  [K in keyof T]: T[K] extends (...args: any[]) =&gt; void ? K : never;
}[keyof T];

I'm expecting CallMethods&lt;ExampleServer&gt; to be &quot;add&quot; | &quot;sub&quot; and NotifyMethods&lt;ExampleService&gt; to be &quot;log&quot;

But with this type definition I'm getting CallMethods&lt;ExampleService&gt; as never and NotifyMethods&lt;ExampleService&gt; as &quot;add&quot; | &quot;sub&quot; | &quot;notify&quot;...

My assumption is that it has to do with substituability as explained in https://github.com/microsoft/TypeScript/wiki/FAQ#why-are-functions-returning-non-void-assignable-to-function-returning-void

答案1

得分: 0

可以这样写(参见 [playground][1]

```ts
export default interface ExampleService {
  add(a: number, b: number): number;
  sub(a: number, b: number): number;
  log(message: string): void;
}

type CallMethods&lt;T&gt; = {
  [K in keyof T]: T[K] extends (...args: any[]) =&gt; (infer R extends NonNullable&lt;infer Z&gt;) ? K : never;
}[keyof T];

type NotifyMethods&lt;T&gt; = {
  [K in keyof T]: T[K] extends (...args: any[]) =&gt; (infer R extends NonNullable&lt;infer Z&gt;) ? never : K;
}[keyof T];

type CallMethodsList = CallMethods&lt;ExampleService&gt;
type NotifyMethodsList = NotifyMethods&lt;ExampleService&gt

为了检测 void 返回类型,我添加了 NonNullable 检查。这种解决方案的缺点是 nullundefined 的返回类型也会被分类为 NotifyMethods,但我目前找不到其他解决办法。


<details>
<summary>英文:</summary>

You can write it like this (see [playground][1])

```ts
export default interface ExampleService {
  add(a: number, b: number): number;
  sub(a: number, b: number): number;
  log(message: string): void;
}

type CallMethods&lt;T&gt; = {
  [K in keyof T]: T[K] extends (...args: any[]) =&gt; (infer R extends NonNullable&lt;infer Z&gt;) ? K : never;
}[keyof T];

type NotifyMethods&lt;T&gt; = {
  [K in keyof T]: T[K] extends (...args: any[]) =&gt; (infer R extends NonNullable&lt;infer Z&gt;) ? never : K;
}[keyof T];

type CallMethodsList = CallMethods&lt;ExampleService&gt;;
type NotifyMethodsList = NotifyMethods&lt;ExampleService&gt;;

In order to detect void return type I've added NonNullable check. Downside of this solution is that return type of null or undefined would also classify as NotifyMethods but I couldn't find other way around it for now.

答案2

得分: 0

以下是代码部分的翻译:

正如您所指出的,的确是因为非 void 返回函数可以分配给 void 返回函数。要解决这个问题,只需检查返回类型 only。不要在这里比较整个函数类型。

比较类型是否为 void 使用 extends 不会产生这种行为。

这也适用于函数的返回类型可为可空类型(包括 null 或 undefined)。

Playground

英文:

As you noted, it is indeed because non-void returning functions are assignable to void returning functions. To get around this, simply check the return type only. Do not compare function types as a whole here.

type CallMethods&lt;T&gt; = {
  [K in keyof T]: T[K] extends (...args: any[]) =&gt; any ? ReturnType&lt;T[K]&gt; extends void ? never : K : never;
}[keyof T];

type NotifyMethods&lt;T&gt; = {
  [K in keyof T]: T[K] extends (...args: any[]) =&gt; any ? ReturnType&lt;T[K]&gt; extends void ? K : never : never;
}[keyof T];

Comparing types to void with extends does not have this behavior.

type Y = number extends void ? true : false;
//   ^? false

This also works if the return type of a function is nullable (includes null or undefined).

Playground

huangapple
  • 本文由 发表于 2023年3月8日 19:20:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/75672359.html