Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for renamed local variables. #22

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ jobs:
fail-fast: false
matrix:
version:
- '1.0'
- '1.0' # lowest supported version
- '1' # latest release
- 'nightly'
os:
- ubuntu-latest
Expand Down
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
50 changes: 36 additions & 14 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,44 @@ 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
# NOTE: before 1.8, error in macroexpand throws LoadError
ERR = VERSION < v"1.8" ? LoadError : ErrorException
@test_throws ERR macroexpand(Main, :(@unpack a))
@test_throws ERR macroexpand(Main, :(@unpack "a fish" = 1))
@test_throws ERR macroexpand(Main, :(@unpack a => "a fish" = 1))
@test_throws ERR macroexpand(Main, :(@unpack 1 => b = 1))
end
end

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