From 0a820c80b50ae5aefb65f0eaab62fb8395890e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20K=2E=20Papp?= Date: Fri, 18 Nov 2022 14:05:25 +0100 Subject: [PATCH 1/3] Add support for renamed local variables. Fixes #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 --- README.md | 14 ++++++++++++++ src/UnPack.jl | 28 +++++++++++++++++++++------- test/runtests.jl | 48 ++++++++++++++++++++++++++++++++++-------------- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index d959b38..bb4e9a8 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/UnPack.jl b/src/UnPack.jl index 40f1961..4768c1e 100644 --- a/src/UnPack.jl +++ b/src/UnPack.jl @@ -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 @@ -104,7 +119,6 @@ macro unpack(args) esc(expr) end - """ ```julia_skip @pack! dict_or_typeinstance = a, b, c, ... diff --git a/test/runtests.jl b/test/runtests.jl index 31cec87..f602931 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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 From 9d6e644062154a46b58427aba74579293e950480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20K=2E=20Papp?= Date: Fri, 18 Nov 2022 14:41:01 +0100 Subject: [PATCH 2/3] depend on julia 1.6 --- .github/workflows/ci.yml | 3 ++- Project.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29d34b3..dc1c792 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,8 @@ jobs: fail-fast: false matrix: version: - - '1.0' + - '1.6' + - '1' - 'nightly' os: - ubuntu-latest diff --git a/Project.toml b/Project.toml index 891f535..8b5bf7a 100644 --- a/Project.toml +++ b/Project.toml @@ -4,7 +4,7 @@ authors = ["Mauro Werder"] version = "1.0.2" [compat] -julia = "1" +julia = "1.6" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" From 14c5bda46a0109d6d67ad1f415d8292b05382bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20K=2E=20Papp?= Date: Fri, 18 Nov 2022 14:50:13 +0100 Subject: [PATCH 3/3] revert minimum Julia to 1, tweak tests --- .github/workflows/ci.yml | 4 ++-- Project.toml | 2 +- test/runtests.jl | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc1c792..d320005 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,8 +10,8 @@ jobs: fail-fast: false matrix: version: - - '1.6' - - '1' + - '1.0' # lowest supported version + - '1' # latest release - 'nightly' os: - ubuntu-latest diff --git a/Project.toml b/Project.toml index 8b5bf7a..891f535 100644 --- a/Project.toml +++ b/Project.toml @@ -4,7 +4,7 @@ authors = ["Mauro Werder"] version = "1.0.2" [compat] -julia = "1.6" +julia = "1" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/runtests.jl b/test/runtests.jl index f602931..119650a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -37,10 +37,12 @@ using Test 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)) + # 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