-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Description
Elixir and Erlang/OTP versions
Erlang/OTP 27 [erts-15.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
Elixir 1.18.1 (compiled with Erlang/OTP 25)
Operating system
macOS 15.2
Current behavior
When compiling a defprotocol with a defstruct that also derives another protocol, there is a compiler error in 1.18+ (1.18.0 and 1.18.1 checked) that did not occur on previous versions.
Example
# hello.exs
defprotocol Hello do
def name(object)
# import Kernel
@derive {Inspect, only: [:name]}
defstruct [:name]
end
Running elixir hello.exs
will cause the following CompileError.
error: undefined function def/2 (there is no such import)
│
6 │ defstruct [
│ ^^^^^^^^^^^
│
└─ hello.exs:6: Inspect.Hello (module)
** (CompileError) hello.exs: cannot compile module Inspect.Hello (errors have been logged)
hello.exs:6: (file)
(elixir 1.18.1) hello.exs:6: Inspect.__deriving__/2
A workaround for the CompileError can be to uncomment the import Kernel
right before deriving.
This can also be seen with any other derived protocol
defprotocol Hello do
def name(object)
@derive JSON.Encoder
defstruct [:name]
end
defimpl inside vs outside defprotocol body
After some more investigation on protocol implementation unrelated to deriving the following does not work in 1.17 or 1.18, and maybe that was always intended to be the case. (unless you import Kernel
for the correct def somewhere)
defprotocol Hello do
def name(object)
defstruct [:name]
# import Kernel
defimpl Inspect do
# import Kernel
def inspect(_struct, _opts) do
"hello"
end
end
end
error: undefined function def/2 (there is no such import)
│
8 │ def inspect(_struct, _opts) do
│ ^
│
└─ hello.exs:9:5: Inspect.Hello (module)
** (CompileError) hello.exs: cannot compile module Inspect.Hello (errors have been logged)
Moving the defimpl outside of the defprotocol body causes it to work in both 1.17 and 1.18, so maybe defimpl in the body was supposed to work?
defprotocol Hello do
def name(object)
defstruct [:name]
end
defimpl Inspect, for: Hello do
def inspect(_struct, _opts) do
"hello"
end
end
Expected behavior
This was discovered when upgrading from 1.17 to 1.18, and it was found that deriving Inspect inside of defprotocol caused a CompileError. The previous behaviour where it compiled without errors would be expected.
After investigation though, I think a little more with protocol implementation is inconsistent. Right now I think it looks something like:
impl method | works in 1.17 | works in 1.18 |
---|---|---|
deriving inside defprotocol body | yes | no |
defimpl inside defprotocol body | no | no |
defimpl outside of defprotocol body | yes | yes |
deriving inside defmodule body | yes | yes |
defimpl inside defmodule body | yes | yes |
defimpl outside of defmodule body | yes | yes |
I think defimpl needs to import to the correct Kernel.def
to make all of the ways to implement a protocol consistent when used on a defprotocol instead of a defmodule, but I'm not sure.