diff --git a/Project.toml b/Project.toml index 94f946b5..7ce8cd4e 100644 --- a/Project.toml +++ b/Project.toml @@ -15,7 +15,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] Compat = "3.18" CompositionsBase = "0.1" -ConstructionBase = "0.1, 1.0" +ConstructionBase = "1.2" MacroTools = "0.4.4, 0.5" Requires = "0.5, 1.0" StaticNumbers = "0.3" diff --git a/src/optics.jl b/src/optics.jl index 69da1fb6..6e21a495 100644 --- a/src/optics.jl +++ b/src/optics.jl @@ -232,21 +232,25 @@ julia> obj = (a=1, b=2); julia> Accessors.mapproperties(x -> x+1, obj) (a = 2, b = 3) ``` + +# Implementation + +This function should not be overloaded directly. Instead both of +* `ConstructionBase.getproperties` +* `ConstructionBase.setproperties` +should be overloaded. $EXPERIMENTAL """ +function mapproperties end + +function mapproperties(f, nt::NamedTuple) + map(f,nt) +end + function mapproperties(f, obj) - # TODO move this helper elsewhere? - # TODO should we use a generated function based on fieldnames? - pnames = propertynames(obj) - if isempty(pnames) - return obj - else - ctor = constructorof(typeof(obj)) - new_props = map(pnames) do p - f(getproperty(obj, p)) - end - return ctor(new_props...) - end + nt = getproperties(obj) + patch = mapproperties(f, nt) + return setproperties(obj, patch) end """ diff --git a/test/runtests.jl b/test/runtests.jl index cc52c771..c6be072e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,7 +3,8 @@ import PerformanceTestTools import Accessors using Documenter: doctest -if VERSION >= v"1.5" # ⨟ needs to be defined +if VERSION == v"1.6" + # ⨟ needs to be defined doctest(Accessors) else @info "Skipping doctests, on old VERSION = $VERSION" diff --git a/test/test_examples.jl b/test/test_examples.jl index 5c75425c..b037b88a 100644 --- a/test/test_examples.jl +++ b/test/test_examples.jl @@ -3,6 +3,8 @@ using Test dir = joinpath("..", "examples") @testset "example $filename" for filename in readdir(dir) path = joinpath(dir, filename) - include(path) + @eval module $(Symbol("TestExample_$filename")) + include($path) + end end end#module diff --git a/test/test_optics.jl b/test/test_optics.jl index adf774ea..9d90ddc4 100644 --- a/test/test_optics.jl +++ b/test/test_optics.jl @@ -1,11 +1,48 @@ module TestOptics using Accessors +using Accessors: mapproperties using Test +import ConstructionBase + +@testset "mapproperties" begin + res = @inferred mapproperties(x->2x, (a=1, b=2)) + @test res === (a=2, b=4) + @test NamedTuple() === @inferred mapproperties(cos, NamedTuple()) + struct S{A,B} + a::A + b::B + end + res = @inferred mapproperties(x->2x, S(1, 2.0)) + @test res === S(2, 4.0) + + # overloading + struct AB + a::Int + b::Int + _checksum::UInt + AB(a,b) = new(a,b,hash(a,hash(b))) + end + + ConstructionBase.getproperties(o::AB) = (a=o.a, b=o.b) + ConstructionBase.setproperties(o::AB, patch::NamedTuple) = AB(patch.a, patch.b) + ab = AB(1,2) + ab2 = @inferred mapproperties(x -> 2x, ab) + @test ab2 === AB(2,4) +end @testset "Properties" begin pt = (x=1, y=2, z=3) @test (x=0, y=1, z=2) === @set pt |> Properties() -= 1 + @inferred modify(x->x-1, pt, Properties()) + + # custom struct + struct Point{X,Y,Z} + x::X; y::Y; z::Z + end + pt = Point(1f0, 2e0, 3) + pt2 = @inferred modify(x->2x, pt, Properties()) + @test pt2 === Point(2f0, 4e0, 6) end @testset "Elements" begin