Skip to content

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

Open
mvanhorn wants to merge 1 commit into
Effect-TS:mainfrom
mvanhorn:fix/6225-tmap-remove-removeall-clears-entire-bucket-on-hash
Open

fix(effect): TMap.remove/removeAll clears entire bucket on hash collision#6233
mvanhorn wants to merge 1 commit into
Effect-TS:mainfrom
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

…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
@mvanhorn mvanhorn requested a review from mikearnaldi as a code owner May 16, 2026 20:16
@github-project-automation github-project-automation Bot moved this to Discussion Ongoing in PR Backlog May 16, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 16, 2026

🦋 Changeset detected

Latest commit: 2b936e5

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

This PR includes changesets to release 36 packages
Name Type
effect Patch
@effect/cli Patch
@effect/cluster Patch
@effect/experimental Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/platform Patch
@effect/printer-ansi Patch
@effect/printer Patch
@effect/rpc Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-drizzle Patch
@effect/sql-kysely Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/sql Patch
@effect/typeclass Patch
@effect/vitest Patch
@effect/workflow Patch
@effect/ai Patch
@effect/ai-amazon-bedrock Patch
@effect/ai-anthropic Patch
@effect/ai-google Patch
@effect/ai-openai Patch
@effect/ai-openrouter 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Discussion Ongoing

Development

Successfully merging this pull request may close these issues.

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

1 participant