Repository navigation
Declare the variance of iterator, generator and tagged template types - #8981
Conversation
Why:
The iterator family's type parameters were invariant, so a generator
of `[#A]` could not be coerced to one of `[#A | #B]`, and values of
library types built on `Generator.t` were not generalised under the
relaxed value restriction. Yields and return values only flow out of
an iterator and resume values only flow in. Needed for generators
(#8934).
Typing modified (coercion):
Example:
let g: Generator.t<[#A], int, [#X | #Y]> = gen()
let h = (g :> Generator.t<[#A | #B], int, [#X]>)
Before (rule):
t<y, r, n> ≤ t<y', r', n'> only if y = y', r = r', n = n'
After (rule):
y ≤ y' r ≤ r' n' ≤ n
───────────────────────── (Iterator variance)
t<y, r, n> ≤ t<y', r', n'>
t ∈ {Iterator, AsyncIterator, Generator, AsyncGenerator, IterableIterator, AsyncIterableIterator, IteratorObject}
rawResult<+y, +r> arrayLike<+a> taggedTemplate<-param, +output>
Review: Is `+'return` sound when `Generator.returnValue` takes a `'return`? Only up to a contrived case: a generator shared under two types, resumed through `returnValue` while a `finally` block yields, hands the wider value to the narrower reference. TypeScript's `Generator` has the same hole.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fc7LEuyEfynjaKCVEBPmTF
Signed-off-by: Christoph Knittel <ck@cca.io>
Semantic Diff1 commit · Full page Declare the variance of iterator, generator and tagged template types85cf497 · Christoph Knittel with Claude Opus 5.5 · Oct 23, 2026
Typing modified (coercion) let g: Generator.t<[#A], int, [#X | #Y]> = gen()
let h = (g :> Generator.t<[#A | #B], int, [#X]>)
Review
|
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 85cf497a87
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #8981 +/- ##
==========================================
+ Coverage 81.22% 81.24% +0.01%
==========================================
Files 457 457
Lines 63862 63863 +1
==========================================
+ Hits 51875 51888 +13
+ Misses 11987 11975 -12
🚀 New features to boost your workflow:
|
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
|
Developer playground preview: https://rescript-lang.github.io/rescript/dev-playground/?version=pr-8981 |
Part of the groundwork for generator functions (RFC #8934), following the variance feedback there.
The iterator and generator types were invariant, so a
Generator.t<[#A], _, _>couldn't be coerced to aGenerator.t<[#A | #B], _, _>, while the equivalentiterablecoercion worked. Library types defined as aliases ofGenerator.t, such as effect types with polymorphic-variant error rows, also weren't generalised under the relaxed value restriction. Yield and return values only flow out of an iterator and resume values only flow in, so this declares:Iterator.t,AsyncIterator.t,Generator.t,AsyncGenerator.t,IterableIterator.t,AsyncIterableIterator.t,IteratorObject.t<+'yield, +'return, -'next>Iterator.rawResult,AsyncIterator.rawResult<+'yield, +'return>Array.arrayLike<+'a>(it is only read)taggedTemplate<-'param, +'output>(it is a function)Annotations only accept more programs, so existing code keeps compiling.
Audit of the rest of the stdlib
array,dict,Map,Set,WeakMap,WeakSet,TypedArray,ref) and the phantom tags (JsxEvent.synthetic, the Dom hierarchy).iterable,asyncIterable,promise,option,list,result,null,undefined,nullableandLazy.t.Jsx.componentis already contravariant.Map,MapDict,MapInt,MapString) could be covariant in their values. But their AVL nodes havemutablefields (mutated only while building fresh trees), so the implementations are inferred invariant. Declaring+'valuewould need an abstract implementation type and casts throughout those modules.'idstay invariant everywhere, because they flow into the comparator.Caveat on
+'returnGenerator.returnValuetakes a'return. A generator shared under two types could:returnValuewith a wider value while afinallyblock yields;This case is contrived, and TypeScript's
Generatorhas the same hole. Keeping'returninvariant would lose most of the benefit.Tests
tests/tests/src/stdlib_variance_test.res:Generator.t;super_errors/GeneratorResumeTypeWidening: widening the resume type is still rejected.🤖 Generated with Claude Code
https://claude.ai/code/session_01Fc7LEuyEfynjaKCVEBPmTF