 1    symbols: Extract symbols from expressions

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))) => #{'+}