diff --git a/Project.toml b/Project.toml index 39391ef..96333a4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,8 +1,9 @@ name = "Setfield" uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46" -version = "0.4.1" +version = "0.5.0" [deps] +ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" [compat] diff --git a/src/experimental.jl b/src/experimental.jl index b40d205..46a792c 100644 --- a/src/experimental.jl +++ b/src/experimental.jl @@ -1,6 +1,6 @@ module Experimental using Setfield -using Setfield: constructor_of +using ConstructionBase: constructorof import Setfield: get, set export MultiPropertyLens @@ -32,7 +32,7 @@ end end Expr(:block, Expr(:meta, :inline), - Expr(:call, :(constructor_of($T)), args...) + Expr(:call, :(constructorof($T)), args...) ) end diff --git a/src/lens.jl b/src/lens.jl index 17d5968..36176c7 100644 --- a/src/lens.jl +++ b/src/lens.jl @@ -1,7 +1,10 @@ export Lens, set, get, modify export @lens export set, get, modify +using ConstructionBase export setproperties +export constructorof + import Base: get using Base: setindex, getproperty @@ -104,66 +107,6 @@ end ) end -@generated constructor_of(::Type{T}) where T = - getfield(parentmodule(T), nameof(T)) - -function assert_hasfields(T, fnames) - for fname in fnames - if !(fname in fieldnames(T)) - msg = "$T has no field $fname" - throw(ArgumentError(msg)) - end - end -end - -""" - setproperties(obj, patch) - -Return a copy of `obj` with attributes updates accoring to `patch`. - -# Examples -```jldoctest -julia> using Setfield - -julia> struct S;a;b;c; end - -julia> s = S(1,2,3) -S(1, 2, 3) - -julia> setproperties(s, (a=10,c=4)) -S(10, 2, 4) - -julia> setproperties((a=1,c=2,b=3), (a=10,c=4)) -(a = 10, c = 4, b = 3) -``` -""" -function setproperties end - -@generated function setproperties(obj, patch) - assert_hasfields(obj, fieldnames(patch)) - args = map(fieldnames(obj)) do fn - if fn in fieldnames(patch) - :(patch.$fn) - else - :(obj.$fn) - end - end - Expr(:block, - Expr(:meta, :inline), - Expr(:call,:(constructor_of($obj)), args...) - ) -end - -@generated function setproperties(obj::NamedTuple, patch) - # this function is only generated to force the following check - # at compile time - assert_hasfields(obj, fieldnames(patch)) - Expr(:block, - Expr(:meta, :inline), - :(merge(obj, patch)) - ) -end - struct ComposedLens{LO, LI} <: Lens outer::LO inner::LI @@ -324,6 +267,7 @@ FunctionLens(f) = FunctionLens{f}() get(obj, ::FunctionLens{f}) where f = f(obj) +Base.@deprecate constructor_of(T) constructorof(T) Base.@deprecate get(lens::Lens, obj) get(obj, lens) Base.@deprecate set(lens::Lens, obj, val) set(obj, lens, val) Base.@deprecate modify(f, lens::Lens, obj) modify(f, obj, lens) diff --git a/test/test_core.jl b/test/test_core.jl index 7f54dfc..44925f9 100644 --- a/test/test_core.jl +++ b/test/test_core.jl @@ -3,6 +3,7 @@ using Test using Setfield using Setfield: compose, get_update_op using Setfield.Experimental +import ConstructionBase struct T a @@ -336,7 +337,7 @@ end @inferred set(obj, l_nested, (a=(a=10.0, c="twenty"), b=:thirty)) end -@testset "type change during @set (default constructor_of)" begin +@testset "type change during @set (default constructorof)" begin obj = TT(2,3) obj2 = @set obj.b = :three @test obj2 === TT(2, :three) @@ -348,9 +349,9 @@ struct B{T, X, Y} y::Y B{T}(x::X, y::Y = 2) where {T, X, Y} = new{T, X, Y}(x, y) end -Setfield.constructor_of(::Type{<: B{T}}) where T = B{T} +ConstructionBase.constructorof(::Type{<: B{T}}) where T = B{T} -@testset "type change during @set (custom constructor_of)" begin +@testset "type change during @set (custom constructorof)" begin obj = B{1}(2,3) obj2 = @set obj.y = :three @test obj2 === B{1}(2, :three) @@ -388,23 +389,19 @@ end @test_throws ArgumentError (@set t.z = 3) end -@testset "setproperties" begin - o = T(1,2) - @test setproperties(o, (a=2, b=3)) === T(2,3) - @test setproperties(o, (a=2, b=3.0)) === T(2,3.0) - @test_throws ArgumentError setproperties(o, (a=2, c=3.0)) -end - struct CustomProperties _a _b end -function Setfield.setproperties(o::CustomProperties, patch) + +function ConstructionBase.setproperties(o::CustomProperties, patch::NamedTuple) CustomProperties(get(patch, :a, getfield(o, :_a)), get(patch, :b, getfield(o, :_b))) end +ConstructionBase.constructorof(::Type{CustomProperties}) = error() + @testset "setproperties overloading" begin o = CustomProperties("A", "B") o2 = @set o.a = :A diff --git a/test/test_quicktypes.jl b/test/test_quicktypes.jl index 099e8a9..1f491a0 100644 --- a/test/test_quicktypes.jl +++ b/test/test_quicktypes.jl @@ -6,6 +6,7 @@ import MacroTools using QuickTypes using Setfield +import ConstructionBase # this is a limitation in `MacroTools.splitarg`. If it is fixed # this test can be removed and our custom splitarg removed. @@ -128,7 +129,7 @@ end # Another way to "support" QuickTypes with type parameters is to use # QuickTypes.construct. @qstruct_fp Plane2(nwheels, weight::Number; brand=:zoomba) -Setfield.constructor_of(::Type{<: Plane2}) = +ConstructionBase.constructorof(::Type{<: Plane2}) = (args...) -> QuickTypes.construct(Plane2, args...) @testset "Plane2" begin diff --git a/test/test_settable.jl b/test/test_settable.jl index 8602db4..4735c60 100644 --- a/test/test_settable.jl +++ b/test/test_settable.jl @@ -1,13 +1,14 @@ module TestSettable using Test using Setfield +import ConstructionBase # If no constructor is defined explicitly, don't generate any # inner-consturctor; let Julia generate the default constructor; i.e., # @settable should be a no-op. @settable struct NoConstructor{A,B} b::B end -Setfield.constructor_of(::Type{T}) where {T <: NoConstructor} = T +ConstructionBase.constructorof(::Type{T}) where {T <: NoConstructor} = T @testset "NoConstructor" begin s1 = NoConstructor{:a,Int}(1) @@ -21,7 +22,7 @@ end b::B ExplicitConstructor{A,B}(b::B) where {A,B} = new{A,B}(b) end -Setfield.constructor_of(::Type{T}) where {T <: ExplicitConstructor} = T +ConstructionBase.constructorof(::Type{T}) where {T <: ExplicitConstructor} = T @testset "ExplicitConstructor" begin s1 = ExplicitConstructor{:a,Int}(1) @@ -41,7 +42,7 @@ end return new{A,B}(a, b) end end -Setfield.constructor_of(::Type{T}) where {T <: TypedConstructor} = T +ConstructionBase.constructorof(::Type{T}) where {T <: TypedConstructor} = T @testset "TypedConstructor" begin s1 = TypedConstructor{Int,Int}(1)