GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Boolean literal types and return type propagation for generators · Issue #2983 · microsoft/TypeScript

Boolean literal types and return type propagation for generators #2983

New issue
Copy link
New issue
Copy link
Closed
#9407
#30790
Closed
Boolean literal types and return type propagation for generators#2983
#9407
#30790
Copy link
Assignees
rbuckton
Labels
CommittedThe team has roadmapped this issueThe team has roadmapped this issueFixedA PR has been merged for this issueA PR has been merged for this issueSuggestionAn idea for TypeScriptAn idea for TypeScript
Milestone
TypeScript 3.6.0

Description

@JsonFreeman
Jason Freeman (JsonFreeman)
opened on May 1, 2015
Issue body actions

This suggestion has a few pieces:

  1. Implement boolean literal types for true and false, in a fashion similar to Singleton types under the form of string literal types #1003
  2. Implement type guards for booleans. Essentially, control flow constructs like if-else, while, for, etc would be subject to a type guard if their guard expression is a boolean.
  3. Now we can update generators to have a next method that returns { done: false; value: TYield; } | { done: true; value: TReturn; }, where TYield is inferred from the yield expressions of a generator, and TReturn is inferred from the return expressions of a generator.
  4. Iterators would return { done: false; value: TYield; } | { done: true; value: any; } to be compatible with generators.
  5. for-of, spread, array destructuring, and the type yielded by yield* would only pick the value associated with done being false.
  6. The value of a yield* expression would pick the value associated with done being true.
  7. Introduce a Generator type that can be used to track the desired type of a yield expression. This would be TNext, and would be the type of the parameter for the next method on a generator.

The generator type would look something like this:

interface Generator<TYield, TReturn, TNext> extends IterableIterator<TYield> {
    next(value?: TNext): IteratorYieldResult<TYield> | IteratorReturnResult<TReturn>;
    throw(exception: any): IteratorYieldResult<TYield> | IteratorReturnResult<TReturn>;
    return(value: any): IteratorYieldResult<TYield> | IteratorReturnResult<TReturn>;
    [Symbol.iterator](): Generator<TYield, TReturn, TNext>;
    [Symbol.toStringTag]: string;
}
Reacted by Kitson Kelly, Felix Becker, Shayan Sayahi, Ziv, Simon Friis Vindum, Fredrik Norén, yeerkkiller1, Adam Pietrasiak, Kirill Agalakov, Maxim Krivchikov and 120 more
Reacted by Han Seoul-Oh, Bnaya Peretz, Marshall Bowers, Patrik Lundqvist, Tylor Steinberger, Gilbert Gilb's, Marius Gundersen, Carl Patenaude-Poulin, eucaos, Gabe Johnson and 34 more

Activity

