diff --git a/Project.toml b/Project.toml index b52a49e9..4cb385a9 100644 --- a/Project.toml +++ b/Project.toml @@ -19,12 +19,14 @@ IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" Requires = "ae029012-a4dd-5104-9daa-d747884805df" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" +Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [extensions] AccessorsAxisKeysExt = "AxisKeys" AccessorsIntervalSetsExt = "IntervalSets" AccessorsStaticArraysExt = "StaticArrays" AccessorsStructArraysExt = "StructArrays" +AccessorsUnitfulExt = "Unitful" [compat] AxisKeys = "0.2" diff --git a/ext/AccessorsStructArraysExt.jl b/ext/AccessorsStructArraysExt.jl index 95a3bf8e..64742117 100644 --- a/ext/AccessorsStructArraysExt.jl +++ b/ext/AccessorsStructArraysExt.jl @@ -9,6 +9,8 @@ Accessors.set(x::StructArray, o::PropertyLens, v) = set(x, o ∘ StructArrays.co Accessors.insert(x::StructArray{<:Union{Tuple, NamedTuple}}, o::PropertyLens, v) = insert(x, o ∘ StructArrays.components, v) Accessors.delete(x::StructArray{<:Union{Tuple, NamedTuple}}, o::PropertyLens) = delete(x, o ∘ StructArrays.components) +Accessors.set(x::StructArray{<:Union{Tuple, NamedTuple}}, ::typeof(propertynames), names) = set(x, propertynames ∘ StructArrays.components, names) + # (named)tuple eltypes: only component arrays themselves are needed in the constructor # can change component number/names Accessors.set(x::StructArray{<:Union{Tuple, NamedTuple}}, ::typeof(StructArrays.components), v) = StructArray(v) diff --git a/ext/AccessorsUnitfulExt.jl b/ext/AccessorsUnitfulExt.jl new file mode 100644 index 00000000..705032bf --- /dev/null +++ b/ext/AccessorsUnitfulExt.jl @@ -0,0 +1,10 @@ +module AccessorsUnitfulExt + +import Accessors: set +using Unitful + +# ustrip(unit, _) works automatically because Unitful defines inverse() for it +# inverse(ustrip) is impossible, so special set() handling is required +set(obj, ::typeof(ustrip), val) = val * unit(obj) + +end diff --git a/src/functionlenses.jl b/src/functionlenses.jl index ed5e3071..301e28ce 100644 --- a/src/functionlenses.jl +++ b/src/functionlenses.jl @@ -49,6 +49,18 @@ set(obj, ::typeof(Base.splat(=>)), val::Pair) = @set Tuple(obj) = Tuple(val) set(obj, ::typeof(getproperties), val::NamedTuple) = setproperties(obj, val) +set(x::Union{Tuple,NamedTuple}, ::typeof(propertynames), names) = set(x, propertynames, Tuple(names)) +function set(x::Union{Tuple,NamedTuple}, ::typeof(propertynames), names::Tuple) + length(names) == length(x) || throw(ArgumentError("Got $(length(names)) for $(length(x)) properties")) + if eltype(names) === Symbol + NamedTuple{names}(Tuple(x)) + elseif eltype(names) <: Integer && names == ntuple(identity, length(names)) + Tuple(x) + else + throw(ArgumentError("invalid property names: $names")) + end +end + ################################################################################ ##### eltype ################################################################################ diff --git a/test/test_extensions.jl b/test/test_extensions.jl index fca7166e..497e6498 100644 --- a/test/test_extensions.jl +++ b/test/test_extensions.jl @@ -6,6 +6,7 @@ using AxisKeys using IntervalSets using StaticArrays, StaticNumbers using StructArrays +using Unitful # most of the tests run on Julia 1.9 and later because of package extensions support @@ -165,6 +166,16 @@ VERSION >= v"1.9-" && @testset "StructArrays" begin s = StructArray([S(1, 2), S(3, 4)]) @test @inferred(set(s, PropertyLens(:a), 10:11))::StructArray == StructArray([S(10, 2), S(11, 4)]) @test @inferred(set(s, PropertyLens(:a), [:a, :b]))::StructArray == StructArray([S(:a, 2), S(:b, 4)]) + + @test_throws "need to overload" set(s, propertynames, (:x, :y)) + s = StructArray(x=[1, 2], y=[:a, :b]) + test_getset_laws(propertynames, s, (:u, :v), (1, 2)) + test_getset_laws(propertynames, s, (1, 2), (:u, :v)) +end + +VERSION ≥ v"1.9-" && @testset "Unitful" begin + test_getset_laws(ustrip, 1u"m", 2., 3) + test_getset_laws(ustrip, 1u"m/mm", 2., 3) end end diff --git a/test/test_functionlenses.jl b/test/test_functionlenses.jl index 34f01d31..281405ce 100644 --- a/test/test_functionlenses.jl +++ b/test/test_functionlenses.jl @@ -109,6 +109,20 @@ end test_getset_laws(NamedTuple{(:x, :y)}, CartesianIndex(1, 2), (y=3, x=4), (x=5, y=6); cmp=cmp) test_getset_laws(Accessors.getproperties, 1+2im, (im=4., re=3.), (re=5, im=6); cmp=cmp) + + @testset for obj in [(10, 20.), (x=10, y=20.)] + @test (@set propertynames(obj) = 1:2) === (10, 20.) + @test (@set propertynames(obj) = (:a, :b)) === (a=10, b=20.) + @test_throws Exception (@set propertynames(obj) = 1:3) + @test_throws Exception (@set propertynames(obj) = (2, 3)) + @test_throws Exception (@set propertynames(obj) = (:a, :b, :c)) + @test_throws Exception (@set propertynames(obj) = (1, :b)) + @test_throws Exception (@set propertynames(obj) = ("a", "b")) + end + test_getset_laws(propertynames, (10, 20.), (:a, :b), (1, 2); cmp=(===)) + test_getset_laws(propertynames, (10, 20.), (1, 2), (:a, :b); cmp=(===)) + test_getset_laws(propertynames, (a=10, b=20.), (:x, :y), (1, 2); cmp=(===)) + test_getset_laws(propertynames, (a=10, b=20.), (1, 2), (:x, :y); cmp=(===)) end @testset "eltype on Number" begin