microsoft / microsoft/TypeScript

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

Abierto
#58,343 0 comentarios 5 reacciones 1 asignado Ver en GitHub

@rbuckton ya está trabajando en esto.

Desde el 2/5/2024.

Needs Investigation
Lenguaje dominante
Go
Estrellas
111k
Forks
14.4k
Merge medio
1 d 19 h
PR fusionados (30 d)
117

Descripción

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

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.