Skip to content

Commit 6e2c29c

Browse files
margnus1José Valim
authored and
José Valim
committed
Fix Dialyzer warnings on opaque protocol calls (#5286)
Prior to this change, calling a protocol function with an opaque type would yield a warning, as Dialyzer concludes that the impl_for/1 function can't handle opaque arguments, since all clauses would destructure their arguments in some way. By adding a catch-all clause that does not destructure its argument, Dialyzer no longer draws this conclusion, and the warnings go away. As noted in the protocol.ex comment, this is technically a hack as it relies on Dialyzer not being smart enough. However, I would not expect it to break soon, if ever. Signed-off-by: José Valim <[email protected]>
1 parent f7b3248 commit 6e2c29c

File tree

3 files changed

+38
-0
lines changed

3 files changed

+38
-0
lines changed

lib/elixir/lib/protocol.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,16 @@ defmodule Protocol do
451451
end
452452
end, builtin)
453453

454+
# Define a catch-all impl_for/1 clause to pacify Dialyzer (since
455+
# destructuring opaque types is illegal, Dialyzer will think none of the
456+
# previous clauses matches opaque types, and without this clause, will
457+
# conclude that impl_for can't handle an opaque argument). This is a hack
458+
# since it relies on Dialyzer not being smart enough to conclude that all
459+
# opaque types will get the any_impl_for/0 implementation.
460+
Kernel.def impl_for(_) do
461+
any_impl_for()
462+
end
463+
454464
@doc false
455465
@spec impl_for!(term) :: atom | no_return
456466
Kernel.def impl_for!(data) do
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
defmodule Dialyzer.ProtocolOpaque do
2+
def circus() do
3+
Dialyzer.ProtocolOpaque.Entity.speak(Dialyzer.ProtocolOpaque.Duck.new)
4+
end
5+
end
6+
7+
defprotocol Dialyzer.ProtocolOpaque.Entity do
8+
def speak(entity)
9+
end
10+
11+
defmodule Dialyzer.ProtocolOpaque.Duck do
12+
@opaque t :: %__MODULE__{}
13+
defstruct feathers: :white_and_grey
14+
15+
@spec new :: t
16+
def new(), do: %__MODULE__{}
17+
18+
defimpl Dialyzer.ProtocolOpaque.Entity do
19+
def speak(%Dialyzer.ProtocolOpaque.Duck{}), do: "Quack!"
20+
end
21+
end

lib/elixir/test/elixir/kernel/dialyzer_test.exs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ defmodule Kernel.DialyzerTest do
8181
assert_dialyze_no_warnings! context
8282
end
8383

84+
test "no warnings on protocol calls with opaque types", context do
85+
copy_beam! context, Dialyzer.ProtocolOpaque
86+
copy_beam! context, Dialyzer.ProtocolOpaque.Entity
87+
copy_beam! context, Dialyzer.ProtocolOpaque.Duck
88+
assert_dialyze_no_warnings! context
89+
end
90+
8491
defp copy_beam!(context, module) do
8592
name = "#{module}.beam"
8693
File.cp! Path.join(context[:base_dir], name),

0 commit comments

Comments
 (0)