Escaping anonymous function in module attribute

I’m currently writing a specie of DSL and I’m trying to understand how can I escape an anonymous function in a module attribute. I’m not sure how it can be achieved and even tried without success to understand how Absinthe does it with the resolve macro (e.g. field :name, :string, resolve: _, _ -> ... end [Absinthe.Type.Field — absinthe v1.7.6]). This is what I have tried so far:

defmodule DSL do
  defmacro __using__(_opts) do
    quote do
      import DSL, only: :macros

      Module.register_attribute(__MODULE__, :columns, accumulate: true)

      @before_compile DSL
    end
  end

  defmacro __before_compile__(_env) do
    quote do
      def __columns__, do: Enum.reverse(@columns)
    end
  end

  defmacro column(field, resolver) do
    quote do
      @columns {unquote(field), unquote(resolver)}
    end
  end
end

defmodule User do
  use DSL

  defstruct ~w[first_name last_name email]a

  column :name, fn user -> "#{user.first_name} #{user.last_name}" end
  column :email, fn user -> user.email end

  def test do
    user = %__MODULE__{first_name: "John", last_name: "Silva"}
    columns = __MODULE__.__columns__()

    then(user, columns[:name])
  end
end

which fails with:

** (ArgumentError) cannot inject attribute @columns into function/macro because cannot escape #Function<1.54200728/1 in :elixir_compiler_0.__MODULE__/1>. The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, PIDs and remote functions in the format &Mod.fun/arity
    (elixir 1.16.0) lib/kernel.ex:3648: Kernel.do_at/5
    (elixir 1.16.0) expanding macro: Kernel.@/1
    /home/wigny/workspace/dsl.exs:25: User.__columns__/0
    /home/wigny/workspace/dsl.exs:25: DSL.__before_compile__/1

I’m aware I could simply solve it by replacing the anonymous function call with a captured function call, but I would like to understand how libraries like Absinthe solve this problem…

and Happy New Year to everybody :).