Skip to content

Fix protocol consolidation crash on beams from older Elixir versions - #15995

Merged
josevalim merged 2 commits into
elixir-lang:mainfrom
jgnagy:fix-protocol-consolidation-stale-beams
Oct 17, 2026
Merged

josevalim merged 2 commits into
elixir-lang:mainfrom
jgnagy:fix-protocol-consolidation-stale-beams

Conversation

@jgnagy

@jgnagy jgnagy commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #15994

Problem

Since 558215b ("Dispatch consolidated protocols through literal external funs", #15986), defprotocol generates __select_impl__/1, __dispatch_impl__/4, and __select_struct__/1, and Protocol.consolidate/5 assumed those definitions are always present. Consolidating a protocol beam compiled before that commit raised a bare MatchError (no match of right hand side value: nil) whenever an existing _build was reused:

** (MatchError) no match of right hand side value: nil
    (elixir 1.21.0-dev) lib/protocol.ex:639: Protocol.consolidate/5
    (elixir 1.21.0-dev) lib/protocol.ex:588: Protocol.consolidate/2
    (mix 1.21.0-dev) lib/mix/compilers/protocol.ex:169: Mix.Compilers.Protocol.consolidate_each/4

Because Mix manifests do not record the Elixir version and the version string stayed 1.21.0-dev, Mix considered such a _build fresh and kept the old protocol beams. This affects both dev builds across main and, without a manifest bump, users upgrading between released versions with a reused _build.

Solution

  • Bump Mix.Compilers.Elixir's @manifest_vsn from 37 to 38, so builds compiled with versions prior to the new protocol representation are fully recompiled

  • Raise a proper ArgumentError with an actionable message in Protocol.consolidate/5 when the expected definitions are missing, instead of a bare MatchError:

    cannot consolidate protocol Foo: it does not define __select_impl__/1, which means it was compiled with an incompatible version of Elixir, please recompile the protocol with the current version

    I considered returning a new {:error, reason} tuple, but Mix.Compilers.Protocol.consolidate_each/4 only handles :ok and :no_beam_info clauses, so an unknown error reason would crash Mix with a MatchError there anyway. Macro.inspect_atom/2 is used for the module name since the Inspect protocol cannot be relied on during consolidation.

Tests

The new test in lib/elixir/test/elixir/protocol/consolidation_test.exs builds a protocol beam that mimics the pre-1.21 layout (persisted __protocol__ attribute and __protocol__/1, without the new internal definitions). It fails with MatchError before this change and passes after.

Verified locally (OTP 29.0.5): full make test_stdlib (7574 tests) and make test_mix (948 tests) pass, plus an end-to-end smoke test recompiling a Mix project with a stale _build from another Elixir version: recompilation happens cleanly, protocols consolidate, and both concrete and Any-fallback dispatch work.


This contribution was written with the assistance of AI tooling and reviewed in full by the human submitter, who certifies the Developer Certificate of Origin via the Signed-off-by trailer on the commit.

Assisted-by: OpenCode:glm-5.3:cloud

Commit 558215b ("Dispatch consolidated protocols through literal
external funs") changed the definitions generated by defprotocol to
__select_impl__/1, __dispatch_impl__/4, and __select_struct__/1, and
Protocol.consolidate/5 assumed they are always present. Consolidating
a protocol beam compiled before that change raised a bare MatchError
(no match of right hand side value: nil) whenever an existing _build
was reused, since Mix manifests do not record the Elixir version and
the version string stayed 1.21.0-dev.

* Bump Mix.Compilers.Elixir's @manifest_vsn from 37 to 38 so builds
  compiled with previous versions are fully recompiled
* Raise a proper ArgumentError with an actionable message in
  Protocol.consolidate/5 when the expected definitions are missing,
  instead of a bare MatchError

Fixes elixir-lang#15994

Assisted-by: OpenCode:glm-5.3:cloud
Signed-off-by: Jonathan Gnagy <jonathan@therubyist.org>
Comment thread CHANGELOG.md Resolved
Comment thread CHANGELOG.md Resolved
Co-authored-by: José Valim <jose.valim@gmail.com>
@jgnagy

jgnagy commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the assist! Sorry if the extra tests were too much.

@josevalim
josevalim merged commit e8c7060 into elixir-lang:main Oct 17, 2026
15 checks passed
@josevalim

Copy link
Copy Markdown
Member

No sweat, all good!

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Protocol consolidation crashes with MatchError after 558215b when an existing _build is reused

2 participants