Skip to content

Commit

Permalink
Add support for renamed local variables.
Browse files Browse the repository at this point in the history
Fixes mauro3#18.

Incidental changes:

- wrap some tests into testsets, so that we can use `@isdefined` in tests

- add more validation of input expressions, test for expressions that
  are invalid but were expanded and failed with an obscure error message
  • Loading branch information
tpapp committed Nov 18, 2022
1 parent 54d7260 commit 0a820c8
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 21 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ d = Dict{String,Any}()
d # -> Dict{String,Any}("a"=>5.0,"c"=>"Hi!")
```

Using `=>` allows unpacking to local variables that are different from a key:
```julia
struct MyContainer{T}
a::T
b::T
end

function Base.:(==)(x::MyContainer, y::MyContainer)
@unpack a, b = x
@unpack a => ay, b => by = y
a == ay && b by
end
```

## Customization of `@unpack` and `@pack!`

What happens during the (un-)packing of a particular datatype is
Expand Down
28 changes: 21 additions & 7 deletions src/UnPack.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,35 @@ Example with type:
```julia
struct A; a; b; c; end
d = A(4,7.0,"Hi")
@unpack a, c = d
a == 4 #true
c == "Hi" #true
@unpack a, c => C = d
a == 4 # true
C == "Hi" # note renaming, true
```
Note that its functionality can be extended by adding methods to the
`UnPack.unpack` function.
"""
macro unpack(args)
args.head!=:(=) && error("Expression needs to be of form `a, b = c`")
(args isa Expr && args.head == :(=)) ||
error("Expression needs to be of form `a, b => b_renamed = c`")
items, suitecase = args.args
items = isa(items, Symbol) ? [items] : items.args
items = (items isa Expr && items.head == :tuple) ? items.args : [items]
function _is_rename_expr(item)
(item isa Expr && item.head == :call) || return false
a = item.args
a[1] == :(=>) && a[2] isa Symbol && a[3] isa Symbol
end
suitecase_instance = gensym()
kd = [:( $key = $UnPack.unpack($suitecase_instance, Val{$(Expr(:quote, key))}()) ) for key in items]
kd = map(items) do item
key, var = if item isa Symbol
item, item
elseif _is_rename_expr(item)
item.args[2], item.args[3]
else
error("Unrecognized key $(item). Keys need to be of the form `key` or `key => var`.")
end
:( $var = $UnPack.unpack($suitecase_instance, Val{$(Expr(:quote, key))}()) )
end
kdblock = Expr(:block, kd...)
expr = quote
local $suitecase_instance = $suitecase # handles if suitecase is not a variable but an expression
Expand All @@ -104,7 +119,6 @@ macro unpack(args)
esc(expr)
end


"""
```julia_skip
@pack! dict_or_typeinstance = a, b, c, ...
Expand Down
48 changes: 34 additions & 14 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,42 @@ using Test
###########################
# Packing and unpacking @unpack, @pack!
##########################
# Example with dict:
d = Dict{Symbol,Any}(:a=>5.0,:b=>2,:c=>"Hi!")
@unpack a, c = d
@test a == 5.0 #true
@test c == "Hi!" #true

d = Dict("a"=>5.0,"b"=>2,"c"=>"Hi!")
@unpack a, c = d
@test a == 5.0 #true
@test c == "Hi!" #true
@testset "dict with symbols" begin
d = Dict{Symbol,Any}(:a=>5.0,:b=>2,:c=>"Hi!")
@unpack a, c = d
@test a == 5.0 #true
@test c == "Hi!" #true
end

# Example with named tuple
@eval d = (a=5.0, b=2, c="Hi!")
@unpack a, c = d
@test a == 5.0 #true
@test c == "Hi!" #true
@testset "dict with strings" begin
d = Dict("a"=>5.0,"b"=>2,"c"=>"Hi!")
@unpack a, c = d
@test a == 5.0 #true
@test c == "Hi!" #true
end

@testset "named tuple" begin
@eval d = (a=5.0, b=2, c="Hi!")
@unpack a, c = d
@test a == 5.0 #true
@test c == "Hi!" #true
end

@testset "named tuple, renaming" begin
d = (a = 1, b = 2)
@unpack a => c, b = d
@test c == 1
@test b == 2
@test !@isdefined a
end

@testset "invalid patterns" begin
@test_throws ErrorException macroexpand(Main, :(@unpack a))
@test_throws ErrorException macroexpand(Main, :(@unpack "a fish" = 1))
@test_throws ErrorException macroexpand(Main, :(@unpack a => "a fish" = 1))
@test_throws ErrorException macroexpand(Main, :(@unpack 1 => b = 1))
end
end

# having struct-defs inside a testset seems to be problematic in some julia version
Expand Down

0 comments on commit 0a820c8

Please sign in to comment.