Skip to content
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

yield operator in generator functions has any return type #26959

Closed
4 tasks done
freddi301 opened this issue Sep 7, 2018 · 12 comments · Fixed by #30790
Closed
4 tasks done

yield operator in generator functions has any return type #26959

freddi301 opened this issue Sep 7, 2018 · 12 comments · Fixed by #30790
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@freddi301
Copy link

freddi301 commented Sep 7, 2018

Search Terms

  • generator
  • yield

Suggestion

  • Correct return type on yield operator
  • Correct return type on yield* operator
  • Correct type for generator function return

Use Cases

better type safety

redux-saga
koa.js

Example

now:

function* f() { // :IterableIterator<number>
  const x = yield 0; // x is any
  const y = yield* [2, 3, 4]; // y is any
}

desired:

function* f1(): TypedIterableIterator<number, number> {
  const x = yield 0; // x is number
  const y = yield* [2, 3, 4]; // y is number
  yield x + y;
}
function* f2() { // :IterableIterator<number, number>
  const x: number = yield 0;
  const y: number = yield* [2, 3, 4];
  yield x + y;
}
function* f3(): TypedIterableIterator<number> {
  const x = yield 0; // x is number
  const y = yield* [2, 3, 4]; // y is number
  yield x + y;
}

interface TypedIterableIterator<T, N = any> {
  next(value: N): T;
}

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. new expression-level syntax)
@freddi301 freddi301 changed the title yield operator in generator functions has any return type yield operator in generator functions has any return type Sep 7, 2018
@gwicksted
Copy link
Contributor

Can you provide an example? I've been using yield on generators - you specify the return type of the function as IterableIterator<T>. Working example:

export function* findParents(element: HTMLElement): IterableIterator<HTMLElement> {
    if (!element) {
        return undefined;
    }

    let parent = element.parentElement;
    while (parent) {
        yield parent;
        parent = parent.parentElement;
    }
}

@ghost
Copy link

ghost commented Sep 7, 2018

I think this issue is that a yield expression is of type any.

function* f() {
    const x = yield 0; // x is any
}

@ghost ghost added the Suggestion An idea for TypeScript label Sep 7, 2018
@gwicksted
Copy link
Contributor

@andy-ms ah now I understand.

In that case, my understanding is this point needs to be addressed:

Correct return type on yield operator

But this point does not (see proof below):

Correct type for generator function return

I'd like to add this point as well:

  • Correct return type on yield* operator

Given this example playground:

function* f() {
    const x = yield 0;
    const y = yield* [2, 3, 4];
}
  • [GOOD] f return type is already implicitly typed as IterableIterator<number>
  • [FIXME] x type should be implicitly typed as number (not any)
  • [FIXME] y type should be implicitly typed as IterableIterator<number> (not any)

@ghost
Copy link

ghost commented Sep 7, 2018

@gwicksted The value of a yield expression isn't necessarily the value being yielded. It depends on who's using the generator, which is why this is complicated. If the user is a for-of loop (which 99% of the time it is) this will simply be void. Some info here.

@gwicksted
Copy link
Contributor

@andy-ms please forgive my ignorance! I hadn't used that syntax before today. I assumed (incorrectly) that let a = yield 5; was equivalent to let a: number; yield (a = 5); which is not the case! yield in JS behaves much like yield in Python.

Other than analyzing the all the consumers of the iterator looking for each call to next to auto-type each yield's return value (good luck!), I expect this should be declared upfront by the generator's return value type declaration.

so instead of just:

interface Iterator<T> {
    next(value?: any): IteratorResult<T>;
}
interface IterableIterator<T> { }

You could have:

interface Iterator<T,TNext = any> {
    next(value?: TNext): IteratorResult<T>; // IteratorResult may also need to change?
}
interface IterableIterator<T,TNext = any> { }

Then define your generator as:

function* f(): IterableIterator<number, string> {
    const str = yield 5; // str is string
}

const iter = f();
const num = iter.next("hello").value; // next's argument is now type checked

Then TS needs to auto type yield's return type to the 2nd generic parameter of the generator's return type. Shouldn't be too difficult since typing of yield's parameter is already done via the 1st generic parameter of the generator's return type.

Of course there are more advanced cases where multiple yield expressions are being passed different types but I think those are even more rare and they could simply use a union of all types expected.

@freddi301
Copy link
Author

@gwicksted examples added (with your suggestion)

@andy-ms great suggestions

I think this could be an opt-in type refinement for case where typing on generators is needed on values supplied into it.

@gwicksted
Copy link
Contributor

@freddi301 this type definition uses the recently added default generic type notation to make it an opt-in feature without breaking compatibility anywhere. If you don't provide the 2nd type arg, it defaults to any which is effectively what it is today:

interface IterableIterator<T,TNext = any> { }

@aleclarson
Copy link

aleclarson commented Sep 20, 2018

An interesting way to support typed "yield returns" is to have Typescript use the type expected by the generator. See the following example:

function* foo(a: boolean) {
  // The `b` and `c` variables must be typed
  const b: number = yield 0
  const c: string = yield b == 0 ? 0 : 1
}

// The inferred generator type
type Foo = (a: boolean) => {
  next(value: number | string): IteratorResult<0 | 1>
  return(): { value: undefined, done: true }
  throw(e?: any): { value: undefined, done: true }
}

typeof foo extends Foo // => true

let gen = foo(true)
gen.next() // [ts] Expected a number|string, got undefined

If Javascript had immutable generators built-in, this would probably be easier. I'm trying to get this library working with Typescript, but I don't think I can until "yield returns" can be typed.

@falsandtru
Copy link
Contributor

related: #2983

@Igorbek
Copy link
Contributor

Igorbek commented Oct 18, 2018

My proposal for generator type definition: #2983 (comment)

@weswigham weswigham added the Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. label Nov 6, 2018
@yortus
Copy link
Contributor

yortus commented Mar 5, 2019

For anyone interested, the issues and ideas raised here were covered in quite some detail in the comment thread in #2873.

@freddi301
Copy link
Author

Why the maintainers just happily closed the issue if it was addressed only by 20%?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants