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

generator next type should be inferred as union (instead of intersection) of yields' types OR just unknown #58343

Open
btoo opened this issue Apr 27, 2024 · 0 comments
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@btoo
Copy link

btoo commented Apr 27, 2024

πŸ”Ž Search Terms

generator
yield
next

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about generators

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.5.0-beta&filetype=ts#code/GYVwdgxgLglg9mAVAAgOYFMzoE4EMpzYAUAlMgN4BQyyECAzlMgJ4zoA2AJgIwBcyjbDDCpkAXhZsu1Wgzjt0AOnZxURcpI49kAXxIy6YRpq4AmfmBABbAEY5xJzgbkLlq9Y9O79NQ8dZaAMz8NnDy6LhgDgHSvi5KKmoaMZyB3pQ6lJR+TBhREnk4+ISklHmKWAAeUKVlmBXo1UQA5ILCqM1kAPRdyAAqzAAO6MitUEIizcgw9MhgcEy49PQwqGC4NgrIBNtDI81YAG44zZQ9FI58o22TunVgDU2m3b0Dw6OWtifTs-OLy6t1psRjsoHsPuhjthTudklJOOZkF5MuUqjVxiB0C9+uDmqFwpEpjM5gtkEsVmsNltQbijiczr04UF+BiRpkgA

πŸ’» Code

the following is perfectly valid javascript

function* generator() {
  const yield1 = yield
  console.log({ yield1 })
  const yield2 = yield
  console.log({ yield2 })
  const yield3 = yield
  console.log({ yield3 })
}

const gen = generator()
gen.next()

gen.next('string')
// { yield1: 'string' }
gen.next(2)
// { yield2: 2 }
gen.next(true)
// { yield3: true }

but becomes a type error when converted to the equivalent typescript

function* generator() {
  const yield1: string = yield
  console.log({ yield1 })
  const yield2: number = yield
  console.log({ yield2 })
  const yield3: boolean = yield
  console.log({ yield3 })
}

const gen = generator()
gen.next()

gen.next('string') // Type 'string' is not assignable to type 'never'
// { yield1: 'string' }
gen.next(2) // Type 'number' is not assignable to type 'never'
// { yield2: 2 }
gen.next(true) // Type 'boolean' is not assignable to type 'never'
// { yield3: true }

πŸ™ Actual behavior

generator's inferred type signature is

function generator(): Generator<undefined, void, never>

because the Generator's TNext gets inferred as the intersection of the yields' types: string & number & boolean -> never

πŸ™‚ Expected behavior

generator's inferred type signature should be the union of the yields' types

function generator(): Generator<undefined, void, string | number | boolean>

OR

just unknown

function generator(): Generator<undefined, void, unknown>

causing (yield) as string (along with the usage of any type to which unknown is not assignable) to be a compilation error

Additional information about the issue

while inferring the union still isn't perfectly type-safe (e.g. the inferred union cannot statically prevent a const yield1: string = yield from being incorrectly called with next(2)), it would go beyond bringing the inferred version up to parity with the explicit version because the explicit version would not be able to type each yield independently:

  • this is a type error
    function* generator(): Generator<undefined, void, string | number | boolean> {
      const yield1: string = yield // Type 'number' is not assignable to type 'string'
      console.log({ yield1 })
      const yield2: number = yield // Type 'string' is not assignable to type 'number'
      console.log({ yield2 })
      const yield3: boolean = yield // Type 'string' is not assignable to type 'boolean'
      console.log({ yield3 })
    }
  • this loses type precision
    function* generator(): Generator<undefined, void, string | number | boolean> {
      const yield1 = yield // string | number | boolean
      console.log({ yield1 })
      const yield2 = yield // string | number | boolean
      console.log({ yield2 })
      const yield3 = yield // string | number | boolean
      console.log({ yield3 })
    }
    • forcing each yield to be typed as the intersection of all yields' respective (desired) type may in fact be desirable because

      the inferred union cannot statically prevent a const yield1: string = yield from being incorrectly called with next(2)

      but if this is the case, perhaps generators should never have their types inferred and instead default to all TNexts/yields being typed as unknown, thereby prompting the developer to provide their explicit types when more precision is needed. this would also promote safer yield consumption, e.g.

      /** inferred as `Generator<undefined, void, unknown>` */
      function* generator() {
        const yield1: string = yield // Type 'unknown' is not assignable to type 'string'
        const yield2: number = yield // Type 'unknown' is not assignable to type 'number'
        const yield3: boolean = yield // Type 'unknown' is not assignable to type 'boolean'
      }

      prompts the developer to rewrite it as

      /** inferred as `Generator<undefined, void, unknown>` */
      function* generator() {
        const yield1 = yield // unknown
        if (typeof yield1 != 'string') throw new Error
        const yield2 = yield // unknown
        if (typeof yield2 != 'number') throw new Error
        const yield3 = yield // unknown
        if (typeof yield3 != 'boolean') throw new Error
      }

      and eventually

      function* generator(): Generator<undefined, void, string | number | boolean> {
        const yield1 = yield // string | number | boolean
        if (typeof yield1 != 'string') throw new Error
        const yield2 = yield // string | number | boolean
        if (typeof yield2 != 'number') throw new Error
        const yield3 = yield // string | number | boolean
        if (typeof yield3 != 'boolean') throw new Error
      }
@btoo btoo changed the title generator next type inferred as intersection (instead of union) of yields' types generator next type should be inferred as union (instead of intersection) of yields' types OR just unknown Apr 27, 2024
@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label May 2, 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.
Projects
None yet
Development

No branches or pull requests

3 participants