Is there any way to ensure that a specific function will not be implemented on module?
The example: I do not want that module A implements the function explode_system/1, and if I do this
defmodule A do
def explode_system(system), do: IO.puts "boom!"
end
Then generates a warning or a compile time error.
Another example with macros if I have a template using use MyTemplate that inject functions I do want the target module override this functions. So, if I have this below I get a error/warning:
defmodule MyTemplate do
defmacro __using__(_opts) do
def explode_system(system) do
IO.puts("Carefully explodes the system...")
end
end
end
defmodule MyModule do
use MyTemplate
# if I try to do this get an error/warning
def explode_system(system), do: IO.puts("boom!")
end
But inside the __before_compile__/1 when I do, List.first(env.context_modules) |> Module.definitions_in() the definition injected by macro via use MyTemplate already is there.
Your example almost works; I added a quote block to make the __using__ a valid macro:
defmodule MyTemplate do
defmacro __using__(_opts) do
quote do
def explode_system(system) do
IO.puts("Carefully explodes the system...")
end
end
end
end
defmodule MyModule do
use MyTemplate
# if I try to do this get an error/warning
def explode_system(system), do: IO.puts("boom!")
end
running that in iex produces a warning:
warning: this clause for explode_system/1 cannot match because a previous clause at line 17 always matches
iex:20
but just putting a guard on injected function stop the warning, and the behave wanted is none function with this name must be present in module
defmodule MyTemplate do
defmacro __using__(_opts) do
quote do
def explode_system(system) when is_integer(system) do
IO.puts("Carefully explodes the system...")
end
end
end
end
A possible solution that I find is using the advice of @hst337 of use Module.definitions_in + inject a @before_compile in the module that is using the template:
defmodule MyTemplate do
defmacro __using__(_env) do
quote do
@before_compile MyTemplate
end
end
defmacro __before_compile__(%Macro.Env{} = env) do
definitions =
env.module
|> Module.definitions_in()
|> Keyword.keys()
|> Enum.uniq()
if :explode_system in definitions do
IO.warn("Do not explode the system dude")
end
end
quote do
def explode_system() do
# ...
end
end
end
Edit: If the opts params from defmacro __using__(opts) is needed , a alternative is setup it in a module attribute and retrieve on __before_compile__/1
defmodule MyTemplate do
defmacro __using__(opts) do
quote do
@before_compile MyTemplate
@opts unquote(opts)
end
end
defmacro __before_compile__(%Macro.Env{} = env) do
# do your stuffs
opts = Module.get_attribute(env.module, :opts)
quote do
# now can use unquote(opts)
def my_function() do
x = unquote(opts)
end
end
end
end