Fix type warnings emitted by use Storex.Store on Elixir 1.18+ - #10
Open
drozdzynski wants to merge 3 commits into
Open
Fix type warnings emitted by use Storex.Store on Elixir 1.18+#10drozdzynski wants to merge 3 commits into
use Storex.Store on Elixir 1.18+#10drozdzynski wants to merge 3 commits into
Conversation
`Storex.Store.__before_compile__/1` generated a `case` over the return value of each store's `init/2` callback. Because `@store` is a concrete module at expansion time, Elixir's type checker (1.18+) proves the unused clauses dead and warns once per store — attributed to the downstream author's own file, where it cannot be fixed. Projects building with `mix compile --warnings-as-errors` could not compile against storex on Elixir 1.18 or newer. Move the branching into `Storex.Store.__init__/3`, `__mutation__/6` and `__terminate__/4`. These dispatch with `apply/3` and are compiled once against the full callback contract rather than specialised per store, so every clause stays reachable and no warning is emitted. The public callback contract and runtime behaviour are unchanged: an unsupported `init/2` return still raises, an unsupported `mutation/5` return still resolves to an error tuple, and an unmatched mutation name is still reported as an error. The `mutation/5` dispatch was already immune because it used `Kernel.apply/3`; routing it through the same helper keeps that property from being lost to a future cleanup. CI gains Elixir 1.18, 1.19 and 1.20 entries and a `mix compile --warnings-as-errors` step so this cannot regress.
The matrix stopped at OTP 28, so the newest supported Erlang was not covered. Elixir 1.20 ships otp-29 builds and OTP 29.0.6 is available for ubuntu-24.04.
drozdzynski
force-pushed
the
fix/elixir-1.18-type-warnings
branch
from
September 6, 2026 18:19
8b54424 to
6216c95
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Storex.Store.__mutation__/6 currently rescues all FunctionClauseErrors and can misreport real runtime bugs as “No mutation matching …”, which should be narrowed to only the unmatched-mutation case.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR refactors the use Storex.Store macro-generated server code to avoid Elixir 1.18+ set-theoretic type checker “dead clause” warnings by moving callback-return-shape dispatch into shared Storex.Store helper functions compiled once.
Changes:
- Add
Storex.Store.__init__/3,__mutation__/6, and__terminate__/4helpers and route the macro-generatedServermodule through them. - Add focused ExUnit coverage for valid/invalid callback return shapes and the “unmatched mutation” path, plus new invalid fixtures.
- Expand CI to test Elixir 1.18–1.20 and enforce
mix compile --warnings-as-errors; bump version to 0.6.2 and update changelog.
File summaries
| File | Description |
|---|---|
lib/storex/store.ex |
Adds shared dispatch helpers and updates generated server code to call them (reduces type-warning noise on Elixir 1.18+). |
test/storex/store_test.exs |
Adds unit/integration tests for init/mutation dispatch and server behavior. |
test/fixtures/stores/invalid_init.ex |
Fixture store with invalid init/2 return to exercise the raise path. |
test/fixtures/stores/invalid_mutation.ex |
Fixture store with invalid/error mutation returns to exercise error normalization. |
CHANGELOG.md |
Adds 0.6.2 entry documenting the warning fix. |
mix.exs |
Bumps library version to 0.6.2. |
package.json |
Bumps JS package version to 0.6.2. |
.github/workflows/main.yml |
Adds Elixir 1.18–1.20 matrix entries and enforces warnings-as-errors at compile time. |
Review details
- Files reviewed: 8/8 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+62
to
+66
| rescue | ||
| FunctionClauseError -> | ||
| {:error, | ||
| "No mutation matching #{inspect(name)} with data #{inspect(data)} in store #{inspect(store)}"} | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Storex.Store.__before_compile__/1generates aServermodule whoseinit_store/2runs acaseover the return value of the user'sinit/2callback, with a clause for every shape the behaviour allows.@storeis a concrete module at expansion time, so Elixir's set-theoretic type checker (1.18+) infers the callback's exact return type and proves the remaining clauses dead.The result is a warning per store module in every downstream project, attributed to the author's own file, pointing at code they did not write. Any project building with
mix compile --warnings-as-errorscannot compile against storex on Elixir 1.18 or newer.Reproduced on this repo's own fixtures:
(1.20 additionally reports the defensive
_ ->catch-all as unreachable.)Fix
Move the branching out of the macro-expanded code into three helpers on
Storex.Store:__init__/3__mutation__/6__terminate__/4They dispatch with
apply/3and are compiled once, against the full callback contract, instead of being specialised per store. Every clause stays reachable, so nothing is reported dead.The public callback contract is untouched and runtime behaviour is identical:
init/2return still raises,mutation/5return still resolves to{:error, "Return value of mutation should be …"},FunctionClauseErrorand reported as an error.The
mutation/5site was already immune to the warning, but only incidentally — it usedKernel.apply/3, which is opaque to the checker. Swapping it for a direct@store.mutation(...)call raises the warning count from 12 to 56. Routing it through the shared helper makes that property explicit instead of accidental.Note: moving only
init/2into a helper that is called with the store's own return value is not enough — the checker propagates the helper's return type per call site and the warning simply relocates to thewithinstart_link/2. Dispatching viaapply/3inside the helper is what actually removes it.Tests
New
test/storex/store_test.exscovers each validinit/2shape, each validmutation/5shape, both invalid-return paths and the unmatched-mutation path, plus the store server start/mutate path throughStorex.Supervisor. Two fixtures added:invalid_init.ex,invalid_mutation.ex.CI
Matrix gains Elixir 1.18, 1.19 and 1.20 (on
ubuntu-24.04; existing rows pinned toubuntu-22.04so the OTP 24/25 builds keep resolving), and amix compile --warnings-as-errorsstep so this cannot regress silently.Verified locally
mix compile --force --warnings-as-errors→ 0 warnings, and the non-browser suite → 52 passed, on:Browser tests (Wallaby) were not run locally — no chromedriver on this machine. Elixir 1.14/1.15 were not available locally either; if the new
--warnings-as-errorsstep trips on a pre-existing warning there, those rows may need adjusting.