jeremyjh / dialyxir

Mix tasks to simplify use of Dialyzer in Elixir projects.
Apache License 2.0
1.69k stars 141 forks source link

Unknown error occurred in format_long #428

Open Sinc63 opened 3 years ago

Sinc63 commented 3 years ago

Unknown error occurred: %FunctionClauseError{args: nil, arity: 1, clauses: nil, function: :format_long, kind: nil, module: Dialyxir.Warnings.OpaqueTypeTest}

Legacy warning: ets.ex:15: The type test is_reference('undefined' | ets:tid()) breaks the opacity of the term 'undefined' | ets:tid()

Source code: @spec alive?() :: boolean def alive?, do: is_reference(whereis())

Precheck

Environment

Elixir 1.7.4 (compiled with Erlang/OTP 20)

Current behavior

Expected behavior

jeremyjh commented 3 years ago

Hi, is there any chance you can share a code-sample that reproduces this problem?

Sinc63 commented 3 years ago

sample.ex:

defmodule Sample do
  def hello do
    if alive?(), do: :world,
    else: :goodbye
  end

  @spec alive?() :: boolean
  def alive?, do: is_reference(whereis())

  @spec whereis() :: reference | :undefined
  def whereis, do: :ets.whereis(:sample)
end

dialyxir output: lib/sample.ex:2:no_return Function hello/0 has no local return.


lib/sample.ex:8:no_return Function alive?/0 has no local return.


Please file a bug in https://github.com/jeremyjh/dialyxir/issues with this message.

Unknown error occurred: %FunctionClauseError{args: nil, arity: 1, clauses: nil, function: :format_long, kind: nil, module: Dialyxir.Warnings.OpaqueTypeTest}

Legacy warning: lib/sample.ex:8: The type test is_reference('undefined' | ets:tid()) breaks the opacity of the term 'undefined' | ets:tid()


done (warnings were emitted) Halting VM with exit status 2

Sinc63 commented 3 years ago

I just had a look at the code in OpaqueTypeTest. I'm quite intrigued by the fact that current source code, reputedly 2 years old, uses the term opaqueness, where my error message says opacity, and I have dialyxir .1.1.0 which is supposed to be current. Why are the words different? Are you building from a branch these days?

jeremyjh commented 3 years ago

Thank you for the sample. We are not using a branch. The word "opacity" in the legacy comment is simply part of the text message that Erlang dialyzer would write to the console. We do not use that message when we format the message in Dialyxir, we use structured information from the term to build the message.

Sinc63 commented 3 years ago

The issue I'm reporting is going to let you eliminate the FunctionClauseError in the Dialyxir code. Is there a good reference that will help me understand how I should fix the actual opacity error? I'm really new to the tool.

jeremyjh commented 3 years ago

I do not really know of a good reference. The only reference I recall reading at first was the dialyzer paper linked in the readme and "Learn You Some Erlang" but that was at a time when there was not a single book written on Elixir, so you had to learn Erlang. There probably are books now that explain it in Elixir terms. You can also ask questions on elixirforums.com. It helps to know Haskell, Scala or OCaml (I've done quite a bit of Haskell) but you really have to develop your own understanding and intuitions as success typing is very different from a whole static type system.

In this specific case the issue might simply be that the spec for :ets.whereis has it returning :ets.tid() | :undefined; I think tid() is an opaque term and you are trying to peel back the cover and look inside by typing it as :reference.

Sinc63 commented 3 years ago

I appreciate that input. That could make sense. Now I have an idea what opaque means. I'll do some digging. Maybe the return :undefined is equivalent to is_reference returning false. That is, that if :ets.whereis returns a tid() then the process is up and :undefined means that it is not. That would make alive? just need to be whereis() != :undefined.

jeremyjh commented 3 years ago

Yes probably so, you can't call is_reference on a tid(). You should also change the @spec for Sample.whereis to match the return spec for :ets.whereis, e.g. @spec whereis() :: :ets.tid() | :undefined