This is because macros work on code before the code is compiled. Source code is first parsed to Symbols, literals (integers, floats, strings, etc), or Expr (expressions). At this point, all expressions contain only these three things.** After the macro is done and returns an expression, that expression is compiled into runtime code where more complicated objects like Dicts can exist.
The code below illustrates the difference before and after compiling. Note how 1+5 and Dict() were expressions in the macro body, but is afterward evaluated to an Int64 and a Dict.
# splat arbitrary number of Expr
macro peekExpr(expr1, expr2, expr3tuple...)
println(typeof(expr1), " ", expr1)
println(typeof(expr2), " ", expr2)
println(typeof(expr3tuple), " ", expr3tuple)
:($expr1, $expr2, $expr3tuple)
end
evaluated = @peekExpr 1+5 Dict() Int64 10 max::Int64
#= printout
Expr 1 + 5
Expr Dict()
Tuple{Symbol,Int64,Expr} (:Int64, 10, :(max::Int64))
=#
for item in evaluated println(typeof(item), " ", item) end
#= printout
Int64 6
Dict{Any, Any} Dict{Any, Any}()
Tuple{Symbol,Int64,Expr} (:Int64, 10, :(max::Int64))
=#
**PS: Bear in mind that Expr can contain other objects if you interpolate runtime objects into them (x = Dict(); :(a in $x).args[end] vs :(a in Dict()).args[end]). It's just that macros do not work at a phase where it can access runtime objects. @peekExpr $x will only see a Expr(:$, :x).