microsoft / microsoft/TypeScript
generator next type should be inferred as union (instead of intersection) of yields' types OR just unknown
@rbuckton arbeitet bereits daran.
Seit 02.5.2024.
- 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
💻 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 intersection 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 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 }
-
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Bewertung
Dieses Issue wurde noch nicht bewertet.