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 })
}
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
}
🔎 Search Terms
generator yield next
🕗 Version & Regression Information
⏯ 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
but becomes a type error when converted to the equivalent typescript
🙁 Actual behavior
generator
's inferred type signature isbecause the
Generator
'sTNext
gets inferred as the intersection of theyield
s' types:string & number & boolean
->never
🙂 Expected behavior
generator
's inferred type signature should be the union of theyield
s' typesOR
just
unknown
causing
(yield) as string
(along with the usage of any type to whichunknown
is not assignable) to be a compilation errorAdditional 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 withnext(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 eachyield
independently:this loses type precision
yield
to be typed as the intersection of allyield
s' respective (desired) type may in fact be desirable becausebut if this is the case, perhaps generators should never have their types inferred and instead default to all
TNext
s/yield
s being typed asunknown
, thereby prompting the developer to provide their explicit types when more precision is needed. this would also promote saferyield
consumption, e.g.prompts the developer to rewrite it as
and eventually