Typespec dialyzer error - the @spec for the function does not match the success typing of the function

Hello, I learning Elixir, and for that I building an Elixir library to connect to Odoo ERP.
I have the code and functions docs, and works ok, but now I’m adding the typespecs and tests before add the package to hex.pm

Dialyzer gives me the next error:

Total errors: 1, Skipped: 0, Unnecessary Skips: 0
done in 0m1.96s
lib/odoo.ex:113:invalid_contract
The @spec for the function does not match the success typing of the function.

Function:
Odoo.search_read/3

Success typing:
@spec search_read(%Odoo.Session{:cookie => _, :url => binary(), :user_context => _, _ => _}, _, Keyword.t()) ::
  {:error, <<_::64, _::size(8)>>} | {:ok, %Odoo.Result{:data => _, :model => _, :opts => _}}

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

And the function:

  @type options ::
          {:limit, non_neg_integer()}
          | {:offset, non_neg_integer()}
          | {:order, String.t()}
          | {:fields, [String.t(), ...]}
          | {:domain, [list(), ...]}
  @spec search_read(%Odoo.Session{}, String.t(), options) ::
          {:ok, %Odoo.Result{}} | {:error, String.t()}
  @spec search_read(%Odoo.Session{}, String.t()) :: {:ok, %Odoo.Result{}} | {:error, String.t()}
  def search_read(odoo = %Odoo.Session{}, model, opts \\ []) do
    Odoo.Core.search_read(odoo, model, opts)
  end

This is a function with accepts opts or not, and opts are a Keyword List.
On the other hand, in the structs modules, I do not have a type por t(), but I guess it is not necessary when using the %Odoo.mystruct{} notation in the spec.

I’m wondering if I should really indicate all arguments in @spec %Odoo.Session{} and %Odoo.Result{} or maybe configure and use %Odoo.Session.t() format.

Repo: elixir odoo repo - branch develop

Any help is welcome, thanks!

I believe this is because

  @type options ::
          {:limit, non_neg_integer()}
          | {:offset, non_neg_integer()}
          | {:order, String.t()}
          | {:fields, [String.t(), ...]}
          | {:domain, [list(), ...]}

is declaring that options is one of a list of 2-tuples. I think you meant to spec a list of options like:

  @spec search_read(%Odoo.Session{}, String.t(), [options, ...]) ::
          {:ok, %Odoo.Result{}} | {:error, String.t()}

where [options, ...] corresponds to a maybe empty Keyword.t.

If this case I would rename @type options .... to be @type option .... to better reflect what you have declared.

3 Likes

Thanks, @kip , that was the solution!
Now I can continue with the next specs.

Sol:

@type option ::
          {:limit, non_neg_integer()}
          | {:offset, non_neg_integer()}
          | {:order, String.t()}
          | {:fields, [String.t(), ...]}
          | {:domain, [list(), ...]}
  @spec search_read(%Odoo.Session{}, String.t(), [option, ...]) ::
          {:ok, %Odoo.Result{}} | {:error, String.t()}
  @spec search_read(%Odoo.Session{}, String.t()) :: {:ok, %Odoo.Result{}} | {:error, String.t()}
  def search_read(odoo = %Odoo.Session{}, model, opts \\ []) do
    Odoo.Core.search_read(odoo, model, opts)
  end

Dialyzer:

Total errors: 0, Skipped: 0, Unnecessary Skips: 0
done in 0m2.2s
done (passed successfully)