Migrate operations/helpers.js to TypeScript by jdeniau · Pull Request #2206 · immutable-js/immutable-js · GitHub
Skip to content

Migrate operations/helpers.js to TypeScript - #2206

Merged
jdeniau merged 9 commits into
6.xfrom
migrate-operations-helpers
Jun 9, 2026
Merged

jdeniau merged 9 commits into
6.xfrom
migrate-operations-helpers

Conversation

@jdeniau

@jdeniau jdeniau commented Jun 1, 2026

Copy link
Copy Markdown
Member

Migrates src/operations/helpers.jshelpers.ts, as part of the v6 TS migration.

Stacked on #2193 (base: extract-mixin-operations).

Typing decisions

Typed precisely with the end-state (all of operations/ in TS) in mind, so the
remaining files consume these helpers cleanly under strict checking later:

  • reify<C extends CollectionImpl<unknown, unknown>>(iter: C, seq): C
    preserves the kind of iter, keeping every "same-type" call site in
    Collection.ts (reverse/slice/sort/skipWhile/interpose/…) cast-free.
    The dynamic create/constructor reconstruction is asserted once, internally,
    at that type-erasure boundary.
  • defaultComparator(a: unknown, b: unknown)unknown params make it
    assignable as a Comparator<V> for any value type (contravariance), avoiding a
    future mismatch when sortFactory is typed.
  • makeSequence / collectionClass / cacheResultThrough typed against the
    source (precise factory union, structural this).

map and flip return a transformed type, so they adopt the overload + loose
): unknown implementation pattern (as filter already does) — cast-free.

'./helpers.js' imports in the still-JS operations files are made extensionless
to resolve against the new .ts.

Verification

  • npm run type-check — 0 errors
  • npm run test:unit — 783 passed
  • npm run build — OK (no load-order cycle)
  • npx tstyche — 828 passed

@jdeniau jdeniau added this to the 6.0 milestone Jun 1, 2026
@jdeniau
jdeniau force-pushed the extract-mixin-operations branch 2 times, most recently from 4c79bfe to c901901 Compare June 1, 2026 20:40
Base automatically changed from extract-mixin-operations to 6.x June 1, 2026 20:44
@jdeniau
jdeniau force-pushed the migrate-operations-helpers branch from f7b1d31 to 4ad1555 Compare June 1, 2026 20:49
Type the shared operation helpers precisely, anticipating that the rest of
operations/ will become TS and consume them under strict checking:

- reify<C extends CollectionImpl<unknown, unknown>>(iter: C, seq): C — preserves
  the kind of `iter`, so the many "same-type" call sites (reverse/slice/sort/
  skipWhile/…) stay cast-free. The dynamic create/constructor reconstruction is
  asserted once, internally, at that type-erasure boundary.
- defaultComparator(a: unknown, b: unknown) — accepts unknown so it is assignable
  as a Comparator<V> for any value type (contravariance), avoiding a future
  mismatch when sortFactory is typed.
- makeSequence / collectionClass / cacheResultThrough typed against the source.

map and flip return a transformed type, so they now use the overload + loose
`): unknown` implementation pattern (as filter already does) to stay cast-free.

Imports of './helpers.js' in the still-JS operations files are made extensionless
so they resolve to the new .ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jdeniau
jdeniau force-pushed the migrate-operations-helpers branch from 4ad1555 to 8333522 Compare June 1, 2026 20:49
jdeniau and others added 6 commits June 1, 2026 21:50
Type the 14 operation factories precisely so reify can carry the result type.

- reify is now reify<S extends CollectionImpl<unknown, unknown>>(iter, seq): S —
  it follows the type of `seq`. The precision therefore lives in each factory's
  return type: same-kind factories (reverse/slice/sort/filter/skipWhile/
  takeWhile/interpose) are <C extends CollectionImpl<...>>(collection: C, …): C,
  while transforming factories return the precise transformed type
  (mapFactory → CollectionImpl<K, M>, flipFactory → KeyedCollectionImpl<V, K>, …).
- As a result `map` and `flip` in Collection.ts are clean single signatures
  again (no `): unknown` implementation overload needed).
- makeSequence / collectionClass are overloaded per collection kind.

The factory bodies build a sequence by dynamically mutating the makeSequence
result and drive the input through its dynamic iteration protocol (the
`IteratorType` union, unknown keys/values). That construction is not expressible
in the type system, so casts are confined to that boundary: a `loose()` view of
the input, the `MutableSequence` shape of the built sequence, and the return.
No `any` anywhere; the exported signatures are fully precise.

`interleave` keeps an overload + loose implementation since its result flows
through `flatten` (which widens to CollectionImpl<unknown, unknown>).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a public `__iterator(type: IteratorType, reverse?): IterableIterator<K | V | [K, V]>`
overload on CollectionImpl (promoting the existing implementation signature).
Operation factories forward a runtime `type` to `__iterator`, which previously
matched none of the literal overloads and forced a loose `unknown`-typed view of
the input.

With it, the `__iteratorUncached` bodies now iterate through the precisely-typed
`collection` directly: literal `ITERATE_ENTRIES` calls yield `[K, V]` steps, so
the per-element `entry[0] as K` / `entry[1] as V` / `value as V` / `key as K`
casts are gone. `loose()` is now only used for `get`/`has`/`includes`/`reverse`/
`flip` (which take `unknown` keys / non-base members), and `LooseInput` no longer
declares `__iterator`.

A few structural iterator-boundary casts remain because `collection.__iterator`
returns a native `IterableIterator` (immutable's `Iterator` class type is frozen)
and a `step` object is reused across output types. Those, the
`makeSequence`/return construction boundary, and the `unknown`-key `get`/`has`
will only fully clear up once the operation sequences become real typed classes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jdeniau
jdeniau force-pushed the migrate-operations-helpers branch from 822c3cf to 226429a Compare June 8, 2026 06:50
@jdeniau
jdeniau merged commit 8741b77 into 6.x Jun 9, 2026
5 checks passed
@jdeniau
jdeniau deleted the migrate-operations-helpers branch June 9, 2026 20:21
jdeniau pushed a commit to jdeniau/immutable-js that referenced this pull request Aug 16, 2026
Each finding from .agents/migration-status.md was traced back to its
originating PR, then fixed or confirmed as an intentional change:

- R1 (isSubset string argument, from immutable-js#2204): keep the new
  character-iteration semantics, consistent with isSuperset and with
  Collection('abc'); documented as a v6 breaking change in the CHANGELOG
  and pinned by multi-char unit tests.
- R2 ('@@iterator' fallback removal, from immutable-js#2127): confirmed intended
  modernisation; explicit CHANGELOG entry, and the two immutable.d.ts
  docstrings no longer mention @@iterator. immutable.js.flow is left
  untouched since Flow's @@iterator syntax denotes Symbol.iterator.
- R3 (reverseFactory reverse-iteration keys, ?? 0 from immutable-js#2206, NaN keys
  already in v5): fixed by counting down from the number returned by
  ensureSize(collection) - the reversed sequence has the same size as
  its source. Regression tests cover both __iterate (reduceRight) and
  __iterator (keyed reverse) paths; CHANGELOG entry since the NaN
  behaviour shipped in v5.
- R4 (wholeSlice with begin === undefined, from immutable-js#2128): restored the
  faithful v5 form (begin !== undefined && begin <= -size) so slice()
  with no arguments goes through sliceFactory again, on empty
  collections too; identity regression test added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122shfP2ngkkjuXZBWoeFGW
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant