Skip to content

Only resolve source return type when actually needed during inference when generic is used #58926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
6 tasks done
finom opened this issue Jun 19, 2024 · 3 comments
Closed
6 tasks done
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status. Suggestion An idea for TypeScript

Comments

@finom
Copy link

finom commented Jun 19, 2024

πŸ” Search Terms

"is referenced directly or indirectly in its own type annotation.ts(2502)"
"self-referencing types"
"circular types"
...

βœ… Viability Checklist

⭐ Suggestion

Thanks to @ahejlsberg the issue #58616 was solved and the change works perfectly. Unfortunately my last comment was written too late and got lost among the other comments and issues. The code like this now works:

function foo(arg: Parameters<typeof bar>[0]) {
  return arg;
}

function bar(arg: string) {
  return foo(arg);
}

But in case if generics are used the arg wouldn't be recognised as expected, even if we still don't need to infer the return type.

πŸ“ƒ Motivating Example

function withCallback<T, R>(arg: T, handler: (arg2: T) => R) {
  console.log(arg);
  return handler;
}

const foo = withCallback('hello', (arg) => {
  return bar(arg);
});

function bar(arg: Parameters<typeof foo>[0]) {
   console.log(arg);
}
image

As you can see the requirement is very similar: we don't need to infer the function type fully, we just need to get the parameters type, and at this example the only argument should clearly be inferred as a string.

πŸ’» Use Cases

I work on a back-end library that relies on service-controller-repository pattern. The idea is to return service method call result from a controller, and to make the service method to accept parameters that are inferred from parameters of the controller.

class X {
  static foo = withCallback('hello', (arg) => {
    return Y.bar(arg);
  });
}

class Y {
  static bar(arg: Parameters<typeof X.foo>[0]) {
    console.log(arg);
  }
}

There is what I actually trying to achieve, it's a bit more complex than the previous examples:

class Service {
  static doSomething(body: VovkControllerBody<typeof Controller.doSomething>) {
    console.log(body);
  }
}

class Controller {
  static doSomething = withZod(z.object({ a: z.string() }), null, async (req) => {
    const body = await req.json(); // body has type of { a: string }
    return Service.doSomething(body); 
  });
}

VovkControllerBody infers the JSON body from Controller.doSomething argument defined by withZod.

The only workaround here is to have Zod schemas somewhere outside of the classes. But it requires to define extra variables and imports making the code less readable in large apps.

Thanks!

@finom
Copy link
Author

finom commented Jun 19, 2024

@ahejlsberg, I hope you can spare a moment to review this. πŸ™ I'm deeply grateful for all your work!

@DanielRosenwasser DanielRosenwasser added Suggestion An idea for TypeScript Needs Investigation This issue needs a team member to investigate its status. labels Jun 20, 2024
@DanielRosenwasser DanielRosenwasser added this to the TypeScript 5.6.0 milestone Jun 20, 2024
@ahejlsberg
Copy link
Member

I don't think anything new is needed here. This seems to work:

function withCallback<T, U extends (arg: T) => any>(arg: T, handler: U) {
  console.log(arg);
  return handler;
}

const foo = withCallback('hello', (arg) => {
  return bar(arg);
});

function bar(arg: Parameters<typeof foo>[0]) {
   console.log(arg);
}

@finom
Copy link
Author

finom commented Jun 27, 2024

@ahejlsberg, you were right! I rewrote the code based on your example and tested it across multiple modules. The change you made works perfectly and is a crucial improvement. It makes the code more readable and reduces the number of required variables. Confidence in the library I work on finally reached the point when I can promote it as alternative to other RPC libraries. Thank you!

@finom finom closed this as completed Jun 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants