I’m trying to create a macro that takes in input a module name and automatically creates a defdelegate for each function defined in that module.
Example
defmodule MyApp.TargetModule1 do
def foo(name, opts \\ []) do
name <> to_string(opts)
end
def bar(name) do
String.uppercase(name)
end
end
defmodule MyApp.TargetModule2 do
def baz(name) do
"Ciao" <> name
end
end
defmodule MyApp.DelegateModule do
delegate_all MyApp.TargetModule1
# the line above will generate the following:
# defdelegate foo(name, opts \\ []), to: MyApp.TargetModule1
# defdelegate bar(name), to: MyApp.TargetModule1
delegate_all MyApp.TargetModule2
# the line above will generate the following:
# defdelegate baz(name), to: MyApp.TargetModule2
end
What I’ve done so far
I achieved this using Code.fetch_docs/1 and Code.Typespec.fetch_specs/1 but unfortunately these functions won’t work unless the .beam file isn’t already written on disk so when I modify any target module I get the :module_not_found error.
I know I can get the function list with MyApp.TargetModule1.__info__(:functions) but this will return a simple list of functions and arities without arguments names and possibly default arguments.
Questions
I already looked into ExUnit.DocTest but it also uses Code.fetch_docs/1 and I don’t know how does it work! Tests are compiled after the main lib files?
How can I get function signature (possibly also specs and documentations) from a module at runtime?
The macro does include a require Module into the testcase, which makes the compiler enforce that Module is compiled before the file/module having the require Module.
I also include a require Module and indeed when I call Code.ensure_loaded/1 I get an {:ok, module} response but nevertheless if I call Code.fetch_docs/1 right after the requrie and ``Code.ensure_loaded/1 I get amodule_not_found` error.
With mod.__info__(:functions) and mod.module_info(:functions) I can’t know parameters name and which parameter ha a default.
My current work around, since I’m working on an umbrella project, is to put every delegate module in a separate app. In my specific case this workaround should work, but I was hoping in a more reliable solution.