6969 @test setproperties ((a= 1 ,), ()) === (a= 1 ,)
7070 @test setproperties ((a= 1 ,), NamedTuple ()) === (a= 1 ,)
7171 @test setproperties (AB (1 ,2 ), ()) === AB (1 ,2 )
72+ @test_throws ArgumentError setproperties (AB (1 ,2 ), (10 ,))
7273 @test setproperties (AB (1 ,2 ), NamedTuple ()) === AB (1 ,2 )
7374
7475 @test setproperties (AB (1 ,2 ), (a= 2 , b= 3 )) === AB (2 ,3 )
262263 end
263264end
264265
265- function funny_numbers (n):: Tuple
266+ # example of a struct with different fields and properties
267+ struct FieldProps{NT <: NamedTuple{(:a, :b)} }
268+ components:: NT
269+ end
270+
271+ Base. propertynames (obj:: FieldProps ) = (:a , :b )
272+ Base. getproperty (obj:: FieldProps , name:: Symbol ) = getproperty (getfield (obj, :components ), name)
273+ ConstructionBase. constructorof (:: Type{<:FieldProps} ) = (a, b) -> FieldProps ((a= a, b= b))
274+
275+ @testset " use properties, not fields" begin
276+ x = FieldProps ((a= 1 , b= :b ))
277+ if VERSION >= v " 1.7"
278+ @test getproperties (x) == (a= 1 , b= :b )
279+ @test setproperties (x, a= " aaa" ) == FieldProps ((a= " aaa" , b= :b ))
280+ VERSION >= v " 1.8-dev" ?
281+ (@test_throws " Failed to assign properties (:c,) to object with properties (:a, :b)" setproperties (x, c= 0 )) :
282+ (@test_throws ArgumentError setproperties (x, c= 0 ))
283+ else
284+ @test_throws ErrorException getproperties (x)
285+ @test_throws ErrorException setproperties (x, a= " aaa" )
286+ @test_throws ErrorException setproperties (x, c= 0 )
287+ end
288+ end
289+
290+ function funny_numbers (:: Type{Tuple} , n):: Tuple
266291 types = [
267292 Int128, Int16, Int32, Int64, Int8,
268293 UInt128, UInt16, UInt32, UInt64, UInt8,
@@ -272,47 +297,129 @@ function funny_numbers(n)::Tuple
272297end
273298
274299function funny_numbers (:: Type{NamedTuple} , n):: NamedTuple
275- t = funny_numbers (n)
300+ t = funny_numbers (Tuple, n)
276301 pairs = map (1 : n) do i
277302 Symbol (" a$i " ) => t[i]
278303 end
279304 (;pairs... )
280305end
281306
282- for n in [0 ,1 ,20 ,40 ]
307+ abstract type S end
308+ Sn_from_n = Dict {Int,Type} ()
309+ for n in [0 ,1 ,10 ,20 ,40 ]
283310 Sn = Symbol (" S$n " )
284311 types = [Symbol (" A$i " ) for i in 1 : n]
285312 fields = [Symbol (" a$i " ) for i in 1 : n]
286313 typed_fields = [:($ ai:: $Ai ) for (ai,Ai) in zip (fields, types)]
287- @eval struct $ (Sn){$ (types... )}
314+ @eval struct $ (Sn){$ (types... )} <: S
288315 $ (typed_fields... )
289316 end
290- @eval funny_numbers (:: Type{$Sn} ) = ($ Sn)(funny_numbers ($ n)... )
317+ @eval Sn_from_n[$ n] = $ Sn
318+ end
319+ function funny_numbers (:: Type{S} , n):: S
320+ fields = funny_numbers (Tuple, n)
321+ Sn_from_n[n](fields... )
322+ end
323+
324+ reconstruct (obj, content) = constructorof (typeof (obj))(content... )
325+
326+ function write_output_to_ref! (f, out_ref:: Ref , arg_ref:: Ref )
327+ arg = arg_ref[]
328+ out_ref[] = f (arg)
329+ out_ref
330+ end
331+ function write_output_to_ref! (f, out_ref:: Ref , arg_ref1:: Ref , arg_ref2:: Ref )
332+ arg1 = arg_ref1[]
333+ arg2 = arg_ref2[]
334+ out_ref[] = f (arg1,arg2)
335+ out_ref
336+ end
337+ function hot_loop_allocs (f:: F , args... ) where {F}
338+ # we want to test that f(args...) does not allocate
339+ # when used in hot loops
340+ # however a naive @allocated f(args...)
341+ # will not be representative of what happens in an inner loop
342+ # Instead it will sometimes box inputs/outputs
343+ # and report too many allocations
344+ # so we use Refs to minimize inputs and outputs
345+ out_ref = Ref (f (args... ))
346+ arg_refs = map (Ref, args)
347+ write_output_to_ref! (f, out_ref, arg_refs... )
348+ out_ref = typeof (out_ref)() # erase out_ref so we can assert work was done later
349+ # Avoid splatting args... which also results in undesired allocs
350+ allocs = if length (arg_refs) == 1
351+ r1, = arg_refs
352+ @allocated write_output_to_ref! (f, out_ref, r1)
353+ elseif length (arg_refs) == 2
354+ r1,r2 = arg_refs
355+ @allocated write_output_to_ref! (f, out_ref, r1, r2)
356+ else
357+ error (" TODO too many args" )
358+ end
359+ @assert out_ref[] == f (args... )
360+ return allocs
361+ end
362+
363+ @testset " no allocs $T " for T in [Tuple, NamedTuple, S]
364+ @testset " n = $n " for n in [0 ,1 ,10 ,20 ]
365+ obj = funny_numbers (T, n)
366+ new_content = funny_numbers (Tuple, n)
367+ @test 0 == hot_loop_allocs (constructorof, typeof (obj))
368+ @test 0 == hot_loop_allocs (reconstruct, obj, new_content)
369+ @test 0 == hot_loop_allocs (getproperties, obj)
370+ @test 0 == hot_loop_allocs (getfields, obj)
371+ patch_sizes = [0 ,1 ,n÷ 3 ,n÷ 2 ,n]
372+ patch_sizes = min .(patch_sizes, n)
373+ patch_sizes = unique (patch_sizes)
374+ for k in patch_sizes
375+ patch = if T === Tuple
376+ funny_numbers (Tuple, k)
377+ else
378+ funny_numbers (NamedTuple, k)
379+ end
380+ @test 0 == hot_loop_allocs (setproperties, obj, patch)
381+ end
382+ end
291383end
292384
293385@testset " inference" begin
294386 @testset " Tuple n=$n " for n in [0 ,1 ,2 ,3 ,4 ,5 ,10 ,20 ,30 ,40 ]
295- t = funny_numbers (n)
387+ t = funny_numbers (Tuple, n)
296388 @test length (t) == n
297389 @test getproperties (t) === t
298390 @inferred getproperties (t)
391+ @test getfields (t) === t
392+ @inferred getfields (t)
393+ @inferred constructorof (typeof (t))
394+ content = funny_numbers (Tuple,n)
395+ @inferred reconstruct (t, content)
299396 for k in 0 : n
300- t2 = funny_numbers (k)
301- @inferred setproperties (t, t2)
397+ t2 = funny_numbers (Tuple,k)
302398 @test setproperties (t, t2)[1 : k] === t2
303399 @test setproperties (t, t2) isa Tuple
304400 @test length (setproperties (t, t2)) == n
305401 @test setproperties (t, t2)[k+ 1 : n] === t[k+ 1 : n]
402+ @inferred setproperties (t, t2)
306403 end
307404 end
308- @inferred getproperties (funny_numbers (100 ))
309- @inferred setproperties (funny_numbers (100 ), funny_numbers (90 ))
405+ @inferred getproperties (funny_numbers (Tuple,100 ))
406+ @inferred setproperties (funny_numbers (Tuple,100 ), funny_numbers (Tuple,90 ))
407+
310408 @testset " NamedTuple n=$n " for n in [0 ,1 ,2 ,3 ,4 ,5 ,10 ,20 ,30 ,40 ]
311409 nt = funny_numbers (NamedTuple, n)
312410 @test nt isa NamedTuple
313411 @test length (nt) == n
314412 @test getproperties (nt) === nt
315413 @inferred getproperties (nt)
414+ @test getfields (nt) === nt
415+ @inferred getfields (nt)
416+
417+ @inferred constructorof (typeof (nt))
418+ if VERSION >= v " 1.3"
419+ content = funny_numbers (NamedTuple,n)
420+ @inferred reconstruct (nt, content)
421+ end
422+ # no_allocs_test(nt, content)
316423 for k in 0 : n
317424 nt2 = funny_numbers (NamedTuple, k)
318425 @inferred setproperties (nt, nt2)
@@ -325,18 +432,27 @@ end
325432 @inferred getproperties (funny_numbers (NamedTuple, 100 ))
326433 @inferred setproperties (funny_numbers (NamedTuple, 100 ), funny_numbers (NamedTuple, 90 ))
327434
435+ @inferred setproperties (funny_numbers (S,0 ), funny_numbers (NamedTuple, 0 ))
436+ @inferred setproperties (funny_numbers (S,1 ), funny_numbers (NamedTuple, 1 ))
437+ @inferred setproperties (funny_numbers (S,20 ), funny_numbers (NamedTuple, 18 ))
438+ @inferred setproperties (funny_numbers (S,40 ), funny_numbers (NamedTuple, 38 ))
439+ @inferred constructorof (S0)
440+ @inferred constructorof (S1)
441+ @inferred constructorof (S20)
442+ @inferred constructorof (S40)
443+ if VERSION >= v " 1.3"
444+ @inferred reconstruct (funny_numbers (S,0 ) , funny_numbers (Tuple,0 ))
445+ @inferred reconstruct (funny_numbers (S,1 ) , funny_numbers (Tuple,1 ))
446+ @inferred reconstruct (funny_numbers (S,20 ), funny_numbers (Tuple,20 ))
447+ @inferred reconstruct (funny_numbers (S,40 ), funny_numbers (Tuple,40 ))
448+ end
328449
329- @inferred setproperties (funny_numbers (S0), funny_numbers (NamedTuple, 0 ))
330- @inferred setproperties (funny_numbers (S1), funny_numbers (NamedTuple, 1 ))
331- @inferred setproperties (funny_numbers (S20), funny_numbers (NamedTuple, 18 ))
332- @inferred setproperties (funny_numbers (S40), funny_numbers (NamedTuple, 38 ))
333- @inferred getproperties (funny_numbers (S0))
334- @inferred getproperties (funny_numbers (S1))
335- @inferred getproperties (funny_numbers (S20))
336- @inferred getproperties (funny_numbers (S40))
337-
338- @inferred getfields (funny_numbers (S0))
339- @inferred getfields (funny_numbers (S1))
340- @inferred getfields (funny_numbers (S20))
341- @inferred getfields (funny_numbers (S40))
450+ @inferred getfields (funny_numbers (S,0 ))
451+ @inferred getfields (funny_numbers (S,1 ))
452+ @inferred getfields (funny_numbers (S,20 ))
453+ @inferred getfields (funny_numbers (S,40 ))
454+ @inferred getproperties (funny_numbers (S,0 ))
455+ @inferred getproperties (funny_numbers (S,1 ))
456+ @inferred getproperties (funny_numbers (S,20 ))
457+ @inferred getproperties (funny_numbers (S,40 ))
342458end
0 commit comments