Skip to content

Commit 67bba81

Browse files
authored
add @locals macro for obtaining dictionary of local vars and values (#29733)
implements #29366
1 parent 7ba6c82 commit 67bba81

File tree

4 files changed

+93
-0
lines changed

4 files changed

+93
-0
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ New language features
55
---------------------
66

77
* An *exception stack* is maintained on each task to make exception handling more robust and enable root cause analysis using `catch_stack` ([#28878]).
8+
* The experimental macro `Base.@locals` returns a dictionary of current local variable names
9+
and values ([#29733]).
810

911
Language changes
1012
----------------

base/reflection.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,42 @@ macro isdefined(s::Symbol)
250250
return Expr(:isdefined, esc(s))
251251
end
252252

253+
"""
254+
@locals()
255+
256+
Construct a dictionary of the names (as symbols) and values of all local
257+
variables defined as of the call site.
258+
259+
# Examples
260+
```jldoctest
261+
julia> let x = 1, y = 2
262+
Base.@locals
263+
end
264+
Dict{Symbol,Any} with 2 entries:
265+
:y => 2
266+
:x => 1
267+
268+
julia> function f(x)
269+
local y
270+
show(Base.@locals); println()
271+
for i = 1:1
272+
show(Base.@locals); println()
273+
end
274+
y = 2
275+
show(Base.@locals); println()
276+
nothing
277+
end;
278+
279+
julia> f(42)
280+
Dict{Symbol,Any}(:x=>42)
281+
Dict{Symbol,Any}(:i=>1,:x=>42)
282+
Dict{Symbol,Any}(:y=>2,:x=>42)
283+
```
284+
"""
285+
macro locals()
286+
return Expr(:locals)
287+
end
288+
253289
"""
254290
objectid(x)
255291

src/julia-syntax.scm

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2442,6 +2442,27 @@
24422442
(if (not (memq (cadr e) env))
24432443
(error "no outer local variable declaration exists for \"for outer\""))
24442444
'(null))
2445+
((eq? (car e) 'locals)
2446+
(let* ((names (filter (lambda (v)
2447+
(and (not (gensym? v))
2448+
(not (length= (string-split (string v) "#") 2))
2449+
(let ((r (assq v renames)))
2450+
(or (atom? r)
2451+
(let ((mapping (cdr r)))
2452+
(not (and (pair? mapping)
2453+
(eq? (car mapping) 'outerref))))))))
2454+
env))
2455+
(names (delete-duplicates
2456+
(filter (lambda (v) (not (eq? v '||)))
2457+
(map unmangled-name names))))
2458+
(d (make-ssavalue)))
2459+
`(block (= ,d (call (call (core apply_type) (top Dict) (core Symbol) (core Any))))
2460+
,@(map (lambda (v)
2461+
(let ((var (resolve-scopes- v env implicitglobals lam renames newlam sp)))
2462+
`(if (isdefined ,var)
2463+
(call (top setindex!) ,d ,var (quote ,v)))))
2464+
names)
2465+
,d)))
24452466
((eq? (car e) 'lambda)
24462467
(let* ((lv (lam:vars e))
24472468
(env (append lv env))
@@ -2494,6 +2515,7 @@
24942515
(new-renames (append (map cons need-rename renamed) ;; map from definition name -> gensym name
24952516
(map cons need-rename-def renamed-def)
24962517
(map (lambda (g) (cons g `(outerref ,g))) new-iglo)
2518+
(map (lambda (g) (cons g `(outerref ,g))) glob)
24972519
(filter (lambda (ren) ;; old renames list, with anything in vars removed
24982520
(let ((var (car ren)))
24992521
(not (or (memq var all-vars) ;; remove anything new

test/reflection.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,3 +813,36 @@ f20872(::Val, ::Val) = false
813813
module M29962 end
814814
# make sure checking if a binding is deprecated does not resolve it
815815
@test !Base.isdeprecated(M29962, :sin) && !Base.isbindingresolved(M29962, :sin)
816+
817+
# @locals
818+
using Base: @locals
819+
let
820+
local x, y
821+
global z
822+
@test isempty(keys(@locals))
823+
x = 1
824+
@test @locals() == Dict{Symbol,Any}(:x=>1)
825+
y = ""
826+
@test @locals() == Dict{Symbol,Any}(:x=>1,:y=>"")
827+
for i = 8:8
828+
@test @locals() == Dict{Symbol,Any}(:x=>1,:y=>"",:i=>8)
829+
end
830+
for i = 42:42
831+
local x
832+
@test @locals() == Dict{Symbol,Any}(:y=>"",:i=>42)
833+
end
834+
@test @locals() == Dict{Symbol,Any}(:x=>1,:y=>"")
835+
x = (y,)
836+
@test @locals() == Dict{Symbol,Any}(:x=>("",),:y=>"")
837+
end
838+
839+
function _test_at_locals1(::Any, ::Any)
840+
x = 1
841+
@test @locals() == Dict{Symbol,Any}(:x=>1)
842+
end
843+
_test_at_locals1(1,1)
844+
function _test_at_locals2(a::Any, ::Any)
845+
x = 2
846+
@test @locals() == Dict{Symbol,Any}(:x=>2,:a=>a)
847+
end
848+
_test_at_locals2(1,1)

0 commit comments

Comments
 (0)