Struggling getting dialyzer to accept my @spec

Hi,

I have the following module:

defmodule MyApp.Payment
  use Ecto.Schema

  import Ecto.Changeset

  schema "payment" do
      field :booking_uuid, :string

      belongs_to :booking, Kraken.Bookings.Booking
  end

  @spec update_payment_booking(__MODULE__, integer(), String.t()) :: Ecto.Changeset.t()
  def update_payment_booking(payment, booking_id, booking_uuid) do
    payment
    |> change(%{booking_id: booking_id, booking_uuid: booking_uuid})
  end
end

VS code highlights this spec with a warning stating it is invalid. I don’t understand this as the function takes an instance of itself, an integer and a string, returning a changeset. When I ask VS code to auto complete it for me, it replaces it with:

@spec update_payment_booking(
          {map, map}
          | %{
              :__struct__ => atom | %{:__changeset__ => any, optional(any) => any},
              optional(atom) => any
            },
          any,
          any
        ) :: Ecto.Changeset.t()

Can someone please run me through why it thinks my spec is wrong?

Thanks in advance

Can you show us the callers of that function? Maybe indeed some of them pass a tuple as a first parameter to it?

Thanks for replying,

Sure, it gets called once in my codebase:

 defp handle_confirm_booking_response({:ok, %{booking: booking}}, payment) do
    updated_payment =
      Payment.update_payment_booking(payment, booking.id, booking.uuid)
      |> Payment.update_payment_status(:captured)
      |> Repo.update!()

And I can confirm the first argument in that payment is indeed a Payment struct.

Just for my own info does that mean dialyzer will mark a spec as invalid if it is called with different arguments in a different module? I assumed it would just highlight the offending call rather than the spec itself,

Thanks

I don’t think the __MODULE__ looks right there. It resolves to MyApp.Payment which is the atom :"Elixir.MyApp.Payment and it looks like you’re passing in an instance of the of the schema. Did you mean to write %__MODULE__{} instead?

Or you could do something like

@opaque t :: %__MODULE__{}

@spec update_payment_booking(t(), integer(), String.t()) :: Ecto.Changeset.t()`

(Edited to s/your/you’re/)

2 Likes

I can confirm %__MODULE__{} clears the warning - Thanks for your explanation which makes sense!

1 Like

You probably want __MODULE__.t(). There was even recent discussion here or simply t() as @paulanthonywilson pointed out.

2 Likes