Next
  1. Jason Freeman (JsonFreeman)
    added
    SuggestionAn idea for TypeScriptAn idea for TypeScript
    on May 1, 2015
  2. Jason Freeman (JsonFreeman)
    added
    In DiscussionNot yet reached consensusNot yet reached consensus
    on Jul 14, 2015
  3. ahejlsberg commented on Jun 29, 2016

    @ahejlsberg
    Anders Hejlsberg (ahejlsberg)
    on Jun 29, 2016
    Member
    More actions

    Boolean literal types are now available in #9407. Once that is merged we can update the return type of generators and iterators.

    Reacted by Jason Freeman, Troy Gerwien, Brandon Bloom, tetsuo, Kirill Agalakov, Tylor Steinberger, Patrik Lundqvist, Artem Tyurin, soerenBoisen, Piyush Katariya and 3 more
    Reacted by Kitson Kelly, Troy Gerwien, Shayan Sayahi, tetsuo, Kirill Agalakov, Tylor Steinberger, Patrik Lundqvist, Flavio Vilante and 流浪大法师
  4. s-panferov commented on Jul 5, 2016

    @s-panferov
    Stanislav Panferov (s-panferov)
    on Jul 5, 2016
    More actions

    Does it allow to type async-like yield expressions in libraries like co or react-saga?

    // getUser(): Promise<User>
    let user = yield getUser()
    // user: ?
    Reacted by Violet Martin, monnef, Dan Wilt, Crhistian Ramirez, Gitowiec, Anton Novik, Richard Simpson, Ashlynne Mitchell, Laurin, Shane Yu and 14 more
  5. yortus commented on Jul 5, 2016

    @yortus
    Troy Gerwien (yortus)
    on Jul 5, 2016
    Contributor
    More actions

    Stanislav Panferov (@s-panferov) unfortunately not. You are talking about the async-runner style of generator used by things like co right?

    The generator proposal (#2873) doesn't offer much typing support for async-runner generators. In particular:

    • All yield expressions are typed as any, as you can see in the example comments below. This is actually a very complex problem and I tried tackling it with some ideas which are all there in Proposal for generators design #2873. This won't change with Number, enum, and boolean literal types #9407. And the TNext type in the OP above won't solve this either, since in an async runner there is generally not a single TNext type, but rather the type of each yield expression is a function of the type of that yield's operand (eg Promise<T> maps to T, Array<Promise<T>> maps to T[], etc). In particular the type of each yield expression is generally unrelated to the types of the other yield expressions in the body in an async-runner generator function.
    • The compiler infers a single TYield type that must be the best common type among all the yield operands. For async runners there often is no such best common type, so such generators often won't compile until at least one yield operand is cast to any. E.g. the function *bar example below doesn't compile for this reason.
    • Return types of generators are currently not tracked because without boolean literals, they can't be separated from the TYield type. This is what will be solvable once Number, enum, and boolean literal types #9407 lands.

    Example code:

    interface User {id; name; address}
    interface Order {id; date; items; supplierId}
    interface Supplier {id; name; phone}
    declare function getUser(id: number): Promise<User>;
    declare function getOrders(user: User): Promise<Order[]>;
    declare function getSupplier(id: number): Promise<Supplier>;
    
    function* foo() {
        let user = yield getUser(42); // user is of type 'any'
        let user2 = <User> user;
        return user2; // This return type is not preserved
    }
    
    function* bar() { // ERROR: No best common type exists among yield expressions
        let user = yield getUser(42);       // user has type 'any'
        let orders = yield getOrders(user); // orders has type 'any'
    
        let orders2 = <Order[]> orders;
        let suppliers = yield orders2.map(o => getSupplier(o.supplierId)); // suppliers has type 'any'
    
        let suppliers2 = <Supplier[]> suppliers;
        return suppliers2; // This return type is not preserved
    }
    Reacted by JeroenNelen, Simon Friis Vindum, Aleksandr Chernyavenko, timm-aym, Dan Wilt, Berkay Yıldız, roman, Yijian Wang, 流浪大法师, Artur Udalov and 1 more
  6. s-panferov commented on Jul 5, 2016

    @s-panferov
    Stanislav Panferov (s-panferov)
    on Jul 5, 2016
    More actions

    Troy Gerwien (@yortus) big thanks for the clarification!

    All yield expressions are typed as any, as you can see in the example comments below. This is actually a very complex problem and I tried tackling it with some ideas which are all there in #2873. This won't change with #9407. And the TNext type in the OP above won't solve this either, since in an async runner there is generally not a single TNext type, but rather the type of each yield expression is a function of the type of that yield's operand (eg Promise maps to T, Promise[] maps to Promise<T[]>, etc). In particular the type of each yield expression is generally unrelated to the types of the other yield expressions in the body in an async-runner generator function.

    Do you know if there is a tracking issue for this use-case? I think we definitely need to continue discussion, because this use-case is quite common and becomes more and more popular.

    Reacted by monnef, Gitowiec, jarmo and 流浪大法师
  7. yortus commented on Jul 5, 2016

    @yortus
    Troy Gerwien (yortus)
    on Jul 5, 2016
    Contributor
    More actions

    Stanislav Panferov (@s-panferov) no problem. I think there's just #2873. There's quite a lot of discussion about the async-runner use-case in there, but I think that the team wanted to focus on getting simpler use cases working initially. Since that issue is now closed, I guess you could open a new issue focused specifically on better typing for co-style generators.

  8. Anders Hejlsberg (ahejlsberg)
    closed this as completedin #9407on Jul 28, 2016
  9. Daniel Rosenwasser (DanielRosenwasser)
    reopened this on Aug 15, 2016
  10. DanielRosenwasser commented on Aug 15, 2016

    @DanielRosenwasser
    Daniel Rosenwasser (DanielRosenwasser)
    on Aug 15, 2016
    Member
    More actions

    This hasn't actually been fixed yet.

    Reacted by Troy Gerwien, Kirill Agalakov, Ben, soerenBoisen, Moe Elsharif, Adam Pietrasiak, akay64, 流浪大法师, Douglas Nassif Roma Junior and Huy Pham
  11. DanielRosenwasser commented on Aug 15, 2016

    @DanielRosenwasser
    Daniel Rosenwasser (DanielRosenwasser)
    on Aug 15, 2016
    Member
    More actions

    The issue as I see it is that without #2175, this would be a breaking change. For example, you start out fixing IteratorResult:

    interface IteratorYieldResult<Y> {
        done: false;
        value: Y;
    }
    
    interface IteratorReturnResult<R> {
        done: true;
        value: R;
    }
    
    type IteratorResult<Y, R> = IteratorYieldResult<Y> | IteratorReturnResult<R>

    Now all of a sudden you need to introduce another type parameter to Iterator:

    interface Iterator<Y, R> {
        next(value?: any): IteratorResult<Y, R>;
        return?(value?: any): IteratorResult<Y, R>;
        throw?(e?: any): IteratorResult<Y, R>;
    }

    which infects Iterable & IterableIterator:

    interface Iterable<Y, R> {
        [Symbol.iterator](): Iterator<Y, R>;
    }
    
    interface IterableIterator<Y, R> extends Iterator<Y, R> {
        [Symbol.iterator](): IterableIterator<Y, R>;
    }

    These now break any users of Iterators. For instance, Array's members needed to be fixed up to:

    interface Array<T> {
        /** Iterator */
        [Symbol.iterator](): IterableIterator<T, undefined>;
    
        /** 
          * Returns an array of key, value pairs for every entry in the array
          */
        entries(): IterableIterator<[number, T], undefined>;
    
        /** 
          * Returns an list of keys in the array
          */
        keys(): IterableIterator<number, undefined>;
    
        /** 
          * Returns an list of values in the array
          */
        values(): IterableIterator<T, undefined>;
    }
  12. JsonFreeman commented on Aug 15, 2016

    @JsonFreeman
    Jason Freeman (JsonFreeman)
    on Aug 15, 2016
    ContributorAuthor
    More actions

    Yes I remember our long discussion about this. The tricky bit is that many users will just want to use for-of, spread and rest, which never use the R type. Those users will not care about R, only Y. Then there are some users who will call the iterator methods explicitly, and they will care about the R type. The art is in serving both use cases simultaneously. I think there needs to be a type with two type parameters, and another type with only one, where the second type argument is any.

    Reacted by Gabe Johnson
  13. falsandtru commented on Sep 1, 2016

    @falsandtru
    falsandtru (falsandtru)
    on Sep 1, 2016
    Contributor
    More actions

    I feel definitions using literal types is too complex for common interfaces because we need to explicitly assert a boolean literal type for now. We need more easy ways to use literal types.

    function iter(): IteratorResult<void, void> {
      return {
        done: <true>true
      };
    }
  14. Igorbek commented on Oct 5, 2016

    @Igorbek
    Igor Oleinikov (Igorbek)
    on Oct 5, 2016
    Contributor
    More actions

    With respect to what Jason Freeman (@JsonFreeman) said according to the concern raised by Daniel Rosenwasser (@DanielRosenwasser), I experimented with a hypothetical typing of iterators that may return values.

    Currently we have this:

    interface IteratorResult<T> {
        done: boolean;
        value: T;
    }
    
    interface Iterator<T> {
        next(value?: any): IteratorResult<T>;
        return?(value?: any): IteratorResult<T>;
        throw?(e?: any): IteratorResult<T>;
    }

    This can be changed to:
    Sorry for names end with 2, that's just for illustration

    interface IteratorYieldResult<Y> {
        done: false;
        value: Y;
    }
    interface IteratorReturnResult<R> {
        done: true;
        value: R;
    }
    
    type IteratorResult2<T, R> = IteratorYieldResult<T> | IteratorReturnResult<R>;
    // redefine IteratorResult through extended interface to preserve generic arity
    type IteratorResult<T> = IteratorResult2<T, any>;
    
    interface Iterator2<T, R, I> {
        next(value?: I): IteratorResult2<T, R>;
        return?(value: R): IteratorResult2<T, R>;
        throw?(e?: any): IteratorResult2<T, R>;
    }
    // redefine Iterator through extended interface to preserve generic arity
    type Iterator<T> = Iterator2<T, any, any>;

    Open questions:

    • next(value: I) or next(value?: I)
      • if value is optional then we will not be able to enforce strict type I in yield expression in generators 👎
      • if value were not be optional then to allow next() we'd need to either type I | undefined or require consumer to work around like next(undefined as I) 👎
    • return(value: R) strictly requires value to be passed, right?
  15. JsonFreeman commented on Oct 5, 2016

    @JsonFreeman
    Jason Freeman (JsonFreeman)
    on Oct 5, 2016
    ContributorAuthor
    More actions

    Nice typing Igor Oleinikov (@Igorbek)!

    I don't think the return parameter should be required. I get the impression that the return method is largely for cleanup, just executing any finally clauses that correspond to try/catch blocks surrounding the execution point (for a generator in particular). Unless you have a yield expression in one of the finally blocks, the consumer will already have the value they want returned.

    For the next parameter, I think it has to be optional. Consider the language constructs for iterating (for-of, spread, etc). None of those constructs pass a parameter. If the generator really needs the parameter to be passed, then I think it is misleading to call it an iterator. It might be better to have a separate generator type for that, since the consumer will have to interact with the object in a different way.

  16. 45 remaining items

  17. Daniel Rosenwasser (DanielRosenwasser)
    added this to the TypeScript 3.3 milestone on Jan 2, 2019
  18. Ron Buckton (rbuckton)
    modified the milestones: TypeScript 3.3, TypeScript 3.4.0 on Jan 22, 2019
  19. zpdDG4gta8XKpMCd commented on Mar 7, 2019

    @zpdDG4gta8XKpMCd
    ZpdDG4gta (zpdDG4gta8XKpMCd)
    on Mar 7, 2019
    More actions

    incorrect typing around return type just bit me, wow this issues is old

    Reacted by Dziamid, Kirill Agalakov, Joe Calzaretta, Yifei Wang, Dom DiCicco, David Piper, Tylor Steinberger, ExE Boss, Fabrizio, Richard Simpson and 12 more
  20. Ryan Cavanaugh (RyanCavanaugh)
    modified the milestones: TypeScript 3.4.0, TypeScript 3.5.0 on Mar 13, 2019
  21. Ron Buckton (rbuckton)
    modified the milestones: TypeScript 3.5.0, TypeScript 3.6.0 on May 22, 2019
  22. brainkim commented on Jun 1, 2019

    @brainkim
    Brian Kim (brainkim)
    on Jun 1, 2019
    More actions

    If this is gonna be worked on, I think a nice easy win separate from typing the return value of generators would be to make the return and throw methods non-optional.
    I’m writing a lot of code like:

    // having this function return an iterator type with non-optional `return` and
    // `throw` results in a type error
    function* gen(): IterableIterator<number> {
      yield 1;
      yield 2;
      yield 3;
    }
    
    // need an exclamation mark because return is optional, despite the fact
    // that the return method is always defined for generator objects.
    gen().return!();
  23. Ron Buckton (rbuckton)
    closed this as completedin #30790on Jul 4, 2019
  24. Daniel Rosenwasser (DanielRosenwasser)
    added
    FixedA PR has been merged for this issueA PR has been merged for this issue
    on Jul 8, 2019
  25. oguimbal commented on Jul 30, 2019

    @oguimbal
    Olivier Guimbal (oguimbal)
    on Jul 30, 2019
    More actions

    I'm curious: Why adding generic parameters to Iterable and AsyncIterable, but not to IterableIterator and AsyncIterableIterator ?

    ... i have been waiting for this feature for a long time, but not extending iterableIterators forbids using strongly typed yield* result.

    Works using this hack, though:

    image

    Reacted by Zak Henry, Igor Adrov, Flavio Vilante, Iorg, Plimeor and 流浪大法师
    Reacted by Brian Kim, Brandon Bil, Flavio Vilante, Iorg and 流浪大法师
  26. ilbrando commented on Jan 15, 2020

    @ilbrando
    ilbrando
    on Jan 15, 2020
    More actions

    Stanislav Panferov (@s-panferov) unfortunately not. You are talking about the async-runner style of generator used by things like co right?

    The generator proposal (#2873) doesn't offer much typing support for async-runner generators. In particular:

    • All yield expressions are typed as any, as you can see in the example comments below. This is actually a very complex problem and I tried tackling it with some ideas which are all there in Proposal for generators design #2873. This won't change with Number, enum, and boolean literal types #9407. And the TNext type in the OP above won't solve this either, since in an async runner there is generally not a single TNext type, but rather the type of each yield expression is a function of the type of that yield's operand (eg Promise<T> maps to T, Array<Promise<T>> maps to T[], etc). In particular the type of each yield expression is generally unrelated to the types of the other yield expressions in the body in an async-runner generator function.
    • The compiler infers a single TYield type that must be the best common type among all the yield operands. For async runners there often is no such best common type, so such generators often won't compile until at least one yield operand is cast to any. E.g. the function *bar example below doesn't compile for this reason.
    • Return types of generators are currently not tracked because without boolean literals, they can't be separated from the TYield type. This is what will be solvable once Number, enum, and boolean literal types #9407 lands.

    Example code:

    interface User {id; name; address}
    interface Order {id; date; items; supplierId}
    interface Supplier {id; name; phone}
    declare function getUser(id: number): Promise<User>;
    declare function getOrders(user: User): Promise<Order[]>;
    declare function getSupplier(id: number): Promise<Supplier>;
    
    function* foo() {
        let user = yield getUser(42); // user is of type 'any'
        let user2 = <User> user;
        return user2; // This return type is not preserved
    }
    
    function* bar() { // ERROR: No best common type exists among yield expressions
        let user = yield getUser(42);       // user has type 'any'
        let orders = yield getOrders(user); // orders has type 'any'
    
        let orders2 = <Order[]> orders;
        let suppliers = yield orders2.map(o => getSupplier(o.supplierId)); // suppliers has type 'any'
    
        let suppliers2 = <Supplier[]> suppliers;
        return suppliers2; // This return type is not preserved
    }

    As it is not an easy task to make TypeScript able to handle generator functions like they are used by Redux Saga, I have come up with a workaround that works for me.
    You can see it here: https://github.com/ilbrando/redux-saga-typescript

    Reacted by CallMeLaNN and 流浪大法师
    Reacted by Brian Kim, CallMeLaNN, Max, Eugene Oskin, 流浪大法师 and Damilola Majek
  27. danielnixon commented on Jan 15, 2020

    @danielnixon
    Daniel Nixon (danielnixon)
    on Jan 15, 2020
    More actions

    ilbrando another alternative: https://github.com/agiledigital/typed-redux-saga

    Reacted by CallMeLaNN, Near Huscarl, 流浪大法师 and Weiyuan Wu
  28. ilbrando commented on Jan 16, 2020

    @ilbrando
    ilbrando
    on Jan 16, 2020
    More actions

    Thank you Daniel Nixon (@danielnixon) this is a really clever solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @rbuckton
    Ron Buckton (rbuckton)

Labels

CommittedThe team has roadmapped this issueThe team has roadmapped this issueFixedA PR has been merged for this issueA PR has been merged for this issueSuggestionAn idea for TypeScriptAn idea for TypeScript

Type

No type

Projects

No projects

    Milestone

    • TypeScript 3.6.0

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402503 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.