microsoft / microsoft/TypeScript

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

Open
#58,343 0 comments 5 reactions 1 assignee View on GitHub

@rbuckton is already working on this.

Since May 2, 2024.

Needs Investigation
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
1d 19h
Merged PRs (30d)
117

Description

🔎 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
      }
      

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.