Nullity of generator result inconsistent between definition and use
- Dominant language
- Rust
- Stars
- 22.3k
- Forks
- 1.9k
- PR merge metrics
- No merged PRs in 30d
Description
There is an inconsistency in the rules for typing generator functions
and the rules for consuming said generators. A generator function with
non-void `Return` type parameter must return:
```js
function* bad(): Generator {
yield 1;
// error: implicit `undefined` incompatible with `string` (good)
}
function* good(): Generator {
yield 1;
return "two"; // works
}
```
And yet, given a `Generator` whose `done`
attribute has been refined to `true`, the `value` is not known to be
`string`:
```js
const g = good();
g.next();
const result = g.next();
if (result.done) {
(result.value: string); // error!?
}
```
It is easy to see that this makes it very frustrating to work with some
functions. Every use of `if (result.done)` must have an additional check
`if (result.value != null)`.
This has been the case ever since `IteratorResult` was
introduced in a373455f42e5c7062b3c6234241d2ed64014fda3.
I understand that an [ECMAScript `IteratorResult`][es-ir] specifies that:
> If the iterator does not have a return value, `value` is
> **undefined**. In that case, the `value` property may be absent from
> the conforming object if it does not inherit an explicit `value`
> property.
However, Flow’s type `IteratorResult` does not match this: it allows the
`value` property to be absent even if the iterator _does_ nominally have
a return value.
[es-ir]: https://www.ecma-international.org/ecma-262/6.0/#sec-iteratorresult-interface
It would be ideal to specify a kind of “template override,” like
(made-up syntax)
```js
type family IteratorResult<+Yield,+Return> =
| { done: true, +value: Return }
| { done: false, +value: Yield };
type instance IteratorResult =
| { done: true, +value?: Return }
| { done: false, +value: Yield };
```
I am not entirely sure how to encode this type into Flow (or how to
prove that this is not possible).
I have devised the following incantation, which seems promising:
```js
type $NonEmptyIteratorResult<
+Yield,
+Return,
+Proof: $NonMaybeType | null
> =
| { done: true, +value: Return }
| { done: false, +value: Yield };
type $EmptyIteratorResult<+Yield, +Return: void> =
| { done: true, +value?: Return }
| { done: false, +value: Yield };
type IteratorResult<+Yield, +Return> =
| $EmptyIteratorResult
| $NonEmptyIteratorResult;
```
This [appears to work in some simple cases][1]:
```js
const irsNumberString: IteratorResult[] = [
{done: false, value: 1},
{done: true, value: "one"},
{done: true}, // properly fails
];
const irsNumberNull: IteratorResult[] = [
{done: false, value: 1},
{done: true, value: null},
{done: true}, // properly fails
];
const irsNumberVoid: IteratorResult[] = [
{done: false, value: 1},
{done: true, value: undefined},
{done: true}, // properly works
];
```
[1]: https://flow.org/try/#0C4TwDgpgBAJAcgewHYFEC2ZQElgQE4CGwCeAShAM4CuANsADwDUAmgJYQ0AmANFI+cCp4kvRgAU8CBADMAXLERIAsgRAAjCABVwEegKFIAfFAA+UJLRrGAvACgopqAG8onZBHnA8VCKIBuBDQ+8vrCUAC+9o4ubkgeUNKBFL58AUHxbBycEQDctqCQsOiYIDj4RCTk1HRMmVyioUjyfgisnDZRZjHunt4pjGk+APwhEIJhkQ5drj0JSf2DGexcufk6UGWExGSUtAwsyzx8jR1TRRjYuFuVuzV1RyedCsjFl+XbVXv097yNv2MGQx5WwAY2QFGAUFYeAocCoaA0eAAyl5WEgAObyTYVHbVBgWBH4XgQvBo9GGADaAF0oNYoBSok5YvFEjRkrxFvIAIzhbiM5m9HwcwLBKAAIncYt5-NmXh8vIcAHpFVAwJJIHgaCA5qw2bYqcCwUgIVCYXDCXg4TQaFirjjPjUCYjeBZrZSaXSGQ4mbNWeyoJyoDy+d6BVA5SlA66aNLQ7K+gqoMrVer8FqdXqDbZQeDIdDYfDEQA1VqcW3vG54+hOokB0vu2n0mVxeR+yMi+LB5vxCPC9LyKhITgQaRoiCcWPOMMRxPJtUIDXpgDuJAA1hR9TkgA
However, due to its complexity, I would not be surprised if it
introduced some subtleties down the road that are hard for me to
predict.
**Questions:**
- Can this type be fixed?
- Is there a workaround by which I can specify that a function returns
a generator that will really, truly return the thing that it
promises to return?
Contributor guide
Assessment
This issue has not been assessed yet.