Can Julia's macros recurse?

Hi there, stoked to be finally be using Julia. I had a question regarding how powerful Julia’s macros can be. I already asked on Stack Overflow, but figured worth pinging here as well.

Basically, I was wondering if it was possible to extract a function’s reachable call graph without fully compiling the code, i.e. with macros. By ‘reachable call graph’, I mean all of the functions (more generally, functors) found in the body of the function that are potentially reachable, and the functions/functors in the bodies of those, and so on. This would be much easier if macros can recurse into the functors found in the caller’s body.

Some code might demonstrate this idea better:

do_something(x::Int) = println(x*2)
do_something(x::String) = println(x)

function foo(a, b)::Bool
    return true

# Ideally something like this would be accessible from Base
functions_in_codebase = [:do_something, :foo]

# Not correct, but gets the idea across
macro access_call_graph(f)
    tokens = f.text.tokens
    graph = [f]
    for t in tokens
        go_deeper = t in functions_in_codebase
        !go_deeper && append!(graph, access_call_graph(get_function_for(t))...)
    return graph

# Should get something like the following, nesting notwithstanding:
# foo
# do_something, do_something

If you can access any kind of call-graph at parse-time, you could use this information to compute the intersection of a Git diff’s changed/modified functions and the union of its call graphs with those of tests, thus allowing automatic filtering and only running tests that are functionally affected by your change. Something that achieves the same as what Mozilla did with Firefox. Instead of ML however we’d be leveraging the expressiveness of Julia itself :slight_smile:

I am aware this sounds like the ramblings of a mad lunatic and just the issue of multiple dispatch alone is terrifying but I’d love to know if something like this could conceivably work.

Macros don’t do what you’re saying but other tools do. Check out Cthulu.jl

1 Like

Recursively traversing the expression tree is pretty common and convenient. The easiest way to do it is for the macro to call a function to walk the expression tree. See, for example, how the @views macro is implemented. The MacroTools.jl package provides some helpful abstractions for this.

That being said, at parse time you don’t have access to the call graph — the parser (and hence macros) only knows how things are “spelled”, not what anything means (e.g. it doesn’t know what called functions refer to). To access the call graph, you need something at a later stage of compilation/interpretation, e.g. Cthulu.jl or Cassette.jl.