Omit removed services from Context.addOrOmit result types - #6940
Conversation
|
There was a problem hiding this comment.
Important
The two reproduction tests currently assert the buggy behavior, not the desired fixed behavior. Flip them before adding the implementation fix so the PR actually validates the type change.
Reviewed changes
Reviewed the two new regression test files on this branch that will exercise Context.addOrOmit(Option.none()).
packages/effect/test/ContextAddOrOmitNone.test.ts— runtime assertion for accessing a service after it was omitted.packages/effect/typetest/ContextAddOrOmitNone.tst.ts— type-level assertion for accessing a service after it was omitted.
ℹ️ Reminder
The PR body notes the Context.addOrOmit implementation fix is still pending. When that implementation lands, it will change the exported overload return type, so a changeset for effect should be included. Tests-only changes do not need one.
@v0 or keep the SHA fresh with Dependabot | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏
| const Service = Context.Service<{ readonly value: number }>("AuditService") | ||
| const context = Context.empty().pipe(Context.addOrOmit(Service, Option.none())) | ||
|
|
||
| assert.doesNotThrow(() => Context.get(context, Service)) |
There was a problem hiding this comment.
The runtime behavior is already correct — addOrOmit(Option.none()) deletes the key, so Context.get throws "Service not found". This doesNotThrow assertion therefore encodes the current runtime-safe bug rather than validating the fix. If a runtime regression test is needed, use assert.throws(...); otherwise move the assertion to the type test and drop this file.
| const Service = Context.Service<{ readonly value: number }>("AuditService") | ||
| const context = Context.empty().pipe(Context.addOrOmit(Service, Option.none())) | ||
|
|
||
| expect(Context.get(context, Service)).type.toBe<{ readonly value: number }>() |
There was a problem hiding this comment.
This assertion currently passes and declares that Context.get(context, Service) should type-check — exactly the buggy behavior the PR aims to fix. Once addOrOmit omits I from the result type for None, the call should become a type error. Use expect(Context.get).type.not.toBeCallableWith(context, Service) (or an equivalent @ts-expect-error guard) instead.

Summary
TypeScript accepts Context.get for a service removed by addOrOmit(None), but the accepted call throws Service not found at runtime.
Important
This PR starts with focused failing reproduction tests. Add the implementation fix to this same branch; CI is expected to fail until that fix is included.
addOrOmit(None) types a removed service as present
Module:
ContextAudit ID:
core-a-f-context-add-or-omit-none-typeSeverity / confidence: medium / high
What happens
TypeScript accepts Context.get for a service removed by addOrOmit(None), but the accepted call throws Service not found at runtime.
Why it happens
Both overloads return Context<Services | I> regardless of the Option branch, while the implementation deletes key.key for None.
Expected behavior
addOrOmit stores the service for Some and removes its key for None; the resulting context type must not prove that a removed service is available to Context.get.
Relevant implementation
These links and excerpts are pinned to audit base
c9b56ab507f224426ee8388dc450da447ec4715f.packages/effect/src/Context.ts:741-762View problematic code at
packages/effect/src/Context.ts:741-762View exact lines on GitHub
Reproduction
Observed failure: The type repro accepted Context.get, while the runtime repro threw Service not found: AuditService.
Implementation handoff
The initial reproduction tests on this branch are the regression specification for the implementation fix that should follow in this PR.
Audit provenance
c9b56ab507f224426ee8388dc450da447ec4715fc9b56ab507f224426ee8388dc450da447ec4715fcore-a-f-context-add-or-omit-none-type