From c7f7422fd107dcf96f6541e88c9e8e636ddf565b Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Fri, 19 Oct 2018 15:26:27 -0400 Subject: [PATCH] add `@locals` macro for obtaining dictionary of local vars and values implements #29366 --- NEWS.md | 3 ++- base/reflection.jl | 36 ++++++++++++++++++++++++++++++++++++ src/julia-syntax.scm | 22 ++++++++++++++++++++++ test/reflection.jl | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index aefdb6a142ac8..dabf5ace34930 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,7 +5,8 @@ New language features --------------------- * An *exception stack* is maintained on each task to make exception handling more robust and enable root cause analysis using `catch_stack` ([#28878]). - + * The experimental macro `Base.@locals` returns a dictionary of current local variable names + and values ([#29733]). Language changes ---------------- diff --git a/base/reflection.jl b/base/reflection.jl index a62e1ecd54eab..f1597afacf1bf 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -250,6 +250,42 @@ macro isdefined(s::Symbol) return Expr(:isdefined, esc(s)) end +""" + @locals() + +Construct a dictionary of the names (as symbols) and values of all local +variables defined as of the call site. + +# Examples +```jldoctest +julia> let x = 1, y = 2 + Base.@locals + end +Dict{Symbol,Any} with 2 entries: + :y => 2 + :x => 1 + +julia> function f(x) + local y + show(Base.@locals); println() + for i = 1:1 + show(Base.@locals); println() + end + y = 2 + show(Base.@locals); println() + nothing + end; + +julia> f(42) +Dict{Symbol,Any}(:x=>42) +Dict{Symbol,Any}(:i=>1,:x=>42) +Dict{Symbol,Any}(:y=>2,:x=>42) +``` +""" +macro locals() + return Expr(:locals) +end + """ objectid(x) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 65d443440c9c3..302a7fba44345 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -2442,6 +2442,27 @@ (if (not (memq (cadr e) env)) (error "no outer local variable declaration exists for \"for outer\"")) '(null)) + ((eq? (car e) 'locals) + (let* ((names (filter (lambda (v) + (and (not (gensym? v)) + (not (length= (string-split (string v) "#") 2)) + (let ((r (assq v renames))) + (or (atom? r) + (let ((mapping (cdr r))) + (not (and (pair? mapping) + (eq? (car mapping) 'outerref)))))))) + env)) + (names (delete-duplicates + (filter (lambda (v) (not (eq? v '||))) + (map unmangled-name names)))) + (d (make-ssavalue))) + `(block (= ,d (call (call (core apply_type) (top Dict) (core Symbol) (core Any)))) + ,@(map (lambda (v) + (let ((var (resolve-scopes- v env implicitglobals lam renames newlam sp))) + `(if (isdefined ,var) + (call (top setindex!) ,d ,var (quote ,v))))) + names) + ,d))) ((eq? (car e) 'lambda) (let* ((lv (lam:vars e)) (env (append lv env)) @@ -2494,6 +2515,7 @@ (new-renames (append (map cons need-rename renamed) ;; map from definition name -> gensym name (map cons need-rename-def renamed-def) (map (lambda (g) (cons g `(outerref ,g))) new-iglo) + (map (lambda (g) (cons g `(outerref ,g))) glob) (filter (lambda (ren) ;; old renames list, with anything in vars removed (let ((var (car ren))) (not (or (memq var all-vars) ;; remove anything new diff --git a/test/reflection.jl b/test/reflection.jl index 76bdac6681167..f05bbb15d7843 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -813,3 +813,36 @@ f20872(::Val, ::Val) = false module M29962 end # make sure checking if a binding is deprecated does not resolve it @test !Base.isdeprecated(M29962, :sin) && !Base.isbindingresolved(M29962, :sin) + +# @locals +using Base: @locals +let + local x, y + global z + @test isempty(keys(@locals)) + x = 1 + @test @locals() == Dict{Symbol,Any}(:x=>1) + y = "" + @test @locals() == Dict{Symbol,Any}(:x=>1,:y=>"") + for i = 8:8 + @test @locals() == Dict{Symbol,Any}(:x=>1,:y=>"",:i=>8) + end + for i = 42:42 + local x + @test @locals() == Dict{Symbol,Any}(:y=>"",:i=>42) + end + @test @locals() == Dict{Symbol,Any}(:x=>1,:y=>"") + x = (y,) + @test @locals() == Dict{Symbol,Any}(:x=>("",),:y=>"") +end + +function _test_at_locals1(::Any, ::Any) + x = 1 + @test @locals() == Dict{Symbol,Any}(:x=>1) +end +_test_at_locals1(1,1) +function _test_at_locals2(a::Any, ::Any) + x = 2 + @test @locals() == Dict{Symbol,Any}(:x=>2,:a=>a) +end +_test_at_locals2(1,1)