Skip to content

Commit dab102a

Browse files
authored
Merge pull request #51 from JuliaObjects/setproperties
use less generated functions
2 parents 6ca6be6 + 127bad6 commit dab102a

File tree

2 files changed

+132
-47
lines changed

2 files changed

+132
-47
lines changed

src/ConstructionBase.jl

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,61 @@ getproperties(o::Tuple) = o
5252
:(NamedTuple{$fnames}($fvals))
5353
end
5454

55+
################################################################################
56+
##### setproperties
57+
################################################################################
5558
function setproperties(obj; kw...)
5659
setproperties(obj, (;kw...))
5760
end
5861

59-
setproperties(obj, patch::NamedTuple) = _setproperties(obj, patch)
60-
setproperties(obj::Tuple, patch::typeof(NamedTuple())) = obj
61-
@noinline function setproperties(obj::Tuple, patch::NamedTuple)
62+
setproperties(obj , patch::Tuple ) = setproperties_object(obj , patch )
63+
setproperties(obj , patch::NamedTuple ) = setproperties_object(obj , patch )
64+
setproperties(obj::NamedTuple , patch::Tuple ) = setproperties_namedtuple(obj , patch )
65+
setproperties(obj::NamedTuple , patch::NamedTuple ) = setproperties_namedtuple(obj , patch )
66+
setproperties(obj::Tuple , patch::Tuple ) = setproperties_tuple(obj , patch )
67+
setproperties(obj::Tuple , patch::NamedTuple ) = setproperties_tuple(obj , patch )
68+
69+
setproperties_namedtuple(obj, patch::Tuple{}) = obj
70+
@noinline function setproperties_namedtuple(obj, patch::Tuple)
6271
msg = """
63-
Tuple has no named properties.
64-
obj ::Tuple = $obj
65-
patch::NamedTuple = $patch
72+
setproperties(obj::NamedTuple, patch::Tuple) only allowed for empty Tuple. Got:
73+
obj = $obj
74+
patch = $patch
6675
"""
6776
throw(ArgumentError(msg))
6877
end
78+
function setproperties_namedtuple(obj, patch)
79+
res = merge(obj, patch)
80+
validate_setproperties_result(res, obj, obj, patch)
81+
res
82+
end
83+
function validate_setproperties_result(
84+
nt_new::NamedTuple{fields}, nt_old::NamedTuple{fields}, obj, patch) where {fields}
85+
nothing
86+
end
87+
@noinline function validate_setproperties_result(nt_new, nt_old, obj, patch)
88+
O = typeof(obj)
89+
P = typeof(patch)
90+
msg = """
91+
Failed to assign properties $(fieldnames(P)) to object with fields $(fieldnames(O)).
92+
You may want to overload
93+
ConstructionBase.setproperties(obj::$O, patch::NamedTuple)
94+
ConstructionBase.getproperties(obj::$O)
95+
"""
96+
throw(ArgumentError(msg))
97+
end
98+
function setproperties_namedtuple(obj::NamedTuple{fields}, patch::NamedTuple{fields}) where {fields}
99+
patch
100+
end
69101

70-
function setproperties(obj::Tuple, patch::Tuple)
71-
setproperties_tuple(obj, patch)
102+
setproperties_tuple(obj::Tuple, patch::NamedTuple{()}) = obj
103+
@noinline function setproperties_tuple(obj::Tuple, patch::NamedTuple)
104+
msg = """
105+
setproperties(obj::Tuple, patch::NamedTuple) only allowed for empty NamedTuple. Got:
106+
obj ::Tuple = $obj
107+
patch::NamedTuple = $patch
108+
"""
109+
throw(ArgumentError(msg))
72110
end
73111
function setproperties_tuple(obj::NTuple{N,Any}, patch::NTuple{N,Any}) where {N}
74112
patch
@@ -94,34 +132,21 @@ end
94132
function after(x::Tuple, ::Val{0})
95133
x
96134
end
97-
_setproperties(obj, patch::typeof(NamedTuple())) = obj
98-
@generated function _setproperties(obj, patch::NamedTuple)
99-
if issubset(fieldnames(patch), fieldnames(obj))
100-
args = map(fieldnames(obj)) do fn
101-
if fn in fieldnames(patch)
102-
:(patch.$fn)
103-
else
104-
:(obj.$fn)
105-
end
106-
end
107-
return Expr(:block,
108-
Expr(:meta, :inline),
109-
Expr(:call,:(constructorof($obj)), args...)
110-
)
111-
else
112-
:(setproperties_unknown_field_error(obj, patch))
113-
end
114-
end
115135

