Skip to content

Commit

Permalink
Fix Dialyzer warnings on opaque protocol calls (#5286)
Browse files Browse the repository at this point in the history
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 <jose.valim@plataformatec.com.br>
  • Loading branch information
margnus1 authored and José Valim committed Oct 4, 2016
1 parent f7b3248 commit 6e2c29c
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 0 deletions.
10 changes: 10 additions & 0 deletions lib/elixir/lib/protocol.ex
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,16 @@ defmodule Protocol do
end
end, builtin)

# Define a catch-all impl_for/1 clause to pacify Dialyzer (since
# destructuring opaque types is illegal, Dialyzer will think none of the
# previous clauses matches opaque types, and without this clause, will
# conclude that impl_for can't handle an opaque argument). This is a hack
# since it relies on Dialyzer not being smart enough to conclude that all
# opaque types will get the any_impl_for/0 implementation.
Kernel.def impl_for(_) do
any_impl_for()
end

@doc false
@spec impl_for!(term) :: atom | no_return
Kernel.def impl_for!(data) do
Expand Down
21 changes: 21 additions & 0 deletions lib/elixir/test/elixir/fixtures/dialyzer/protocol_opaque.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule Dialyzer.ProtocolOpaque do
def circus() do
Dialyzer.ProtocolOpaque.Entity.speak(Dialyzer.ProtocolOpaque.Duck.new)
end
end

defprotocol Dialyzer.ProtocolOpaque.Entity do
def speak(entity)
end

defmodule Dialyzer.ProtocolOpaque.Duck do
@opaque t :: %__MODULE__{}
defstruct feathers: :white_and_grey

@spec new :: t
def new(), do: %__MODULE__{}

defimpl Dialyzer.ProtocolOpaque.Entity do
def speak(%Dialyzer.ProtocolOpaque.Duck{}), do: "Quack!"
end
end
7 changes: 7 additions & 0 deletions lib/elixir/test/elixir/kernel/dialyzer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ defmodule Kernel.DialyzerTest do
assert_dialyze_no_warnings! context
end

test "no warnings on protocol calls with opaque types", context do
copy_beam! context, Dialyzer.ProtocolOpaque
copy_beam! context, Dialyzer.ProtocolOpaque.Entity
copy_beam! context, Dialyzer.ProtocolOpaque.Duck
assert_dialyze_no_warnings! context
end

defp copy_beam!(context, module) do
name = "#{module}.beam"
File.cp! Path.join(context[:base_dir], name),
Expand Down

0 comments on commit 6e2c29c

Please sign in to comment.