The symbols
function is the key in our static analysis. It takes a s-expression, and returns a set of symbols defined by this s-expression.
For Constants, it returns an empty set.
(symbols 2) => #{}
(symbols true) => #{}
(symbols "foo") => #{}
(symbols :foo) => #{}
(symbols nil) => #{}
(symbols #"foobar") => #{}
(symbols *ns*) => #{}
For a symbol, symbols
returns a set with that symbol.
(symbols 'x) => #{'x}
symbols
goes into Clojure structures and aggergates symbols from there.
(symbols '(+ a b)) => #{'+ 'a 'b}
(symbols '[1 a 2 b]) => #{'a 'b}
(symbols {'x 'y}) => #{'x 'y}
(symbols #{'a 'b 3 4 5}) => #{'a 'b}
In a let*
special form, the bindings are removed from the returned set.
(symbols '(let* [x 1 y 2] (+ x y))) => #{'+}
(symbols '(let* [x a] x)) => #{'a}
symbols
expands macros, so it also handles the more familiar let
form
(symbols '(let [x a] x a)) => #{'a}
loop
is supported similarly.
(symbols '(loop [x a y b] a x b y c)) => #{'a 'b 'c}
In the fn*
special form (used by the fn
macro) symbols
removes the argument names from the set.
(symbols '(fn [x y] (+ x y a))) => #{'a '+}
(symbols '(fn foo [x y] (+ x (foo y a)))) => #{'a '+}
The function name (in named functions) is also removed.
(symbols '(fn foo [x] (foo x))) => #{}
Multi-arity functions are supported as well.
(symbols '(fn ([x] (+ a x))
([x y] (+ b x y)))) => #{'a 'b '+}
def
is not allowed inside an expression (it is allowed on the top-level, as described below).
(symbols '(def x 2)) => (throws "def is not allowed")
Similarly, reference to variables is not allowed.
(symbols '#'var) => (throws "vars are not allowed")
Quoted symbols are ignored.
(symbols ''(a b c)) => #{}
In special forms such as if
and do
, the form's name is ignored.
(symbols '(if a b c)) => #{'a 'b 'c}
(symbols '(do a b c)) => #{'a 'b 'c}
(symbols '(recur a b)) => #{'a 'b}
Exceptions are not supported because they use Java interop.
(symbols '(throw foo)) => (throws Exception "throw is not allowed. Use error instead")
(symbols '(try foo bar baz)) => (throws "try/catch is not allowed")
While for
is a macro and not a special form, its definition makes use of Java interop, which we disallow. We therefore make for
a special case.
(symbols '(for [x foo] (* x 2))) => #{'foo '*}
Reader macros annonimous functions are supported.
(symbols '#(+ 1 %)) => #{'+}
(symbols '(fn* [x] (+ 1 x))) => #{'+}