This issue at hand is quite complex, and I must admit that I do not fully grasp it. However, I may be able to shed some light on the matter.
What prompted these additions?
In a particular commit (referenced here), TypeScript made the decision to enforce stricter type checks for generators. This change was implemented with valid reasoning, as exemplified in the following scenario:
function* foo() {
let m = 0;
while (m < 10) {
yield m++;
}
return "done";
}
let gen = foo(),
curr;
while(!(curr = gen.next()).done) {}
// At this point we should deduce
// that curr.value is a string because curr.done is true
The challenge lies in discerning whether a value has been returned or yielded—despite logical expectations dictating otherwise. Hence, the introduction of TReturn and TNext was deemed necessary, as elucidated in their implementation here:
[…] correctly check, and provide a type for, the result of a yield
expression based on the next type of the generator's return type annotation (i.e. the TNext
type in the Generator
definition above).
Why incorporate default values?
If a decision is made to enact such alterations, it is inevitable that some existing code will be affected—the aim being minimal disruption. It is crucial to note that there exists contrast in the usage of the next()
function between generators and non-generator iterators, as stipulated in the ECMA-262 documentation.
While arguments may be passed to the next
function, their interpretation and validity hinge upon the target Iterator. The common utilization of Iterators, such as in for-of loops, does not involve passing any arguments; hence, Iterator objects anticipating such usage must accommodate calls without arguments.
Generators are predominantly employed in for-of loops where no argument is supplied to next. In fact, the act of furnishing an argument to the next function is exceedingly rare (as underscored by MDN referring to it as a "zero-argument function"). Therefore, setting the default value of TNext to 'undefined' is the most pragmatic choice—one that avoids impeding type checking complexities, particularly when compiled with '--strictNullChecks'.
All seems well until one considers scenarios where passing an argument to the `next()` function with generators is commonplace—a practice validated in the standard specifications:
Generator.prototype.next(value)
The next
method executes the following steps:
- Let g represent the current instance.
- Return ?GeneratorResume(g, value, empty).
In accordance with MDN documentation:
The provided value serves as input to the generator.
Said value is allocated as the outcome of a yield
expression. For example, given `variable = yield expression`, the value passed to `.next()` will be assigned to `variable`.
Moreover, typically, the initial `.next()` call lacks an argument, whereas subsequent calls include one. Regrettably, demarcating a type as "optional first time, unknown subsequently" proves challenging; thus, opting for 'unknown' as the TNext default within Generators appeared reasonable given the circumstances.
Inevitably, achieving perfection remains elusive. Thus, compromises reflecting lesser consequences are sought after.
For those intrigued by the intricacies discussed herein, further insight can be gleaned from this documented issue.