Get a struct definition from `__before_compile__/2` callback

Prior to v1.18.0 I used the following vode to reach the struct definition in a module from __before_compile__/2 callback.

    defp fields(module) do
      module
      |> Module.get_attribute(:__struct__, %{})
      |> Map.delete(:__struct__)
      |> Map.keys()
    end

I know that’s the undocumented, internal feature, but I did not manage to find a better way of doing this. In v1.18.0 this :__struct__ module attribute has gone. By digging down the rabbit hole, I managed to do the following:

    defp fields(module) do
      with {set, _bag} <- module |> :elixir_module.data_tables(),
           true <- :ets.member(set, {:elixir, :struct}),
           [{{:elixir, :struct}, [_ | _] = fields}] <- :ets.lookup(set, {:elixir, :struct}) do
        for %{field: field} <- fields, do: field
      end
    end

While it works, it looks extremely hacky and might break again tomorrow. Is there the correct way of doing that that I am missing?

I’d unimport Kerner.defstruct, import your own wrapper (I expect you have a use MyModule) and have it persist the information you need in a way your before compile callback can access the information.

I hate to imply limitations like “you must use This” before calling a standard library macro defstruct/1 onto the user. I expect the code below to work smoothly

defstruct [:foo, :bar]

use MyModule

In any case, I expect to be able to get a struct definition from the __before_compile__/2 callback, and I find the necessity to hack it quite disturbing.

Apologies for a likely uninformed question but don’t compilation tracers give you what you need? One of them is this:

  • {:struct_expansion, meta, module, keys} - traced whenever module’s struct is expanded. meta is the struct AST metadata and keys are the keys being used by expansion

https://hexdocs.pm/elixir/1.18.0-rc.0/Macro.html#struct_info!/2

This function is also capable of expanding structs defined under the module being compiled.

That might be the new way to go then based on the changelog.

2 Likes

Thanks a ton! I must have been reading the changelog without a brain involved.

Sure they would, but attaching a tracer to get a list of struct keys looks like an overkill to me.

And if you need to support previous versions, there is Macro.struct!.

2 Likes