Access to some information not available in mix xref

How can I get the following information about a mix project?

  1. For every function or macro (public and private), file and line (EDIT: lines, because of multiple arities) where they are defined.
  2. For all modules, file and line where a module is defined
  3. For every function, macro and module call (from both the project and it’s dependencies), file and line where they are called. For individual functions or modules I can use xref, but I wanted to do this for every function used in the module.

This is to be used on whole projects, and not in a real time way like it’s done in IDEs.

The goal here is to build a visualization tool based on HTML + hyperlinked source.

Should I read mix’s source and borrow from there? Is the information I requested available?

Thanks

  1. If you are using Erlang/OTP 20, Elixir v1.5 has a :debug_info chunk with Elixir AST which will answer this question. Here is a place we get definitions in the Elixir source: https://github.com/elixir-lang/elixir/blob/0125cd05edbd87b8d270b149c05cea161725bedf/lib/elixir/lib/exception.ex#L227-L229 - you can also get the :abstract_code, which is the Erlang AST. It has the benefit of also working with Erlang modules but it loses Elixir specific information

  2. For all modules in a given application, Application.lib_dir(:my_app, "ebin"). Then you traverse the .beam files there, running the instructions from step 1. To get all modules in the system, see :code.get_paths.

  3. The best would possibly be to improve xref to also expose this. The xref “database” is not public in anyway.

4 Likes

Unofficially, what would be the best place to look for the unofficial “database” so that I could have at least a proof of concept? I solemnly promise I’ll never ever complain if anything breaks :smile:

Explore the source. :slight_smile: See the Mix.Tasks.Xref implementation.

Ok thanks!

It’s actually :code.get_path (singular). It’s an obvious typo but it might get people confused if they go the docs and try to search for it :slight_smile:

Wow, mix xref really does sit on top of a tresure trove of data about the code! It’s a shame there is no column information, but this is great. I really hope you open the unofficial database to the public :blush:

1 Like

Is there any way of getting that information for .exs files? Could I compile them somehow and inspect the .beam files?

It is unlikely that we will open it all but, if there is some particular that do you need, we can always expose that.

1 Like

Bringing this back to life, because I’ve had a some time to work on this.

Currently I can get the call places of functions defined in an external module. Is there any way of accessing the call places of functions defined in the same module? Given that there doesn’t seem to be a standard way to do it, I’m thinking of just doing it by parsing the AST of the definitions and detecting the pattern (pseudocode):

{func_name, [line: line], args} when is_atom(func_name) and is_list(args)

Then, I can just lookup {func_name, length(args)} in the list of defs for the current module, and add line line to the list of call sites of the function. I think this would work because even when a module such as A.B.C is aliased, the AST desugars the call into a literal concatenation with .. For example, the expression A.B.C.g(x) in the following module:

defmodule M do
  def f(x), do: A.B.C.g(x)
end

desugars into:

{{:., [line: 3], [A.B.C, :g]}, [line: 3], [{:x, [line: 3], nil}]}

Also, there seems to be no risk from confusing a module-level function call with an anonymous function call, because the expression: f.(x) desugars into:

{{:., [], [{:f, [], Elixir}]}, [], [{:x, [], Elixir}]}

which again I can distinguish from module-level function: the function f is not called with an argument list. The dot is the one that’s called with an argument list, so there’s no confusion.

Is there something I’m missing here, or is this a safe way to proceed?

1 Like

Yes, local calls can be retrieved from both Elixir and Erlang AST without ambiguity. Any import should be expanded by then.

2 Likes

Been a long time since the initial post but did you ever end up making this? I just inherited a slightly chaotic project myself and a tool like this would be extremely useful!

I did, but it used Amnesia (an Elixir interface to :mnesia) and apparently Amnesia used something that broke when Elixir upgraded a minor version number. I’m not in the mood of rewriting the whole database layer right now, so I don’t think I’ll have it working in the near term.

1 Like