Detect module functions with specific module attributes

Hey,

is it possible to find out which module functions have specific module attributes?

defmodule MyModule do

  @attr_1
  def my_func_1() do
  end

  @attr_1
  def my_func_2() do
  end

  @attr_2
  def my_func_3() do
  end

  @attr_2
  def my_func_4() do
  end

  @attr_3 "foo"
  def my_func_5() do
  end

  @attr_3 "bar"
  def my_func_6() do
  end
end 

The above example module has 6 functions. Two of them have @attr_1, another two have @attr_2 and the last two functions have @attr_3 but each of them got a unique value.

Now I want to know all functions with the @attr_2 module attribute, or all functions with @attr_3 "foo". Is there something inside Elixir that I could use for that? @doc and @spec kinda work the same, at least the language knows that they belong to a certain function definition.

The position of the @... attribute doesn’t matter in a file, they all get put into a singular global attribute mapping (if they are defined to be stored). If you want positional data then you’ll need to parse it out yourself ‘before’ it ever becomes module attributes and it is still just AST information (a wrapper around defmodule works easy for that).

That means @doc and @spec are special cases handled by the parser/compiler?

Rather they just accumulate the values and the def's essentially just ‘pop’ them off, this is handled by the defmodule macro/specialform, not by the parser. Hence why when you wrap defmodule then you can do whatever else you want. :slight_smile:

This isn’t even valid syntax, as a module attribute always needs a value.

Well, they are module attributes, not function attributes, so basically no.

There are some special attributes like @doc or @spec which are specially treated by def and friends to associate the meta data to the function.

1 Like

It’s valid syntax. It will cause warnings unless another macro transforms it away first, but it’s valid and it will return nil where used. :slight_smile:

Ah thanks guys.

In my case, I could load the file content as string and get its AST and then try to pick out the relationships.

iex(2)> file = File.read!("my_module.ex")
"defmodule MyModule do\n\n  @attr_1\n  def my_func_1() do\n  end\n\n  @attr_1\n  def my_func_2() do\n  end\n\n  @attr_2\n  def my_func_3() do\n  end\n\n  @attr_2\n  def my_func_4() do\n  end\n\n  @attr_3 \"foo\"\n  def my_func_5() do\n  end\n\n  @attr_3 \"bar\"\n  def my_func_6() do\n  end\nend\n\n"
iex(3)> Code.string_to_quoted file
{:ok,
 {:defmodule, [line: 1],
  [
    {:__aliases__, [line: 1], [:MyModule]},
    [
      do: {:__block__, [],
       [
         {:@, [line: 3], [{:attr_1, [line: 3], nil}]},
         {:def, [line: 4],
          [{:my_func_1, [line: 4], []}, [do: {:__block__, [], []}]]},
         {:@, [line: 7], [{:attr_1, [line: 7], nil}]},
         {:def, [line: 8],
          [{:my_func_2, [line: 8], []}, [do: {:__block__, [], []}]]},
         {:@, [line: 11], [{:attr_2, [line: 11], nil}]},
         {:def, [line: 12],
          [{:my_func_3, [line: 12], []}, [do: {:__block__, [], []}]]},
         {:@, [line: 15], [{:attr_2, [line: 15], nil}]},
         {:def, [line: 16],
          [{:my_func_4, [line: 16], []}, [do: {:__block__, [], []}]]},
         {:@, [line: 19], [{:attr_3, [line: 19], ["foo"]}]},
         {:def, [line: 20],
          [{:my_func_5, [line: 20], []}, [do: {:__block__, [], []}]]},
         {:@, [line: 23], [{:attr_3, [line: 23], ["bar"]}]},
         {:def, [line: 24],
          [{:my_func_6, [line: 24], []}, [do: {:__block__, [], []}]]}
       ]}
    ]
  ]}}
iex(4)>
1 Like