Elixir and Erlang/OTP versions
Erlang/OTP 28 [erts-16.3] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
Elixir 1.19.5 (compiled with Erlang/OTP 28)
Operating system
any
Current behavior
defimpl for: Any creates an inverted compile dependency from the protocol definition to the Any implementation, forming a compile cycle. This causes changes to any.ex to force recompilation of the protocol, and transitively all other implementations.
Protocol:
defprotocol Presentable do
@fallback_to_any true
@spec to_type(t()) :: map() | nil
def to_type(entity)
end
Struct:
defmodule Models.Cat do
defstruct [:name]
end
Implementation:
defimpl Presentable, for: Models.Cat do
def to_type(%Models.Cat{name: name}), do: %{kind: "cat", name: name}
end
Any implementation:
defimpl Presentable, for: Any do
def to_type(_entity), do: nil
end
Running mix xref graph --label compile-connected --fail-above 0 shows:
lib/presentable.ex
└── lib/presentable/any.ex (compile)
** (Mix) Too many references (found: 1, permitted: 0)
The trigger is the existence of defimpl for: Any, not @fallback_to_any true itself. Removing the Any implementation while keeping @fallback_to_any true results in zero compile-connected dependencies.
You can check the repo created to reproduce the issue here: https://github.com/ribanez7/protocol_fallback_bug
Note: On Elixir 1.18.3 (Erlang/OTP 27.3) the scope was larger; every defimpl file gained a compile-connected dependency to the protocol (4 edges instead of 1). It appears 1.19.x partially fixed this but the inverted dependency from the protocol to any.ex remains.
Expected behavior
defimpl for: Any should not add a compile dependency from the protocol to the implementation. I expect to run mix xref graph --label compile-connected --fail-above 0 and have zero compile-connected dependencies.
Elixir and Erlang/OTP versions
Erlang/OTP 28 [erts-16.3] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
Elixir 1.19.5 (compiled with Erlang/OTP 28)
Operating system
any
Current behavior
defimpl for: Anycreates an inverted compile dependency from the protocol definition to theAnyimplementation, forming a compile cycle. This causes changes toany.exto force recompilation of the protocol, and transitively all other implementations.Protocol:
Struct:
Implementation:
Any implementation:
Running
mix xref graph --label compile-connected --fail-above 0shows:The trigger is the existence of
defimpl for: Any, not@fallback_to_any trueitself. Removing theAnyimplementation while keeping@fallback_to_any trueresults in zero compile-connected dependencies.You can check the repo created to reproduce the issue here: https://github.com/ribanez7/protocol_fallback_bug
Note: On Elixir 1.18.3 (Erlang/OTP 27.3) the scope was larger; every
defimplfile gained a compile-connected dependency to the protocol (4 edges instead of 1). It appears 1.19.x partially fixed this but the inverted dependency from the protocol toany.exremains.Expected behavior
defimpl for: Anyshould not add a compile dependency from the protocol to the implementation. I expect to runmix xref graph --label compile-connected --fail-above 0and have zero compile-connected dependencies.