116-
@noinline function setproperties_unknown_field_error(obj, patch)
117-
O = typeof(obj)
118-
P = typeof(patch)
136+
setproperties_object(obj, patch::Tuple{}) = obj
137+
@noinline function setproperties_object(obj, patch::Tuple)
119138
msg = """
120-
Failed to assign properties $(fieldnames(P)) to object with fields $(fieldnames(O)).
121-
You may want to overload
122-
ConstructionBase.setproperties(obj::$O, patch::NamedTuple)
139+
setproperties(obj, patch::Tuple) only allowed for empty Tuple. Got:
140+
obj = $obj
141+
patch = $patch
123142
"""
124-
throw(ArgumentError(msg))
143+
end
144+
setproperties_object(obj, patch::NamedTuple{()}) = obj
145+
function setproperties_object(obj, patch)
146+
nt = getproperties(obj)
147+
nt_new = merge(nt, patch)
148+
validate_setproperties_result(nt_new, nt, obj, patch)
149+
constructorof(typeof(obj))(Tuple(nt_new)...)
125150
end
126151

127152
include("nonstandard.jl")

test/runtests.jl

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,32 @@ end
3232
end
3333

3434
@testset "setproperties" begin
35-
o = AB(1,2)
36-
@test setproperties(o, (a=2, b=3)) === AB(2,3)
37-
@test setproperties(o, (a=2, b=3.0)) === AB(2,3.0)
38-
@test setproperties(o, a=2, b=3.0) === AB(2,3.0)
3935

40-
res = @test_throws ArgumentError setproperties(o, (a=2, this_field_does_not_exist=3.0))
36+
@test setproperties(NamedTuple(), NamedTuple()) === NamedTuple()
37+
@test setproperties((), NamedTuple()) === ()
38+
@test setproperties(NamedTuple(), ()) === NamedTuple()
39+
@test setproperties((), ()) === ()
40+
@test setproperties(1, ()) === 1
41+
@test setproperties(1, NamedTuple()) === 1
42+
43+
@test setproperties((1,), ()) === (1,)
44+
@test setproperties((1,), NamedTuple()) === (1,)
45+
@test setproperties((a=1,), ()) === (a=1,)
46+
@test setproperties((a=1,), NamedTuple()) === (a=1,)
47+
@test setproperties(AB(1,2), ()) === AB(1,2)
48+
@test setproperties(AB(1,2), NamedTuple()) === AB(1,2)
49+
50+
@test setproperties(AB(1,2), (a=2, b=3)) === AB(2,3)
51+
@test setproperties(AB(1,2), (a=2, b=3.0)) === AB(2,3.0)
52+
@test setproperties(AB(1,2), a=2, b=3.0) === AB(2,3.0)
53+
54+
res = @test_throws ArgumentError setproperties(AB(1,2), (a=2, this_field_does_not_exist=3.0))
4155
msg = sprint(showerror, res.value)
4256
@test occursin("this_field_does_not_exist", msg)
4357
@test occursin("overload", msg)
4458
@test occursin("ConstructionBase.setproperties", msg)
4559

46-
res = @test_throws ArgumentError setproperties(o, a=2, this_field_does_not_exist=3.0)
60+
res = @test_throws ArgumentError setproperties(AB(1,2), a=2, this_field_does_not_exist=3.0)
4761
msg = sprint(showerror, res.value)
4862
@test occursin("this_field_does_not_exist", msg)
4963
@test occursin("overload", msg)
@@ -58,12 +72,11 @@ end
5872
@test setproperties((a=1, b=2), (a=1.0,)) === (a=1.0, b=2)
5973
@test setproperties((a=1, b=2), a=1.0) === (a=1.0, b=2)
6074

61-
@inferred setproperties(o, a=2, b=3.0)
75+
@inferred setproperties(AB(1,2), a=2, b=3.0)
6276
@inferred setproperties(Empty(), NamedTuple())
6377
@inferred setproperties((a=1, b=2), a=1.0)
6478
@inferred setproperties((a=1, b=2), (a=1.0,))
6579

66-
@test setproperties((),()) === ()
6780
@test setproperties((1,), ()) === (1,)
6881
@test setproperties((1,), (10,)) === (10,)
6982
@test_throws ArgumentError setproperties((1,), (10,20)) === (10,)
@@ -75,7 +88,7 @@ end
7588
@test setproperties((1,2,3), (10.0,20,30)) === (10.0,20,30)
7689
@test_throws ArgumentError setproperties((1,2,3), (10.0,20,30,40))
7790

