Error in macro that defines a module with a macro inside

I have some relatively legitimate reasons do do something like this (even if you think this is not even remotely legitimate, the question at the end is independent from this):

defmodule MySnippet do
  defmacro __using__(_) do
    quote do
      def common_function1() do
        # ...
      end

      def common_function2() do
        # ...
      end
      # etc...
    end
  end
end

defmodule MyModule1 do
  use MySnippet

  def specific_function1() do
    # ...
  end

  def specific_function2() do
    # ...
  end
  # etc...
end

defmodule MyModule2 do
  use MySnippet

  def specific_function_a() do
    # ...
  end

  def specific_function_b() do
    # ...
  end
  # etc...
end

I’ve thought about defining a defsnippet macro that exapnds so that I coould do this:

require Snippet
Snippet.defsnippet MySnippet do
  def f(x), do: x
  def g(x), do: x
end

This way, it looks like a normal module definition and is easier to type.
I’ve written the following macro, which tries to encapsulate the module definition and the __using__/1 macro definition.

defmodule Snippet do
  defmacro defsnippet(module_name, [do: body]) do
    quote bind_quoted: [module_name: module_name, body: body] do
      defmodule module_name do
        @doc false
        defmacro __using__(_opts) do
          quote do
            body
          end
        end
      end
    end
  end
end

But when I try to run it like this

# lib/schism/example.ex
require Snippet
Snippet.defsnippet MySnippet do
  def f(x), do: x
  def g(x), do: x
end

I get this error:

== Compilation error in file lib/schism/example.ex ==
  ** (ArgumentError) cannot invoke def/2 outside module
      (elixir) lib/kernel.ex:5142: Kernel.assert_module_scope/3
      (elixir) lib/kernel.ex:3905: Kernel.define/4
      (elixir) expanding macro: Kernel.def/2
      lib/schism/example.ex:4: (file)
      expanding macro: Snippet.defsnippet/2
      lib/schism/example.ex:3: (file)

I don’t understand where this comes from, because I’m not calling def/2 outside of a module. I’m just taking some AST that happens to contain defs and putting it inside a macro which I can invoke later.

Doing all of this just to save some typing is probably not worth it and I don’t know if I’ll end up going that way, but no I’m curious about what the problem is independently of the rest.

I am not sure if it fixes the whole issue (I believe it does,) but you definitely should use Module.create/3 not Kernel.defmodule/2 in this context.

Eh I think defmodule is fine here.

However the issue is that you cannot use bind_quoted in this way (I learned that well long ago). Basically what bind_quoted will do is turn the code into something like this from the return of your macro:

module_name = MySnippet

body =
  (
    def f(x), do: x
    def g(x), do: x
  )

defmodule(module_name) do
  @doc false 
  defmacro(__using__(_opts)) do
    quote do
      body
    end
  end
end

And this is why you are getting the def is not allowed here message. I’ve always had to do ‘fun’ unquote messing when I had to do similar things. So if you Macro.escape the body in bind_quoted then use unquote(body) inside the function then it might work (you might need to ‘escape’ the inner quote though or something like that, I usually just don’t use bind_quoted at all in this case and just unquote through the layers as needed or build the AST manually without quote…).

It doesn’t. In fact, defmodule doesn’t have anything to do with it. OverminDL1 has given the correct answer. Thanks for your input anyway!