Writing a Macro to Rewrite a Block

Hello! I’ve got a little macro question for which it seems there should be a simple answer, but it escapes me. This is for my Versioned library which is having some fun with macros, enhancing Ecto.Schema and Ecto.Migration, and I’d appreciate any feedback on the library in general!

Ok, so in this case, I’m working to extend Absinthe.Schema.Notation where you declare an object block with fields. My versioned_object wrapper simply declares :my thing and :my_thing_version both, so my complimentary “version” object can be automatically injected with the same fields. This is all working:

I think I’d now like to add an alternative to absinthe’s field called version_field. When this is used, it should declare a field with the given type and options for the version but leave it out completely for the base (non-version) object.

I’m trying to make this happen just like I did in another module (Versioned.Schema) where the versioned_object macro would open up the {:__block__, _, lines} and rewrite the lines as needed. This means that when I create the :my_thing object, I’m wanting to leave the version_field lines out, and when I create the :my_thing_version object, I want to rewrite version_field lines to be field ones.

The issue is that I don’t seem to actually need a version_field macro at all since the wrapping versioned_object (block) macro is going to iterate over the ast and rewrite it before I need it to do anything haha… If I don’t write the version_field fun/macro, I get an “undefined function” error, even though a compile-time IO.inspect reveals that it has already translated it to the ast for a field call ({:field, [...], [...]}). If I still need one, what would I need a version_field fun/macro to do? Is there a completely different approach that I need to take?

versioned_object :my_thing do
  field :name, :string
  version_field :other_thing_version, non_null(:other_thing_version), resolve: &Bla.get_blah/3
end

# From this, I would want to essentially translate to:

object :my_thing do
  field :name, :string
end

object :my_thing_version do
  field :name, :string
  field :other_thing_version, non_null(:other_thing_version), resolve: &Bla.get_blah/3
end

Hopefully that covers it, but let me know if might be able to make my question more clear. I really very much appreciate any thoughts!

1 Like

I know macros work with successive expansions until all is expanded. Based on the debugging I mentioned, it seems that the versioned_object macro has already replaced the {:version_field, ...} ast with {:field, ...} so I’m left wondering why I need a version_field macro at all. But of course I’m missing something here about how the macros/compilation are working.