78-
@test_throws MethodError setproperties((a=1,b=2), (10,20))
91+
@test_throws ArgumentError setproperties((a=1,b=2), (10,20))
7992
@test_throws ArgumentError setproperties((), (10,))
8093
@test_throws ArgumentError setproperties((1,2), (a=10,b=20))
8194
end
@@ -225,29 +238,76 @@ end
225238
end
226239
end
227240

228-
229-
function funny_numbers(n)
241+
function funny_numbers(n)::Tuple
230242
types = [
231243
Int128, Int16, Int32, Int64, Int8,
232244
UInt128, UInt16, UInt32, UInt64, UInt8,
233245
Float16, Float32, Float64,
234246
]
235-
[T(true) for T in rand(types, n)]
247+
Tuple([T(true) for T in rand(types, n)])
248+
end
249+
250+
function funny_numbers(::Type{NamedTuple}, n)::NamedTuple
251+
t = funny_numbers(n)
252+
pairs = map(1:n) do i
253+
Symbol("a$i") => t[i]
254+
end
255+
(;pairs...)
256+
end
257+
258+
for n in [0,1,20,40]
259+
Sn = Symbol("S$n")
260+
types = [Symbol("A$i") for i in 1:n]
261+
fields = [Symbol("a$i") for i in 1:n]
262+
typed_fields = [:($ai::$Ai) for (ai,Ai) in zip(fields, types)]
263+
@eval struct $(Sn){$(types...)}
264+
$(typed_fields...)
265+
end
266+
@eval funny_numbers(::Type{$Sn}) = ($Sn)(funny_numbers($n)...)
236267
end
237268

238269
@testset "inference" begin
239270
@testset "Tuple n=$n" for n in [0,1,2,3,4,5,10,20,30,40]
240-
t = Tuple(funny_numbers(n))
271+
t = funny_numbers(n)
241272
@test length(t) == n
242273
@test getproperties(t) === t
243274
@inferred getproperties(t)
244275
for k in 0:n
245-
t2 = Tuple(funny_numbers(k))
276+
t2 = funny_numbers(k)
246277
@inferred setproperties(t, t2)
247278
@test setproperties(t, t2)[1:k] === t2
248279
@test setproperties(t, t2) isa Tuple
249280
@test length(setproperties(t, t2)) == n
250281
@test setproperties(t, t2)[k+1:n] === t[k+1:n]
251282
end
252283
end
284+
@inferred getproperties(funny_numbers(100))
285+
@inferred setproperties(funny_numbers(100), funny_numbers(90))
286+
@testset "NamedTuple n=$n" for n in [0,1,2,3,4,5,10,20,30,40]
287+
nt = funny_numbers(NamedTuple, n)
288+
@test nt isa NamedTuple
289+
@test length(nt) == n
290+
@test getproperties(nt) === nt
291+
@inferred getproperties(nt)
292+
for k in 0:n
293+
nt2 = funny_numbers(NamedTuple, k)
294+
@inferred setproperties(nt, nt2)
295+
@test Tuple(setproperties(nt, nt2))[1:k] === Tuple(nt2)
296+
@test setproperties(nt, nt2) isa NamedTuple
297+
@test length(setproperties(nt, nt2)) == n
298+
@test Tuple(setproperties(nt, nt2))[k+1:n] === Tuple(nt)[k+1:n]
299+
end
300+
end
301+
@inferred getproperties(funny_numbers(NamedTuple, 100))
302+
@inferred setproperties(funny_numbers(NamedTuple, 100), funny_numbers(NamedTuple, 90))
303+
304+
305+
@inferred setproperties(funny_numbers(S0), funny_numbers(NamedTuple, 0))
306+
@inferred setproperties(funny_numbers(S1), funny_numbers(NamedTuple, 1))
307+
@inferred setproperties(funny_numbers(S20), funny_numbers(NamedTuple, 18))
308+
@inferred setproperties(funny_numbers(S40), funny_numbers(NamedTuple, 38))
309+
@inferred getproperties(funny_numbers(S0))
310+
@inferred getproperties(funny_numbers(S1))
311+
@inferred getproperties(funny_numbers(S20))
312+
@inferred getproperties(funny_numbers(S40))
253313
end

0 commit comments

Comments
 (0)