Wrapping Ecto.Schema in macro results in transitive compile time dependency

By following the practice in Ecto.Schema docs to set schema attributes globally (Ecto.Schema — Ecto v3.7.2), I realized that transitive compile time dependencies are created when defining a protocol implementation for an ecto schema.

Please check this example, it’s an empty phoenix project with this:

Ecto.Schema wrapper:

defmodule ProtoComp.Schema do
  defmacro __using__(_) do
    quote do
      use Ecto.Schema

      @timestamps_opts type: :utc_datetime_usec
    end
  end
end

Schema:

defmodule ProtoComp.Things.Chair do
  use ProtoComp.Schema
  import Ecto.Changeset

  schema "chairs" do
    field :attributes, :string
    field :color, :string

    timestamps()
  end

  @doc false
  def changeset(chair, attrs) do
    chair
    |> cast(attrs, [:color, :attributes])
    |> validate_required([:color, :attributes])
  end
end

Protocol:

defprotocol ProtoCompWeb.UseCases.GrabWith do
  def accessories(thing)
end

Implementation:

defimpl ProtoCompWeb.UseCases.GrabWith, for: ProtoComp.Things.Chair do
  def accessories(_), do: "use your hands"
end

And nothing else. Now, when trying to check the compile-linked dependencies with mix xref graph --label compile-connected --fail-above 0, the result is that the Chair schema is a transitive dependency for the protocol implementation.

What I don’t understand is why this happens, and why it’s not happening when I remove the Ecto.Schema wrapper and use it directly in the Chair schema, like this:

defmodule ProtoComp.Things.Chair do
  use Ecto.Schema
  import Ecto.Changeset

  @timestamps_opts type: :utc_datetime_usec

  schema "chairs" do
    field :attributes, :string
  ...
end

What’s making this difference? I think this approach shouldn’t raise a transitive dependency in any of those circumstances.

In case someone wants the example repo, here the link: GitHub - ribanez7/protocols_compilation

It happens because compile-connected only checks for modules within the same app. Ecto.Schema is not in the same app. In any case, this is a bug in Elixir. :for should not add a compile-time dependency. Can you please open up a bug report?

Thanks @josevalim, sure, I’ll create the bug report.

Here it is: `:for` creates compile-time dependencies when defining protocol implementations · Issue #11706 · elixir-lang/elixir · GitHub

3 Likes