Can this error be caught by a tool (mix compile, Dialyzer, etc.)?

TL;DR
A single element, in this case an integer, is given to a Phoenix.Component but a list of such elements should be given. Can a compile-time warning/error be produced by any tool?

I’ve made a repro project. The gist of it is that

  1. A.Foo returns an integer to
  2. AWeb.ALive and it in turn (incorrectly) forwards this integer (rather than a list of integers) to
  3. AWeb.AComponent.
#1
defmodule A.Foo do
  @spec number() :: pos_integer()
  def number() do
    1729
  end
end

#2
defmodule AWeb.ALive do
  use AWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok,
     socket
     |> assign(:number, A.Foo.number())}
  end

  def render(assigns) do
    ~H"""
    <AWeb.AComponent.bar numbers={@number} />  # Should've been `[@number]`
    """
  end
end

#3
defmodule AWeb.AComponent do
  use Phoenix.Component

  attr(:numbers, :list, required: true)

  @spec bar(%{numbers: list(pos_integer)}) :: any
  def bar(assigns) do
    ~H"""
    <li :for={number <- @numbers}>
      <%= number %>
    </li>
    """
  end
end

Can this be catched by the compiler, Dialyzer, anything? My understanding is “no”, but I hope I’m wrong:

  • attr macro atm only does compile-time validation of literals
  • even @specing the Phoenix.Component above with @spec bar(%{numbers: list(pos_integer)}) :: any doesn’t help, as mix dialyzer gives no warnings.

Is there any other tool that would help with this?

I haven’t looked at the implementation of the heex compiler, but my intuition suggests there is a runtime apply happening versus compiling a direct invocation of the function in the component syntax. If that is the case then there is no static link between the bar function and its invocation. I do not know if that is a detail that can be changed, but I’d start there and at least rule out the possibility that could be done to support dialyzer.

1 Like