microsoft / microsoft/TypeScript

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

Offen
#58,343 0 Kommentare 5 Reaktionen 1 zugewiesene Person Auf GitHub ansehen

@rbuckton arbeitet bereits daran.

Seit 02.5.2024.

Needs Investigation
Vorherrschende Sprache
Go
Sterne
111k
Forks
14.3k
Ø Merge
1 T. 19 Std.
Gemergte PRs (30 T.)
117

Beschreibung

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

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.