Skip to content

fix(effect): TMap.remove/removeAll clears entire bucket on hash collision - #6233

Merged
tim-smart merged 1 commit into
Effect-TS:v3from
mvanhorn:fix/6225-tmap-remove-removeall-clears-entire-bucket-on-hash
Jul 31, 2026
Merged

fix(effect): TMap.remove/removeAll clears entire bucket on hash collision#6233
tim-smart merged 1 commit into
Effect-TS:v3from
mvanhorn:fix/6225-tmap-remove-removeall-clears-entire-bucket-on-hash

Conversation

@mvanhorn

Copy link
Copy Markdown

Summary

TMap.remove and TMap.removeAll were clearing every entry in a bucket whenever two keys hashed to the same slot. The fix is a 2-line change in packages/effect/src/internal/stm/tMap.ts.

Why this matters

Bucket-level data loss on hash collision is a correctness bug for any user storing entries whose key types collide (custom Hash implementations, value types with low-entropy hashes). The bug was reported in #6225 with a minimal repro showing three entries in a single bucket where removing one drops the other two. The fixed code has two related issues:

  1. Chunk.partition returns [predicate-true, predicate-false]. The destructuring const [toRemove, toRetain] = ... was backwards.
  2. The predicate was Equal.equals(entry[1], key), comparing the entry's value against the key instead of the entry's key (entry[0]).

Together these meant the code was retaining the matched key and removing every other key in the bucket.

Changes

  • packages/effect/src/internal/stm/tMap.ts - swap the destructuring to [toRetain, toRemove] and compare entry[0] (key) against the key in both remove and removeAll.
  • packages/effect/test/TMap.test.ts - add two regression tests using a CollidingKey type whose Hash always returns 1, forcing every entry into one bucket.
  • .changeset/fix-tmap-remove-colliding-keys.md - patch-level changeset.

Testing

pnpm test --filter=effect -- TMap.test (Vitest). The new remove - preserves colliding keys and removeAll - preserves colliding keys tests fail on main and pass with the fix.

Fixes #6225

@mvanhorn
mvanhorn requested a review from mikearnaldi as a code owner May 16, 2026 20:16
@changeset-bot

changeset-bot Bot commented May 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 92add35

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
effect Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@IMax153
IMax153 changed the base branch from main to v3 July 13, 2026 23:55
@IMax153 IMax153 added the 3.0 Used for issues, pull requests, etc. that are relevant for the `v3` branch targeting Effect v3. label Jul 14, 2026
@tim-smart tim-smart added the bug Something isn't working label Jul 23, 2026 — with ChatGPT Codex Connector
…sion

The fixed code in tMap.ts had two related bugs:
1. The destructuring of Chunk.partition was reversed (toRemove/toRetain swapped)
2. The predicate compared entry[1] (value) to key instead of entry[0] (key)

Together these caused removing one key with a hash collision to drop every
other entry in the same bucket. Added regression tests using a CollidingKey
type that forces every entry into one bucket.

Fixes Effect-TS#6225
@tim-smart
tim-smart force-pushed the fix/6225-tmap-remove-removeall-clears-entire-bucket-on-hash branch from 2b936e5 to 92add35 Compare July 31, 2026 08:34
@tim-smart
tim-smart merged commit 291d5a9 into Effect-TS:v3 Jul 31, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3.0 Used for issues, pull requests, etc. that are relevant for the `v3` branch targeting Effect v3. bug Something isn't working ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TMap.remove and removeAll incorrectly clear entire bucket on hash collision

3 participants