Repository: TypeScript
Issue: 💡 Yield Overrides
Labels: Suggestion,Awaiting More Feedback

# Suggestion

## 🔍 Search Terms

yield, any, generator, saga

## ✅ Viability Checklist

My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).

## ⭐ Suggestion

A new `YieldType` builtin generic type would instruct TypeScript to ignore the actual generator type in a `yield` expression, and to instead use whatever is used as generic parameter. The following would thus be true:

```ts
declare const val: YieldType<boolean>;

function* generator() {
  const res = yield val;
  assertType<res, boolean>();
}
```

## 📃 Motivating Example

Imagine you define an API where the user provides a generator and can interact with your application by the mean of "effects". Something akin to this:

```typescript
run(function* () {
  const counter = yield select(state => state.counter);
});
```

Under the current model, TypeScript is forced to type `counter` as `any`, because in theory the generator runner may return whatever value it wants to the `yield` expression. It can be somewhat improved by adding an explicit type to the generator, but then all `yield` expressions will return the same type - which is obviously a problem when doing multiple `select` calls, each returning a different value type:

```ts
run(function* (): Generator<any, any, number | string> {
  const counter = yield select(state => state.counter);
  const firstName = yield select(state => state.firstName);
});
```

Not only does it prevent return type inference and yield value checks, but it also unnecessarily widens the type of both `counter` and `firstName` into `number | string`. The alternative is to write the expressed values at callsite:

```ts
run(function* () {
  const counter: number = yield select(state => state.counter);
  const firstName: string = yield select(state => state.firstName);
});
```

But it then requires the user code to have an explicit requirement on the exact types, which prevents accessing various refactoring tools and generally leads to worse UX (after all that's why TS has type inference in the first place). The last option is to change the API:

```
run(function* () {
  const counter = yield* selectS(state => state.counter);
  const firstName = yield* selectS(state => state.firstName);
});
```

By using a `select` variant supporting `yield*`, library code would be able to define a return type that TS would be able to use. However, it requires all libraries to adopt a non-idiomatic runtime pattern just for the sake of TypeScript, which is especially problematic considering that it would lead to worse performances.

The main point is that in all those cases, the library code already knows what's the value returned by the `yield select(...)` expression. All we need is a way to transmit this information to TypeScript.

## 💻 Use Cases

### Redux-Saga (21.5K ⭐, 6 years old)

The `redux-saga` library **heavily** use generators (there are various reasons why async/await isn't enough, you can see them [here](https://github.com/redux-saga/redux-saga/issues/987#issuecomment-301039792)). As a result, its collection of [effects](https://redux-saga.js.org/docs/api) are all typed as `any`, leaving their users mostly unprotected on segments of code that would significantly benefit from types (for instance the `select` utility mentioned above can return any state slice from a larger one - typing the return as `any` leaves the door open to refactoring mistakes, typos, etc).

Additionally, under `noImplicitAny`, TS will put red underline on the whole `select` call, hiding important errors that could arise within the selector itself. In some of my own application files, **about a third of the lines** have red squiggles because of this issue:

![image](https://user-images.githubusercontent.com/1037931/119894543-0142d700-bf3d-11eb-8315-d2b7bd2f53dd.png)

### MobX (23.8K ⭐)

https://mobx.js.org/actions.html#using-flow-instead-of-async--await-

> Note that the flowResult function is only needed when using TypeScript. Since decorating a method with flow, it will wrap the returned generator in a promise. However, TypeScript isn't aware of that transformation, so flowResult will make sure that TypeScript is aware of that type change.

### Others

- [Ember Concurrency](https://github.com/machty/ember-concurrency) (663 ⭐)

  > Due to limitations in TypeScript's understanding of generator functions, it is not possible to express the relationship between the left and right hand side of a yield expression. As a result, the resulting type of a yield expression inside a task function must be annotated manually:

- [Effection](https://github.com/thefrontside/effection) (138 ⭐)
- [GenSync](https://github.com/loganfsmyth/gensync) (29 ⭐)

## 💻 Try it Out

I've implemented this feature (minus tests, and I'm not a TS contributor so some adjustments may be needed):

https://github.com/microsoft/TypeScript/compare/master...arcanis:mael/yield-override

Here's a playground: [Before](https://www.staging-typescript.org/play?ts=4.5.4#code/PTAEBUAsEsGdQCYFMDGAbAhgJwwF2gPYB2oA7gQK5oKgBGSoADlgQG7TI20CeoABliQIKADwC0sDAHMMfAFDJ02BgDMKRFPmKhYSNKlwAecAD4AFCqIAuUGdi48SGwGUHuJAEpQAXhMQPNgCa0HoI4NyMSMYmANxyciAQMPBwZJB4oLiQDBS6WJhENCgEyGSU1KBoBAQA1pXQNUhy0ETuWCoYKAyujqAA3nKgoKwYaBROoEQUALb0WHEAvvFqGlpEAFSgUkhESDi4BFhmXgNDxUT2OgCOFMoAaqPjPqDcIRW6+pp2bgy+Oj8AOhGYwY602ACYPHEhhhYHlcABRG6jQxTWZ7cweOzIwQPEEAGkyWHGUPiZ2IlzgAHFBI4sFAMEQAIwABmer1COj0Bm+vT+9kcQMeDD8rNJMLhe0RyLQhlo1X0jMxZmptLaDOZLMJuGJnjig1A50u2REeKe3hebxoHx5AvcPj8dqQQpBAIOriwLSkZiZADYPOLQLD4UjbrL7J6iFJlSazUhtbrSUsEmBnARpgxsmhIlh4AdhqMOL1cBEkLA5CXIqAAIKSrDSsPGQkIkSRTRCPwWgDa4GbrYMQgAuqAkCJ3IV4F2W233AhCeBhwB+IlPGwdNC6OJG3BBusN0bPQwAVXxmIdoEMd1PZmBNivI-77YQNlrIZlx8JdxMXj+fQWcSAA) | [After](https://www.staging-typescript.org/play?ts=4.6.0-pr-47548-5#code/PTAEBUAsEsGdQCYFMDGAbAhgJwwF2gPYB2oA7gQK5oKgBGSoADlgQG7TI20CeoABliQIKADwC0sDAHMMfAFDJ02BgDMKRFPmKhYSNKlwAecAD4AFCqIAuUGdi48SGwGUHuJAEpQAXhMQPNgCa0HoI4NyMSMYmANxyciAQMPBwZJB4oLiQDBS6WJhENCgEyGSU1KBoBAQA1pXQNUhy0ETuWCoYKAyujqAA3nKgoKwYaBROoEQUALb0WHEAvvFqGlpEAFSgUkhESDi4BFhmXgNDxUT2OgCOFMoAaqPjPqDcIRW6+pp2bgy+Oj8AOhGYwY602ACYPHEhhhYHlcABRG6jQxTWZ7cweOzIwQPEEAGkyWHGUPiZ2IlzgAHFBI4sFAMEQAIwABmer1COj0Bm+vT+9kcQMeDD8rNJMLhe0RyLQhlo1X0jMxZmptLaDOZLMJuGJnjig1A50u2REeKe3hebxoHx5AvcPj8dqQQpBAIOriwLSkZiZADYPOLQLD4UjbrL7J6iFJlSazUhtbrSUsEmBnARpgxsmhIlh4AdhqMOL1cBEkLA5CXIqAAIKSrDSsPGQkIkSRTRCPwWgDa4GbrYMQgAuqAkCJ3IV4F2W233AhCeBhwB+IlPGwdNC6OJG3BBusN0bPQwAVXxmIdoEMd1PZmBNivI-77YQNlrIZlx8JdxMXj+fQWcSAA) ✨

cc @Andarist