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

Generators/Async Generators with TReturn type argument which extends undefined have a return method with a non-optional argument in 3.6.2 #33357

Open
brainkim opened this issue Sep 10, 2019 · 7 comments
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@brainkim
Copy link

brainkim commented Sep 10, 2019

TypeScript Version: 3.6.2

Search Terms:
generator iterator return argument optional required

Code

function *gen(): Generator<number, number | undefined> {
  yield 1;
  return 2;
}

gen().return();

Expected behavior:
The argument passed to the return method should be optional. Not sure if this is possible with typescript.

Actual behavior:

file.ts(6,7): error TS2554: Expected 1 arguments, but got 0.
6 gen().return();
        ~~~~~~~~

  typescript/lib/lib.es2015.generator.d.ts:26:12
    26     return(value: TReturn): IteratorResult<T, TReturn>;
                  ~~~~~~~~~~~~~~
    An argument for 'value' was not provided.

Playground Link:

Playground is not available for 3.6.2

Related Issues:
#30790 Strongly typed iterator PR.
#32682 Other (async) iterator usability issues.
#33239 Trouble implementing async iterator interface in 3.6.2
#33241 Unable to use done property of IteratorResult to narrow types in typescript 3.6.2

@brainkim brainkim changed the title Generators/Async Generators with undefined TReturn argument have a return method with a non-optional argument in 3.6.2 Generators/Async Generators with TReturn type argument which extends undefined have a return method with a non-optional argument in 3.6.2 Sep 10, 2019
@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Sep 16, 2019
@rbuckton
Copy link
Member

If a generator indicates it returns number, calling gen.return() without an argument would violate that constraint, as you could force it to return undefined, which is not assignable to number. We currently have no way to make an argument optional based on a type variable that would work in all cases. We could approximate that behavior with conditional types and tuples, but that would cause issues with assignability.

We currently allow you to call next without a value only because the argument you provide the first time you call next on a generator is ignored, but we have no way to restrict subsequent calls to next from eliding a required argument.

@brainkim
Copy link
Author

brainkim commented Sep 18, 2019

Did some more playing around. Replacing undefined with void in the example above seems to make the argument passed to return optional:

function *gen(): Generator<number, number | void> {
  yield 1;
  return 2;
}

gen().return();

Was this intentional? This solution makes sense to me, because the TReturn type describes the return value of the generator type and the void type has additional behavior related to implicit returns.

@brainkim
Copy link
Author

brainkim commented Oct 1, 2019

If the optional-argument behavior of void is intended, I’m okay with this issue being closed as “Working as Intended.” However, the wildly outdated TypeScript spec states:

NOTE: We might consider disallowing declaring variables of type Void as they serve no useful purpose. However, because Void is permitted as a type argument to a generic type or function it is not feasible to disallow Void properties or parameters.

This feels like a valid use-case for void parameters.

Related issues: #29131

@rbuckton
Copy link
Member

rbuckton commented Oct 2, 2019

Yes, the behavior of void being treated as an optional argument at the end of an argument list is intended and is a recent addition.

@brainkim
Copy link
Author

brainkim commented Oct 6, 2019

Using void works, but it doesn’t work with the default any, which means that code like the following doesn’t compile:

function* createGen(): Generator<number> {
  yield 1;
  yield 2;
}

const gen = createGen();
console.log(gen.next()); // {value: 1, done: false}
gen.return(); // TReturn is any but return still expects an argument.

If the return type of the generator is any, shouldn’t I be allowed to call return without arguments? This would mimic the way that only functions with a return type of void or any can have an implicit return. This is simply an idiomatic usage of generators which is disallowed by the current typings. I can use the void trick as above, but sometimes I just want to use any because I want to quickly access the IteratorResult value without checking the done property, and void cannot be easily narrowed or eliminated from type unions.

function* createGen(): Generator<number, void> {
  yield 1;
  yield 2;
}

const gen = createGen();
console.log(Math.max(gen.next().value!, 0)); // non-null assertion won’t eliminate void

I stand by my comments on the original PR that TReturn should necessarily extend undefined precisely because of situations like this. You can’t use a non-null assertion operator (!) to eliminate void from a type union, so I’m stuck calling gen.return(undefined) when TReturn is any which feels completely wrong.

@brainkim
Copy link
Author

brainkim commented Oct 6, 2019

We could approximate that behavior with conditional types and tuples, but that would cause issues with assignability.

@rbuckton
Why is this not a valid solution? Can you show an example of assignability issues? I feel like a theme with a lot of iterator/generator issues (I am probably the only one who cares 😓) is that you (rbuckton specifically) seem to know a lot about these “issues with assignability” but never give code examples. The example above (calling gen.return without arguments on a generator with a default TReturn) seems like something that should just work.

@brainkim
Copy link
Author

Related issue about making undefined parameters optional:
#12400

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.
Projects
None yet
Development

No branches or pull requests

3 participants