How to get the free variables of a function?

Is there a way to get thefree variables of a function?
For example:

myfunc(a) = a+some_func(b,a)
free_variables(myfunc) # returns (:b,)  or  (:b, :some_func) is also fine

A follow-up question (that I will likely have to ask somewhere else): Is there a lint in vscode that marks these variables as they could prevent some bugs?

The technical term for this is “free variables”: Free variables and bound variables - Wikipedia. I don’t know of a utility function that computes that list. Perhaps @jeff.bezanson knows of one.

probably meant nonlocal_vars(myfunc).

In theory, this shoud be derivable from the lowered form of the function.

I got it to work. I’m sure it is not perfect but it works atleast for my test case. Not sure what I need to do with the allowed_modules in the free_variables function.

For future reference, I will edit the title to contain free variables and I will fix my typo in the question.

A short explanation: free variables are put in the lowered code as Main.<>. But also functions get put in it as Main.<>. So we need to filter the valid functions out. (i.e. those that are really defined in Main).

If the goal was to only get free symbols, not free functions. Then in defined_in_main would just become something like getproperty(Main, isa Function.

using MacroTools

macro free_variables(ex0...)
    thecall = InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :free_variables, ex0)
        local results = $thecall
        length(results) == 1 ? results[1] : results

function free_variables(@nospecialize(f), @nospecialize(t=Tuple))
    allowed_from_main = filter(name->getproperty(Main,name) isa Function,names(Main,imported=true))
    allowed_modules = (Core,Base) #setdiff(values(Base.loaded_modules),:Main)
    defined_in_main = union(map(mod->Set(names(mod,imported=true)),allowed_modules)...,allowed_from_main)

    _codelines = code_lowered(f,t)
    length(_codelines) == 0 && error("Wrong arguments given for function $f")
    codelines = length(_codelines) == 1 ? _codelines[1] : _codelines

    set = Set{Symbol}()
    for line_expr in codelines.code
        MacroTools.postwalk(line_expr) do expr
            expr isa GlobalRef && !( in defined_in_main) && push!(set,


function hypot(x,y)
    x = abs(x+c)
    y = abs(y) + func(x)
    if x > y
        r = y/x
        return x*sqrt(1+r*r) + c + d
    if y == 0
        return zero(x)
    r = x/y
    return y*sqrt(1+r*r)

free_vars = free_variables(hypot,(Float64,Float64)) # (:d, :c, :func)
@free_variables hypot(5.,5.)  #  (:d, :c, :func)
1 Like