# Determine the scope of symbols in an Expr

I asked about this on Zulip and cleaned it up and posted to Stack Overflow but I figure it might be helpful to cross pollinate here as well. I know I’ve wondered how to do this a few times, so probably other people have too.

Here’s the question:

Is there any way to run name resolution on an arbitrary expression without running it? e.g. I would like to take an expression such as

``````quote
x = 1
y = 2*x + 1
z = x^2 - 1
f(x) = 2*x + 1
end
``````

and be told that the names defined in the scope of this block are `x, y, z, f` and the names `*, +, ^, -` are pulled in from outside the scope of this block. Bonus points if it can tell me that there’s a sub-scope defined in the body of `f` which creates it’s own name `x` and pulls in `+` from an enclosing scope.

This question appeared in the Julia Zulip community

3 Likes

Thanks to @tkf for showing me how to solve this on Zulip

We can get a list of locally defined names in the outermost scope of a julia expression like so:

``````ex = quote
x = 1
y = 2*x + 1
z = x^2 - 1
f(x) = 2*x + 1
end
``````
``````using JuliaVariables, MLStyle

function get_locals(ex::Expr)
vars = (solve_from_local ∘ simplify_ex)(ex).args[1].bounds
map(x -> x.name, vars)
end

julia> get_locals(ex)
4-element Array{Symbol,1}:
:f
:y
:z
:x
``````

and we can get the symbols pulled in from outside the scope like this:

``````_get_outers(_) = Symbol[]
_get_outers(x::Var) = x.is_global ? [x.name] : Symbol[]
function _get_outers(ex::Expr)
@match ex begin
Expr(:(=), _, rhs) => _get_outers(rhs)
Expr(:tuple, _..., Expr(:(=), _, rhs)) => _get_outers(rhs)
Expr(_, args...) => mapreduce(_get_outers, vcat, args)
end
end

get_outers(ex) = (unique! ∘ _get_outers ∘ solve_from_local ∘ simplify_ex)(ex)

julia> get_outers(ex)
6-element Array{Symbol,1}:
:+
:*
:-
:^
``````
1 Like