diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 5aefdad..3f4a788 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -39,32 +39,37 @@ struct NamedTupleConstructor{names} end end end -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 - function setproperties(obj; kw...) setproperties(obj, (;kw...)) end @generated function setproperties(obj, patch::NamedTuple) - assert_hasfields(obj, fieldnames(patch)) - args = map(fieldnames(obj)) do fn - if fn in fieldnames(patch) - :(patch.$fn) - else - :(obj.$fn) + if issubset(fieldnames(patch), fieldnames(obj)) + args = map(fieldnames(obj)) do fn + if fn in fieldnames(patch) + :(patch.$fn) + else + :(obj.$fn) + end end + return Expr(:block, + Expr(:meta, :inline), + Expr(:call,:(constructorof($obj)), args...) + ) + else + :(setproperties_unknown_field_error(obj, patch)) end - Expr(:block, - Expr(:meta, :inline), - Expr(:call,:(constructorof($obj)), args...) - ) +end + +function setproperties_unknown_field_error(obj, patch) + O = typeof(obj) + P = typeof(patch) + msg = """ + Failed to assign properties $(fieldnames(P)) to object with fields $(fieldnames(O)). + You may want to overload + ConstructionBase.setproperties(obj::$O, patch::NamedTuple) + """ + throw(ArgumentError(msg)) end diff --git a/test/runtests.jl b/test/runtests.jl index 42e3dfb..24bc7bb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -24,8 +24,18 @@ end @test setproperties(o, (a=2, b=3.0)) === AB(2,3.0) @test setproperties(o, a=2, b=3.0) === AB(2,3.0) - @test_throws ArgumentError setproperties(o, (a=2, c=3.0)) - @test_throws ArgumentError setproperties(o, a=2, c=3.0) + res = @test_throws ArgumentError setproperties(o, (a=2, this_field_does_not_exist=3.0)) + msg = sprint(showerror, res.value) + @test occursin("this_field_does_not_exist", msg) + @test occursin("overload", msg) + @test occursin("ConstructionBase.setproperties", msg) + + res = @test_throws ArgumentError setproperties(o, a=2, this_field_does_not_exist=3.0) + msg = sprint(showerror, res.value) + @test occursin("this_field_does_not_exist", msg) + @test occursin("overload", msg) + @test occursin("ConstructionBase.setproperties", msg) + @test setproperties(Empty(), NamedTuple()) === Empty() @test setproperties(Empty()) === Empty()