Use typespec in the correct way

Hi all
Can please someone tell me, if I am using TypeSpec in the right way?

@spec validate(String.t) :: {:atom}
  def validate(path) do
    res = System.cmd("gs", ["\-o /dev/null", "\-sDEVICE=nullpage", "\#{path}"])
    valid?(res)
  end

as parameter the function expect type string and return a tuple atom.

The whole module looks like:

defmodule Pdf.Inspector do

  @spec validate(String.t) :: {:atom}
  def validate(path) do
    {_, code} = System.cmd("gs", ["-o /dev/null", "-sDEVICE=nullpage", path])
    valid?(code)
  end

  defp valid?(0) do
    {:ok}
  end

  defp valid?(1) do
    {:error}
  end

end

So when I run the dialyzer, it says me:

mix dialyzer
Starting Dialyzer
dialyzer --no_check_plt --plt /Users/developer/.dialyxir_core_19_1.3.2.plt -Wunmatched_returns -Werror_handling -Wrace_conditions -Wunderspecs /Volumes/Dev/elixir/pdf/_build/dev/lib/pdf/ebin
  Proceeding with analysis...
inspector.ex:3: Invalid type specification for function 'Elixir.Pdf.Inspector':validate/1. The success typing is (binary()) -> {'error'} | {'ok'}
 done in 0m1.33s
done (warnings were emitted)

Thanks

1 Like

I haven’t used Dialyzer much, but this seems to be ok? Could you try with {atom()}?

EDIT: why are you using {:ok} and {:error} as return values? Why not just the atom without the tuple?

2 Likes

I did it with

@spec validate(String.t) :: {:ok} | {:error}

Thanks

Why are you doing {:ok} | {:error} instead of :ok | :error ? There’s no point in wrapping it in a tuple.

2 Likes

The output you got from dialyzer uses erlang syntax, so you need to know, how to translate it to elixir syntax, to actually make sense from it.

  • Erlang does not have nested modules, so elixir modules are prepended by Elixir. and then used as modulename completely.
  • Erlang does separate module and function name by a colon, not a dot.
  • Atoms are just written as they are, but for some special cases the are enclosed in singlequotes. Dialyzer output does use singlewquotes unconditionally.
  • Typenames in erlang have to use parens.

Knowing this, we can read the output line (which was Invalid type specification for function 'Elixir.Pdf.Inspector':validate/1. The success typing is (binary()) -> {'error'} | {'ok'}) as Invalid type specification for function Pdf.Inspector.validate/1. The success typing is (String.t) -> {:error} | {:ok}.

So dialyzer complains, that you are returning either {:ok} or {:error}, while you are specifying to return ONLY {:atom}. So dialyzer does tell you which was the correct type of your function.

If you want to specify any atom you have to use {atom} instead of {:atom}.


Additionally to your question which is related to dialyzer, I do see some problems as well, which I want to address here only in short:

  • Instead of using an unary tuple, in most cases it would be better to use the value directly
  • gs will probably always fail, for a further axplanation why, see your other thread about System.cmd/2 (https://elixirforum.com/t/double-quotes-in-string/1489?u=nobbz)
  • gs does not only exit with 0 or 1 but with any integer, 0 meaning “no error” and any other value signals an error. Which value is for what kind of error is written somewhere in the gs manual.
5 Likes