microsoft / microsoft/TypeScript

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

Aperta
#58,343 0 commenti 5 reazioni 1 assegnatario Vedi su GitHub

@rbuckton ci sta già lavorando.

Dal 2/5/2024.

Needs Investigation
Lingua principale
Go
Stelle
111k
Fork
14.3k
Merge medio
1g 19h
PR unite (30g)
117

Descrizione

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

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.