Extend a macro to wrap a struct type

I’ve got a wrapper module that I am using around a library. The purpose of this module is to add small behavior to the library without outright forking it. I have a macro that produces the appropriate defdelegate and defoverridable calls on all of the functions that I need from library but I find myself writing a ton of boilerplate that could probably also be in the macro.

I have a struct called LibraryWrapper that looks like this:

defstruct type: Library.new(...)

For 1 arity functions I have a generalization that looks like this:

alias LibraryWrapper, as: LW

def foo(%LW{type: type}), do: Library.foo(type)
defdelegate foo(type), to: Library

For 2 arity functions, I have a generalization that looks like this:

alias LibraryWrapper, as: LW

def bar(%LW{type: type_a}, %LW{type: type_b}), do: Library.bar(type_a, type_b)
def bar(type_a, %LW{type: type_b}), do: Library.bar(type_a, type_b)
def bar(%LW{type: type_a}, type_b), do: Library.bar(type_a, type_b)
defdelegate(type_a, type_b), to: Library

If there were higher arity functions, the pattern would continue (but there aren’t for this particular library). Heres what the macro I am using looks like currently; and as you will see the defdelegate calls inside of it are completely irrelevant because the overridden functions override these making it so that they will never match. But if I could add a general pattern for all arity functions before the defdelegate call then I could just use the macro and not write all of the boilerplate.

defmacro wrap(module) do
  module= Macro.expand(module, __CALLER__)
  functions = module.__info__(:functions)

  sigs = 
    Enum.map(functions, fn {name, arity} ->
       args = 
           if arity == 0 do
             []
            else
              Enum.map(1..arity, fn i ->
                {String.to_atom(<<?A + i - 1>>), [], nil}
              end)
            end
        {name, [], args}
    end)

  combined = List.zip([sigs, functions])

  for comb <- combined do
    quote do 
      defdelegate unquote(elem(comb, 0)), to: unquote(module)
      defdelegate unquote([elem(comb, 1)])
     end
   end
end

Where I am running into issues is trying to figure out how to build the AST for getting the field out of the struct that I plan to pass into the macro. Also, I am not entirely sure how to build AST where the function matches on the specific struct type as I am doing above. I also have to wonder if there is a better way to do this apart from making a struct wrapper.

Was able to figure it out with a pretty janky macro. But at least it seems to work in this case:

defmacro __using__(modules) do
    defs =
      Enum.reduce(modules, [], fn mod_ast, acc ->
        exports =
          mod_ast
          |> Macro.expand(__ENV__)
          |> apply(:module_info, [:exports])

        pre_defs = [module_info: 0, module_info: 1, __info__: 1]

        [{mod_ast, exports -- pre_defs} | acc]
      end)

    for {module, exports} <- defs do
      for {func_name, arity} <- exports do
        args = Macro.generate_arguments(arity, __MODULE__)
        body = make_body(args)
        quote do
          def unquote(func_name)(unquote_splicing(args)) do
            unquote_splicing(body)
            unquote(module).unquote(func_name)(unquote_splicing(args))
          end

          defoverridable unquote([{func_name, arity}])
        end
      end
    end
  end

defp make_body(args) do
    Enum.map(args, fn arg ->
      quote do
        unquote(arg) =
          case Map.get(unquote(arg), :type) do
            nil ->
              unquote(arg)

            type ->
              type
          end
      end
    end)
  end

I know this can be cleaned up significantly but at least it works for my purposes.