facebook / facebook/flow

Nullity of generator result inconsistent between definition and use

Open
#6,633 0 comments 0 reactions 0 assignees View on GitHub
generators
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.