generator next type should be inferred as union (instead of intersection) of yields' types OR just unknown
🔎 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
yieldto be typed as the union of allyields' respective (desired) type may in fact be desirable becausethe inferred union cannot statically prevent a
const yield1: string = yieldfrom being incorrectly called withnext(2)but if this is the case, perhaps generators should never have their types inferred and instead default to all
TNexts/yields being typed asunknown, thereby prompting the developer to provide their explicit types when more precision is needed. this would also promote the usage of saferyieldconsumption, 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 }
-