From 7d90f503db65c778a4341781dabac50fa5826de1 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Sat, 26 Mar 2022 20:43:58 +0100 Subject: [PATCH 01/49] implement and document fieldvalues --- src/ConstructionBase.jl | 13 ++++++++++++ src/constructorof.md | 12 +++++------ src/fieldvalues.md | 45 +++++++++++++++++++++++++++++++++++++++++ src/setproperties.md | 2 +- test/runtests.jl | 17 ++++++++++++++++ 5 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 src/fieldvalues.md diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 0302fc0..7ef7bd8 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -3,11 +3,14 @@ module ConstructionBase export getproperties export setproperties export constructorof +export fieldvalues + # Use markdown files as docstring: for (name, path) in [ :ConstructionBase => joinpath(dirname(@__DIR__), "README.md"), :constructorof => joinpath(@__DIR__, "constructorof.md"), + :fieldvalues => joinpath(@__DIR__, "fieldvalues.md"), :getproperties => joinpath(@__DIR__, "getproperties.md"), :setproperties => joinpath(@__DIR__, "setproperties.md"), ] @@ -52,6 +55,16 @@ getproperties(o::Tuple) = o :(NamedTuple{$fnames}($fvals)) end +################################################################################ +#### fieldvalues +################################################################################ +fieldvalues(x::Tuple) = x +fieldvalues(x::NamedTuple) = Tuple(x) +@generated function fieldvalues(x::T) where {T} + fields = (:(getfield(x, $i)) for i in 1:fieldcount(T)) + Expr(:tuple, fields...) +end + ################################################################################ ##### setproperties ################################################################################ diff --git a/src/constructorof.md b/src/constructorof.md index 4c88f81..d955c3b 100644 --- a/src/constructorof.md +++ b/src/constructorof.md @@ -32,17 +32,17 @@ julia> constructorof(S)(1,2,4) ERROR: AssertionError: a + b == checksum ``` Instead `constructor` can be any object that satisfies the following properties: -* It must be possible to reconstruct an object from the `NamedTuple` returned by -`getproperties`: +* It must be possible to reconstruct an object from the `Tuple` returned by +[`fieldvalues`](@ref): ```julia ctor = constructorof(typeof(obj)) -@assert obj == ctor(getproperties(obj)...) -@assert typeof(obj) == typeof(ctor(getproperties(obj)...)) +@assert obj == ctor(fieldvalues(obj)...) +@assert typeof(obj) == typeof(ctor(fieldvalues(obj)...)) ``` -* The other direction should hold for as many values of `args` as possible: +* The other direction should hold for as many values of `args::Tuple` as possible: ```julia ctor = constructorof(T) -getproperties(ctor(args...)) == args +fieldvalues(ctor(args...)) == args ``` For instance given a suitable parametric type it should be possible to change the type of its fields: diff --git a/src/fieldvalues.md b/src/fieldvalues.md new file mode 100644 index 0000000..d155b7a --- /dev/null +++ b/src/fieldvalues.md @@ -0,0 +1,45 @@ + fieldvalues(obj) -> Tuple + +Return a tuple containing field values of `obj`. + +# Examples +```jldoctest +julia> using ConstructionBase + +julia> struct S + a::A + b::B + end + +julia> fieldvalues(S(1,2)) +(1,2) + +julia> fieldvalues((a=10,b=20)) +(10,20) + +julia> fieldvalues((4,5,6)) +(4,5,6) +``` + +# Specification + +Semantically `fieldvalues` boils down to `getfield` and `fieldcount`: +```julia +fieldvalues(obj) == Tuple(getfield(obj,i) for i in 1:fieldcount(obj)) +``` +The following relation to [`constructorof`](@ref) should be satisfied: +```julia +@assert obj == constructorof(obj)(fieldvalues(obj)...) +``` + +# Implementation + +The semantics of `fieldvalues` should generally not be changed. It should equivalent to +```julia +Tuple(getfield(obj,i) for i in 1:fieldcount(obj)) +``` +even if that included private fields of `obj`. +See also [`getproperties`](@ref), [`constructorof`](@ref) + + +See also [Tips section in the manual](@ref type-tips) diff --git a/src/setproperties.md b/src/setproperties.md index e40ac4d..11faa4b 100644 --- a/src/setproperties.md +++ b/src/setproperties.md @@ -1,6 +1,6 @@ setproperties(obj, patch::NamedTuple) -Return a copy of `obj` with attributes updates accoring to `patch`. +Return a copy of `obj` with attributes updates according to `patch`. # Examples ```jldoctest diff --git a/test/runtests.jl b/test/runtests.jl index b3b0198..8279cc3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -19,6 +19,18 @@ end @test constructorof(Tuple{Nothing, Missing})(1.0, 2) === (1.0, 2) end +@testset "fieldvalues" begin + @test fieldvalues(()) === () + @test fieldvalues([]) === () + @test fieldvalues(Empty()) === () + @test fieldvalues(NamedTuple()) === () + @test fieldvalues((10,20,30)) === (10,20,30) + @test fieldvalues((a=10,b=20f0,c=true)) === (10,20f0,true) + @test fieldvalues(AB(1, 10)) === (1, 10) + +end + + @testset "getproperties" begin o = AB(1, 2) @test getproperties(o) === (a=1, b=2) @@ -310,4 +322,9 @@ end @inferred getproperties(funny_numbers(S1)) @inferred getproperties(funny_numbers(S20)) @inferred getproperties(funny_numbers(S40)) + + @inferred fieldvalues(funny_numbers(S0)) + @inferred fieldvalues(funny_numbers(S1)) + @inferred fieldvalues(funny_numbers(S20)) + @inferred fieldvalues(funny_numbers(S40)) end From 22fc7c72261b45619009ecbcaa2ca4c60db1ca4b Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Sat, 26 Mar 2022 20:49:33 +0100 Subject: [PATCH 02/49] fix typos and add test --- src/fieldvalues.md | 5 +---- test/runtests.jl | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/fieldvalues.md b/src/fieldvalues.md index d155b7a..38391c5 100644 --- a/src/fieldvalues.md +++ b/src/fieldvalues.md @@ -38,8 +38,5 @@ The semantics of `fieldvalues` should generally not be changed. It should equiva ```julia Tuple(getfield(obj,i) for i in 1:fieldcount(obj)) ``` -even if that included private fields of `obj`. +even if that includes private fields of `obj`. See also [`getproperties`](@ref), [`constructorof`](@ref) - - -See also [Tips section in the manual](@ref type-tips) diff --git a/test/runtests.jl b/test/runtests.jl index 8279cc3..5280885 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -27,7 +27,8 @@ end @test fieldvalues((10,20,30)) === (10,20,30) @test fieldvalues((a=10,b=20f0,c=true)) === (10,20f0,true) @test fieldvalues(AB(1, 10)) === (1, 10) - + adder(a) = x -> x + a + @test fieldvalues(adder(1)) === (1,) end From 91ba22cff4608898cf5c3e6a0bb1c59c7b822371 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Sun, 27 Mar 2022 07:42:50 +0200 Subject: [PATCH 03/49] fix doctests --- docs/Manifest.toml | 101 +++++++++++++++++++++++++++++++++++---------- src/fieldvalues.md | 8 ++-- 2 files changed, 83 insertions(+), 26 deletions(-) diff --git a/docs/Manifest.toml b/docs/Manifest.toml index fe71747..4910790 100644 --- a/docs/Manifest.toml +++ b/docs/Manifest.toml @@ -1,27 +1,51 @@ # This file is machine-generated - editing it directly is not advised +[[ANSIColoredPrinters]] +git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" +uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9" +version = "0.0.1" + +[[Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + [[Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +[[CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" + +[[ConstructionBase]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "f74e9d5388b8620b4cee35d4c5a618dd4dc547f4" +uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" +version = "1.3.0" + [[Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" -[[Distributed]] -deps = ["Random", "Serialization", "Sockets"] -uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" - [[DocStringExtensions]] -deps = ["LibGit2", "Markdown", "Pkg", "Test"] -git-tree-sha1 = "0513f1a8991e9d83255e0140aace0d0fc4486600" +deps = ["LibGit2"] +git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.8.0" +version = "0.8.6" [[Documenter]] -deps = ["Base64", "DocStringExtensions", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] -git-tree-sha1 = "1b6ae3796f60311e39cd1770566140d2c056e87f" +deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] +git-tree-sha1 = "7d9a46421aef53cbd6b8ecc40c3dcbacbceaf40e" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "0.23.3" +version = "0.27.15" + +[[Future]] +deps = ["Random"] +uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" + +[[IOCapture]] +deps = ["Logging", "Random"] +git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a" +uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" +version = "0.2.2" [[InteractiveUtils]] deps = ["Markdown"] @@ -29,16 +53,30 @@ uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[JSON]] deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "b34d7cef7b337321e97d22242c3c2b91f476748e" +git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.0" +version = "0.21.3" [[LibGit2]] +deps = ["Base64", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +[[Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[LinearAlgebra]] +deps = ["Libdl", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + [[Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +[[MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.9" + [[Markdown]] deps = ["Base64"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" @@ -46,39 +84,54 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" [[Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" +[[NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" + +[[OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" + [[Parsers]] -deps = ["Dates", "Test"] -git-tree-sha1 = "ef0af6c8601db18c282d092ccbd2f01f3f0cd70b" +deps = ["Dates"] +git-tree-sha1 = "85b5da0fa43588c75bb1ff986493443f821c70b7" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "0.3.7" - -[[Pkg]] -deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"] -uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "2.2.3" [[Printf]] deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" [[REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets"] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[Random]] -deps = ["Serialization"] +deps = ["SHA", "Serialization"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +[[Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.0" + [[SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" [[Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +[[Setfield]] +deps = ["ConstructionBase", "Future", "MacroTools", "Requires"] +git-tree-sha1 = "38d88503f695eb0301479bc9b0d4320b378bafe5" +uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46" +version = "0.8.2" + [[Sockets]] uuid = "6462fe0b-24de-5631-8697-dd941f90decc" [[Test]] -deps = ["Distributed", "InteractiveUtils", "Logging", "Random"] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [[UUIDs]] @@ -87,3 +140,7 @@ uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [[Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[libblastrampoline_jll]] +deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" diff --git a/src/fieldvalues.md b/src/fieldvalues.md index 38391c5..61534bf 100644 --- a/src/fieldvalues.md +++ b/src/fieldvalues.md @@ -6,19 +6,19 @@ Return a tuple containing field values of `obj`. ```jldoctest julia> using ConstructionBase -julia> struct S +julia> struct S{A,B} a::A b::B end julia> fieldvalues(S(1,2)) -(1,2) +(1, 2) julia> fieldvalues((a=10,b=20)) -(10,20) +(10, 20) julia> fieldvalues((4,5,6)) -(4,5,6) +(4, 5, 6) ``` # Specification From a0082e441f839433c9412d49067de2f03a323036 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Sun, 27 Mar 2022 07:49:44 +0200 Subject: [PATCH 04/49] add fieldvalues to the manual --- docs/src/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/index.md b/docs/src/index.md index 0411784..e03c0db 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -8,6 +8,7 @@ ```@docs ConstructionBase ConstructionBase.constructorof +ConstructionBase.fieldvalues ConstructionBase.getproperties ConstructionBase.setproperties ``` From 1b12fc8a3c05cde16ba9ac0cd0338d388f196f2e Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Wed, 30 Mar 2022 11:39:01 +0200 Subject: [PATCH 05/49] rename fieldvalues -> getfields --- docs/src/index.md | 2 +- src/ConstructionBase.jl | 12 ++++++------ src/constructorof.md | 8 ++++---- src/fieldvalues.md | 16 ++++++++-------- test/runtests.jl | 26 +++++++++++++------------- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index e03c0db..5c15141 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -8,7 +8,7 @@ ```@docs ConstructionBase ConstructionBase.constructorof -ConstructionBase.fieldvalues +ConstructionBase.getfields ConstructionBase.getproperties ConstructionBase.setproperties ``` diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 7ef7bd8..bcae5a7 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -3,14 +3,14 @@ module ConstructionBase export getproperties export setproperties export constructorof -export fieldvalues +export getfields # Use markdown files as docstring: for (name, path) in [ :ConstructionBase => joinpath(dirname(@__DIR__), "README.md"), :constructorof => joinpath(@__DIR__, "constructorof.md"), - :fieldvalues => joinpath(@__DIR__, "fieldvalues.md"), + :getfields => joinpath(@__DIR__, "getfields.md"), :getproperties => joinpath(@__DIR__, "getproperties.md"), :setproperties => joinpath(@__DIR__, "setproperties.md"), ] @@ -56,11 +56,11 @@ getproperties(o::Tuple) = o end ################################################################################ -#### fieldvalues +#### getfields ################################################################################ -fieldvalues(x::Tuple) = x -fieldvalues(x::NamedTuple) = Tuple(x) -@generated function fieldvalues(x::T) where {T} +getfields(x::Tuple) = x +getfields(x::NamedTuple) = Tuple(x) +@generated function getfields(x::T) where {T} fields = (:(getfield(x, $i)) for i in 1:fieldcount(T)) Expr(:tuple, fields...) end diff --git a/src/constructorof.md b/src/constructorof.md index d955c3b..e548966 100644 --- a/src/constructorof.md +++ b/src/constructorof.md @@ -33,16 +33,16 @@ ERROR: AssertionError: a + b == checksum ``` Instead `constructor` can be any object that satisfies the following properties: * It must be possible to reconstruct an object from the `Tuple` returned by -[`fieldvalues`](@ref): +[`getfields`](@ref): ```julia ctor = constructorof(typeof(obj)) -@assert obj == ctor(fieldvalues(obj)...) -@assert typeof(obj) == typeof(ctor(fieldvalues(obj)...)) +@assert obj == ctor(getfields(obj)...) +@assert typeof(obj) == typeof(ctor(getfields(obj)...)) ``` * The other direction should hold for as many values of `args::Tuple` as possible: ```julia ctor = constructorof(T) -fieldvalues(ctor(args...)) == args +getfields(ctor(args...)) == args ``` For instance given a suitable parametric type it should be possible to change the type of its fields: diff --git a/src/fieldvalues.md b/src/fieldvalues.md index 61534bf..5823904 100644 --- a/src/fieldvalues.md +++ b/src/fieldvalues.md @@ -1,4 +1,4 @@ - fieldvalues(obj) -> Tuple + getfields(obj) -> Tuple Return a tuple containing field values of `obj`. @@ -11,30 +11,30 @@ julia> struct S{A,B} b::B end -julia> fieldvalues(S(1,2)) +julia> getfields(S(1,2)) (1, 2) -julia> fieldvalues((a=10,b=20)) +julia> getfields((a=10,b=20)) (10, 20) -julia> fieldvalues((4,5,6)) +julia> getfields((4,5,6)) (4, 5, 6) ``` # Specification -Semantically `fieldvalues` boils down to `getfield` and `fieldcount`: +Semantically `getfields` boils down to `getfield` and `fieldcount`: ```julia -fieldvalues(obj) == Tuple(getfield(obj,i) for i in 1:fieldcount(obj)) +getfields(obj) == Tuple(getfield(obj,i) for i in 1:fieldcount(obj)) ``` The following relation to [`constructorof`](@ref) should be satisfied: ```julia -@assert obj == constructorof(obj)(fieldvalues(obj)...) +@assert obj == constructorof(obj)(getfields(obj)...) ``` # Implementation -The semantics of `fieldvalues` should generally not be changed. It should equivalent to +The semantics of `getfields` should generally not be changed. It should equivalent to ```julia Tuple(getfield(obj,i) for i in 1:fieldcount(obj)) ``` diff --git a/test/runtests.jl b/test/runtests.jl index 5280885..2704fe7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -19,16 +19,16 @@ end @test constructorof(Tuple{Nothing, Missing})(1.0, 2) === (1.0, 2) end -@testset "fieldvalues" begin - @test fieldvalues(()) === () - @test fieldvalues([]) === () - @test fieldvalues(Empty()) === () - @test fieldvalues(NamedTuple()) === () - @test fieldvalues((10,20,30)) === (10,20,30) - @test fieldvalues((a=10,b=20f0,c=true)) === (10,20f0,true) - @test fieldvalues(AB(1, 10)) === (1, 10) +@testset "getfields" begin + @test getfields(()) === () + @test getfields([]) === () + @test getfields(Empty()) === () + @test getfields(NamedTuple()) === () + @test getfields((10,20,30)) === (10,20,30) + @test getfields((a=10,b=20f0,c=true)) === (10,20f0,true) + @test getfields(AB(1, 10)) === (1, 10) adder(a) = x -> x + a - @test fieldvalues(adder(1)) === (1,) + @test getfields(adder(1)) === (1,) end @@ -324,8 +324,8 @@ end @inferred getproperties(funny_numbers(S20)) @inferred getproperties(funny_numbers(S40)) - @inferred fieldvalues(funny_numbers(S0)) - @inferred fieldvalues(funny_numbers(S1)) - @inferred fieldvalues(funny_numbers(S20)) - @inferred fieldvalues(funny_numbers(S40)) + @inferred getfields(funny_numbers(S0)) + @inferred getfields(funny_numbers(S1)) + @inferred getfields(funny_numbers(S20)) + @inferred getfields(funny_numbers(S40)) end From 1c88ff8813c6cafd19fcdb706be36db24e6d5860 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Wed, 30 Mar 2022 12:00:16 +0200 Subject: [PATCH 06/49] fix --- src/{fieldvalues.md => getfields.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{fieldvalues.md => getfields.md} (100%) diff --git a/src/fieldvalues.md b/src/getfields.md similarity index 100% rename from src/fieldvalues.md rename to src/getfields.md From 4e13aebc2afea483b5403303ae280b940d5cc4d6 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Wed, 30 Mar 2022 12:26:36 +0200 Subject: [PATCH 07/49] fix docs/Manifest.toml --- docs/Manifest.toml | 73 ++++++++++++++++++++++++---------------------- docs/Project.toml | 1 + 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/docs/Manifest.toml b/docs/Manifest.toml index 4910790..ae8bbc4 100644 --- a/docs/Manifest.toml +++ b/docs/Manifest.toml @@ -1,146 +1,149 @@ # This file is machine-generated - editing it directly is not advised -[[ANSIColoredPrinters]] +julia_version = "1.7.0" +manifest_format = "2.0" + +[[deps.ANSIColoredPrinters]] git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9" version = "0.0.1" -[[Artifacts]] +[[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" -[[Base64]] +[[deps.Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" -[[CompilerSupportLibraries_jll]] +[[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -[[ConstructionBase]] +[[deps.ConstructionBase]] deps = ["LinearAlgebra"] -git-tree-sha1 = "f74e9d5388b8620b4cee35d4c5a618dd4dc547f4" +path = ".." uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" version = "1.3.0" -[[Dates]] +[[deps.Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" -[[DocStringExtensions]] +[[deps.DocStringExtensions]] deps = ["LibGit2"] git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" version = "0.8.6" -[[Documenter]] +[[deps.Documenter]] deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] git-tree-sha1 = "7d9a46421aef53cbd6b8ecc40c3dcbacbceaf40e" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" version = "0.27.15" -[[Future]] +[[deps.Future]] deps = ["Random"] uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" -[[IOCapture]] +[[deps.IOCapture]] deps = ["Logging", "Random"] git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a" uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" version = "0.2.2" -[[InteractiveUtils]] +[[deps.InteractiveUtils]] deps = ["Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" -[[JSON]] +[[deps.JSON]] deps = ["Dates", "Mmap", "Parsers", "Unicode"] git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" version = "0.21.3" -[[LibGit2]] +[[deps.LibGit2]] deps = ["Base64", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" -[[Libdl]] +[[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" -[[LinearAlgebra]] +[[deps.LinearAlgebra]] deps = ["Libdl", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -[[Logging]] +[[deps.Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" -[[MacroTools]] +[[deps.MacroTools]] deps = ["Markdown", "Random"] git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf" uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" version = "0.5.9" -[[Markdown]] +[[deps.Markdown]] deps = ["Base64"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" -[[Mmap]] +[[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" -[[NetworkOptions]] +[[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" -[[OpenBLAS_jll]] +[[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -[[Parsers]] +[[deps.Parsers]] deps = ["Dates"] git-tree-sha1 = "85b5da0fa43588c75bb1ff986493443f821c70b7" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" version = "2.2.3" -[[Printf]] +[[deps.Printf]] deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" -[[REPL]] +[[deps.REPL]] deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" -[[Random]] +[[deps.Random]] deps = ["SHA", "Serialization"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -[[Requires]] +[[deps.Requires]] deps = ["UUIDs"] git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" uuid = "ae029012-a4dd-5104-9daa-d747884805df" version = "1.3.0" -[[SHA]] +[[deps.SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" -[[Serialization]] +[[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" -[[Setfield]] +[[deps.Setfield]] deps = ["ConstructionBase", "Future", "MacroTools", "Requires"] git-tree-sha1 = "38d88503f695eb0301479bc9b0d4320b378bafe5" uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46" version = "0.8.2" -[[Sockets]] +[[deps.Sockets]] uuid = "6462fe0b-24de-5631-8697-dd941f90decc" -[[Test]] +[[deps.Test]] deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -[[UUIDs]] +[[deps.UUIDs]] deps = ["Random", "SHA"] uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" -[[Unicode]] +[[deps.Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" -[[libblastrampoline_jll]] +[[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" diff --git a/docs/Project.toml b/docs/Project.toml index 13764a2..33447c3 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,3 +1,4 @@ [deps] +ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" From 191b1d34c14b72b479da48059b46a01bc8aa553c Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Wed, 30 Mar 2022 18:35:21 +0200 Subject: [PATCH 08/49] special case getfields for Base types with potential undef fields --- src/getfields.md | 4 ---- src/nonstandard.jl | 1 + test/runtests.jl | 30 +++++++++++++++--------------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/getfields.md b/src/getfields.md index 5823904..22ac70f 100644 --- a/src/getfields.md +++ b/src/getfields.md @@ -27,10 +27,6 @@ Semantically `getfields` boils down to `getfield` and `fieldcount`: ```julia getfields(obj) == Tuple(getfield(obj,i) for i in 1:fieldcount(obj)) ``` -The following relation to [`constructorof`](@ref) should be satisfied: -```julia -@assert obj == constructorof(obj)(getfields(obj)...) -``` # Implementation diff --git a/src/nonstandard.jl b/src/nonstandard.jl index 8ca9104..1da660d 100644 --- a/src/nonstandard.jl +++ b/src/nonstandard.jl @@ -35,6 +35,7 @@ end function tridiagonal_constructor(dl::V, d::V, du::V, du2::V) where {V<:AbstractVector{T}} where T Tridiagonal{T,V}(dl, d, du, du2) end +getfields(o::Tridiagonal) = Tuple(getproperties(o)) # `du2` may be undefined, so we need a custom `getproperties` that checks `isdefined` function getproperties(o::Tridiagonal) diff --git a/test/runtests.jl b/test/runtests.jl index 2704fe7..4d535cc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -133,29 +133,29 @@ end @testset "SubArray" begin subarray = view(A1, 1:2, 3:4) - @test constructorof(typeof(subarray))(getproperties(subarray)...) === subarray + @test constructorof(typeof(subarray))(getfields(subarray)...) === subarray @test all(constructorof(typeof(subarray))(A2, (Base.OneTo(2), 3:4), 0, 0) .== Float32[1 1; 1 1]) - @inferred constructorof(typeof(subarray))(getproperties(subarray)...) + @inferred constructorof(typeof(subarray))(getfields(subarray)...) @inferred constructorof(typeof(subarray))(A2, (Base.OneTo(2), 3:4), 0, 0) end @testset "ReinterpretArray" begin ra1 = reinterpret(Float16, A1) @test constructorof(typeof(ra1))(A1) === ra1 - @test constructorof(typeof(ra1))(getproperties(ra1)...) === ra1 + @test constructorof(typeof(ra1))(getfields(ra1)...) === ra1 ra2 = constructorof(typeof(ra1))(A2) @test size(ra2) == (10, 6) @test eltype(ra2) == Float16 - @inferred constructorof(typeof(ra1))(getproperties(ra1)...) + @inferred constructorof(typeof(ra1))(getfields(ra1)...) @inferred constructorof(typeof(ra1))(A2) end @testset "PermutedDimsArray" begin pda1 = PermutedDimsArray(A1, (2, 1)) @test constructorof(typeof(pda1))(A1) === pda1 - @test constructorof(typeof(pda1))(getproperties(pda1)...) === pda1 + @test constructorof(typeof(pda1))(getfields(pda1)...) === pda1 @test eltype(constructorof(typeof(pda1))(A2)) == Float32 - @inferred constructorof(typeof(pda1))(getproperties(pda1)...) + @inferred constructorof(typeof(pda1))(getfields(pda1)...) @inferred constructorof(typeof(pda1))(A2) end @@ -166,24 +166,24 @@ end tda = Tridiagonal(dl, d, du) @test isdefined(tda, :du2) == false @test constructorof(typeof(tda))(dl, d, du) === tda - @test constructorof(typeof(tda))(getproperties(tda)...) === tda + @test constructorof(typeof(tda))(getfields(tda)...) === tda # lu factorization defines du2 tda_lu = lu!(tda).factors @test isdefined(tda_lu, :du2) == true - @test constructorof(typeof(tda_lu))(getproperties(tda_lu)...) === tda_lu - @test constructorof(typeof(tda_lu))(getproperties(tda)...) !== tda_lu - @test constructorof(typeof(tda_lu))(getproperties(tda)...) === tda - @inferred constructorof(typeof(tda))(getproperties(tda)...) - @inferred constructorof(typeof(tda))(getproperties(tda_lu)...) + @test constructorof(typeof(tda_lu))(getfields(tda_lu)...) === tda_lu + @test constructorof(typeof(tda_lu))(getfields(tda)...) !== tda_lu + @test constructorof(typeof(tda_lu))(getfields(tda)...) === tda + @inferred constructorof(typeof(tda))(getfields(tda)...) + @inferred constructorof(typeof(tda))(getfields(tda_lu)...) end @testset "LinRange" begin lr1 = LinRange(1, 7, 10) lr2 = LinRange(1.0f0, 7.0f0, 10) @test constructorof(typeof(lr1))(1, 7, 10, nothing) === lr1 - @test constructorof(typeof(lr1))(getproperties(lr2)...) === lr2 - @inferred constructorof(typeof(lr1))(getproperties(lr1)...) - @inferred constructorof(typeof(lr1))(getproperties(lr2)...) + @test constructorof(typeof(lr1))(getfields(lr2)...) === lr2 + @inferred constructorof(typeof(lr1))(getfields(lr1)...) + @inferred constructorof(typeof(lr1))(getfields(lr2)...) end end From c76960390361a903564084bc192083ae05136891 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 31 Mar 2022 13:30:15 +0200 Subject: [PATCH 09/49] make getfields return a NamedTuple --- src/ConstructionBase.jl | 23 +++++++++-------------- src/getfields.md | 28 ++++++++++++++++++++-------- src/nonstandard.jl | 5 ++--- test/runtests.jl | 23 +++++++++++++++++------ 4 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index bcae5a7..848ee63 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -44,27 +44,22 @@ struct NamedTupleConstructor{names} end end end -getproperties(o::NamedTuple) = o -getproperties(o::Tuple) = o -@generated function getproperties(obj) - fnames = fieldnames(obj) - fvals = map(fnames) do fname - Expr(:call, :getproperty, :obj, QuoteNode(fname)) - end - fvals = Expr(:tuple, fvals...) - :(NamedTuple{$fnames}($fvals)) -end ################################################################################ #### getfields ################################################################################ getfields(x::Tuple) = x -getfields(x::NamedTuple) = Tuple(x) -@generated function getfields(x::T) where {T} - fields = (:(getfield(x, $i)) for i in 1:fieldcount(T)) - Expr(:tuple, fields...) +getfields(x::NamedTuple) = x +@generated function getfields(obj) + fnames = fieldnames(obj) + fvals = map(fnames) do fname + Expr(:call, :getfield, :obj, QuoteNode(fname)) + end + fvals = Expr(:tuple, fvals...) + :(NamedTuple{$fnames}($fvals)) end +getproperties(o) = getfields(o) ################################################################################ ##### setproperties ################################################################################ diff --git a/src/getfields.md b/src/getfields.md index 22ac70f..9da7448 100644 --- a/src/getfields.md +++ b/src/getfields.md @@ -1,6 +1,7 @@ - getfields(obj) -> Tuple + getfields(obj) -> NamedTuple + getfields(obj::Tuple) -> Tuple -Return a tuple containing field values of `obj`. +Return a `NamedTuple` containing fields of `obj`. # Examples ```jldoctest @@ -12,10 +13,10 @@ julia> struct S{A,B} end julia> getfields(S(1,2)) -(1, 2) +(a = 1, b = 2) julia> getfields((a=10,b=20)) -(10, 20) +(a = 10, b = 20) julia> getfields((4,5,6)) (4, 5, 6) @@ -23,16 +24,27 @@ julia> getfields((4,5,6)) # Specification -Semantically `getfields` boils down to `getfield` and `fieldcount`: +Semantically `getfields` boils down to `getfield` and `fieldnames`: ```julia -getfields(obj) == Tuple(getfield(obj,i) for i in 1:fieldcount(obj)) +function getfields(obj) + pairs = (fnames => getfield(obj, fname) for fname in fieldnames(typeof(obj))) + (;pairs...) +end ``` +However the actual implementation can be more optimized. For builtin types, there can also be deviations from this semantics: +* `getfields(::Tuple)::Tuple` since `Tuples` don't have symbolic fieldnames +* There are some types in `Base` that have `undef` fields. Since accessing these results +in an error, `getfields` instead just omits these. # Implementation -The semantics of `getfields` should generally not be changed. It should equivalent to +The semantics of `getfields` should not be changed for user defined types. It should equivalent to ```julia -Tuple(getfield(obj,i) for i in 1:fieldcount(obj)) +function getfields(obj) + pairs = (fnames => getfield(obj, fname) for fname in fieldnames(typeof(obj))) + (;pairs...) +end ``` even if that includes private fields of `obj`. +If a change of semantics is desired, consider overloading [`getproperties`](@ref) instead. See also [`getproperties`](@ref), [`constructorof`](@ref) diff --git a/src/nonstandard.jl b/src/nonstandard.jl index 1da660d..640306e 100644 --- a/src/nonstandard.jl +++ b/src/nonstandard.jl @@ -35,10 +35,9 @@ end function tridiagonal_constructor(dl::V, d::V, du::V, du2::V) where {V<:AbstractVector{T}} where T Tridiagonal{T,V}(dl, d, du, du2) end -getfields(o::Tridiagonal) = Tuple(getproperties(o)) -# `du2` may be undefined, so we need a custom `getproperties` that checks `isdefined` -function getproperties(o::Tridiagonal) +# `du2` may be undefined, so we need a custom `getfields` that checks `isdefined` +function getfields(o::Tridiagonal) if isdefined(o, :du2) (dl=o.dl, d=o.d, du=o.du, du2=o.du2) else diff --git a/test/runtests.jl b/test/runtests.jl index 4d535cc..8d93b0c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -21,16 +21,27 @@ end @testset "getfields" begin @test getfields(()) === () - @test getfields([]) === () - @test getfields(Empty()) === () - @test getfields(NamedTuple()) === () + @test getfields([]) === NamedTuple() + @test getfields(Empty()) === NamedTuple() + @test getfields(NamedTuple()) === NamedTuple() @test getfields((10,20,30)) === (10,20,30) - @test getfields((a=10,b=20f0,c=true)) === (10,20f0,true) - @test getfields(AB(1, 10)) === (1, 10) + @test getfields((a=10,b=20f0,c=true)) === (a=10,b=20f0,c=true) + @test getfields(AB(1, 10)) === (a=1, b=10) adder(a) = x -> x + a - @test getfields(adder(1)) === (1,) + @test getfields(adder(1)) === (a=1,) end +struct DontTouchProperties + a + b +end +Base.propertynames(::DontTouchProperties) = error() +Base.getproperty(::DontTouchProperties, ::Symbol) = error() +ConstructionBase.getproperties(::DontTouchProperties) = error() +@testset "getfields does not depend on properties" begin + @test getfields(DontTouchProperties(1,2)) === (a=1, b=2) + @test constructorof(DontTouchProperties) === DontTouchProperties +end @testset "getproperties" begin o = AB(1, 2) From e49513707f2e5134487eaa52db1f2068034204f8 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 23 Jun 2022 11:41:50 +0200 Subject: [PATCH 10/49] add more inference tests --- test/runtests.jl | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index b3b0198..88a0357 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -267,28 +267,39 @@ for n in [0,1,20,40] end @testset "inference" begin + reconstruct(obj, content) = constructorof(typeof(obj))(content...) + @testset "Tuple n=$n" for n in [0,1,2,3,4,5,10,20,30,40] t = funny_numbers(n) @test length(t) == n @test getproperties(t) === t @inferred getproperties(t) + T = typeof(t) + @inferred constructorof(T) + content = funny_numbers(n) + @inferred reconstruct(t, content) for k in 0:n t2 = funny_numbers(k) - @inferred setproperties(t, t2) @test setproperties(t, t2)[1:k] === t2 @test setproperties(t, t2) isa Tuple @test length(setproperties(t, t2)) == n @test setproperties(t, t2)[k+1:n] === t[k+1:n] + @inferred setproperties(t, t2) end end @inferred getproperties(funny_numbers(100)) @inferred setproperties(funny_numbers(100), funny_numbers(90)) + @testset "NamedTuple n=$n" for n in [0,1,2,3,4,5,10,20,30,40] nt = funny_numbers(NamedTuple, n) @test nt isa NamedTuple @test length(nt) == n @test getproperties(nt) === nt @inferred getproperties(nt) + + @inferred constructorof(typeof(nt)) + content = funny_numbers(NamedTuple,n) + @inferred reconstruct(nt, content) for k in 0:n nt2 = funny_numbers(NamedTuple, k) @inferred setproperties(nt, nt2) @@ -306,6 +317,15 @@ end @inferred setproperties(funny_numbers(S1), funny_numbers(NamedTuple, 1)) @inferred setproperties(funny_numbers(S20), funny_numbers(NamedTuple, 18)) @inferred setproperties(funny_numbers(S40), funny_numbers(NamedTuple, 38)) + @inferred constructorof(S0) + @inferred constructorof(S1) + @inferred constructorof(S20) + @inferred constructorof(S40) + @inferred reconstruct(funny_numbers(S0), funny_numbers(0)) + @inferred reconstruct(funny_numbers(S1), funny_numbers(1)) + @inferred reconstruct(funny_numbers(S20), funny_numbers(20)) + @inferred reconstruct(funny_numbers(S40), funny_numbers(40)) + @inferred getproperties(funny_numbers(S0)) @inferred getproperties(funny_numbers(S1)) @inferred getproperties(funny_numbers(S20)) From bce3d2537a127d27add52ed039ffc8e439d40063 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 23 Jun 2022 13:14:38 +0200 Subject: [PATCH 11/49] add allocation tests --- test/runtests.jl | 118 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 91 insertions(+), 27 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 88a0357..e799c7b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -238,7 +238,7 @@ end end end -function funny_numbers(n)::Tuple +function funny_numbers(::Type{Tuple}, n)::Tuple types = [ Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8, @@ -248,38 +248,101 @@ function funny_numbers(n)::Tuple end function funny_numbers(::Type{NamedTuple}, n)::NamedTuple - t = funny_numbers(n) + t = funny_numbers(Tuple,n) pairs = map(1:n) do i Symbol("a$i") => t[i] end (;pairs...) end -for n in [0,1,20,40] +abstract type S end +Sn_from_n = Dict{Int,Type}() +for n in [0,1,10,20,40] Sn = Symbol("S$n") types = [Symbol("A$i") for i in 1:n] fields = [Symbol("a$i") for i in 1:n] typed_fields = [:($ai::$Ai) for (ai,Ai) in zip(fields, types)] - @eval struct $(Sn){$(types...)} + @eval struct $(Sn){$(types...)} <: S $(typed_fields...) end - @eval funny_numbers(::Type{$Sn}) = ($Sn)(funny_numbers($n)...) + @eval Sn_from_n[$n] = $Sn +end +function funny_numbers(::Type{S}, n)::S + fields = funny_numbers(Tuple, n) + Sn_from_n[n](fields...) end -@testset "inference" begin - reconstruct(obj, content) = constructorof(typeof(obj))(content...) +reconstruct(obj, content) = constructorof(typeof(obj))(content...) +function write_output_to_ref!(f, out_ref::Ref, arg_ref::Ref) + arg = arg_ref[] + out_ref[] = f(arg) + out_ref +end +function write_output_to_ref!(f, out_ref::Ref, arg_ref1::Ref, arg_ref2::Ref) + arg1 = arg_ref1[] + arg2 = arg_ref2[] + out_ref[] = f(arg1,arg2) + out_ref +end +function hot_loop_allocs(f::F, args...) where {F} + # we want to test that f(args...) does not allocate + # when used in hot loops + # however a naive @allocated f(args...) + # will not be representative of what happens in an inner loop + # Instead it will sometimes box inputs/outputs + # and report too many allocations + # so we use Refs to minimize inputs and outputs + out_ref = Ref(f(args...)) + arg_refs = map(Ref, args) + write_output_to_ref!(f, out_ref, arg_refs...) + out_ref = typeof(out_ref)() # erase out_ref so we can assert work was done later + # Avoid splatting args... which also results in undesired allocs + allocs = if length(arg_refs) == 1 + r1, = arg_refs + @allocated write_output_to_ref!(f, out_ref, r1) + elseif length(arg_refs) == 2 + r1,r2 = arg_refs + @allocated write_output_to_ref!(f, out_ref, r1, r2) + else + error("TODO too many args") + end + @assert out_ref[] == f(args...) + return allocs +end + +@testset "no allocs $T" for T in [Tuple, NamedTuple, S] + @testset "n = $n" for n in [0,1,10,20] + obj = funny_numbers(T, n) + new_content = funny_numbers(Tuple, n) + @test 0 == hot_loop_allocs(constructorof, typeof(obj)) + @test 0 == hot_loop_allocs(reconstruct, obj, new_content) + @test 0 == hot_loop_allocs(getproperties, obj) + patch_sizes = [0,1,n÷3,n÷2,n] + patch_sizes = min.(patch_sizes, n) + patch_sizes = unique(patch_sizes) + for k in patch_sizes + patch = if T === Tuple + funny_numbers(Tuple, k) + else + funny_numbers(NamedTuple, k) + end + @test 0 == hot_loop_allocs(setproperties, obj, patch) + end + end +end + +@testset "inference" begin @testset "Tuple n=$n" for n in [0,1,2,3,4,5,10,20,30,40] - t = funny_numbers(n) + t = funny_numbers(Tuple,n) @test length(t) == n @test getproperties(t) === t @inferred getproperties(t) - T = typeof(t) - @inferred constructorof(T) - content = funny_numbers(n) + @inferred constructorof(typeof(t)) + content = funny_numbers(Tuple,n) @inferred reconstruct(t, content) for k in 0:n - t2 = funny_numbers(k) + t2 = funny_numbers(Tuple,k) @test setproperties(t, t2)[1:k] === t2 @test setproperties(t, t2) isa Tuple @test length(setproperties(t, t2)) == n @@ -287,8 +350,8 @@ end @inferred setproperties(t, t2) end end - @inferred getproperties(funny_numbers(100)) - @inferred setproperties(funny_numbers(100), funny_numbers(90)) + @inferred getproperties(funny_numbers(Tuple,100)) + @inferred setproperties(funny_numbers(Tuple,100), funny_numbers(Tuple,90)) @testset "NamedTuple n=$n" for n in [0,1,2,3,4,5,10,20,30,40] nt = funny_numbers(NamedTuple, n) @@ -300,6 +363,7 @@ end @inferred constructorof(typeof(nt)) content = funny_numbers(NamedTuple,n) @inferred reconstruct(nt, content) + #no_allocs_test(nt, content) for k in 0:n nt2 = funny_numbers(NamedTuple, k) @inferred setproperties(nt, nt2) @@ -313,21 +377,21 @@ end @inferred setproperties(funny_numbers(NamedTuple, 100), funny_numbers(NamedTuple, 90)) - @inferred setproperties(funny_numbers(S0), funny_numbers(NamedTuple, 0)) - @inferred setproperties(funny_numbers(S1), funny_numbers(NamedTuple, 1)) - @inferred setproperties(funny_numbers(S20), funny_numbers(NamedTuple, 18)) - @inferred setproperties(funny_numbers(S40), funny_numbers(NamedTuple, 38)) + @inferred setproperties(funny_numbers(S,0), funny_numbers(NamedTuple, 0)) + @inferred setproperties(funny_numbers(S,1), funny_numbers(NamedTuple, 1)) + @inferred setproperties(funny_numbers(S,20), funny_numbers(NamedTuple, 18)) + @inferred setproperties(funny_numbers(S,40), funny_numbers(NamedTuple, 38)) @inferred constructorof(S0) @inferred constructorof(S1) @inferred constructorof(S20) @inferred constructorof(S40) - @inferred reconstruct(funny_numbers(S0), funny_numbers(0)) - @inferred reconstruct(funny_numbers(S1), funny_numbers(1)) - @inferred reconstruct(funny_numbers(S20), funny_numbers(20)) - @inferred reconstruct(funny_numbers(S40), funny_numbers(40)) - - @inferred getproperties(funny_numbers(S0)) - @inferred getproperties(funny_numbers(S1)) - @inferred getproperties(funny_numbers(S20)) - @inferred getproperties(funny_numbers(S40)) + @inferred reconstruct(funny_numbers(S,0) , funny_numbers(Tuple,0)) + @inferred reconstruct(funny_numbers(S,1) , funny_numbers(Tuple,1)) + @inferred reconstruct(funny_numbers(S,20), funny_numbers(Tuple,20)) + @inferred reconstruct(funny_numbers(S,40), funny_numbers(Tuple,40)) + + @inferred getproperties(funny_numbers(S,0)) + @inferred getproperties(funny_numbers(S,1)) + @inferred getproperties(funny_numbers(S,20)) + @inferred getproperties(funny_numbers(S,40)) end From 78cde0e89334b252eb0fe630cba09de5ccc279e2 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 23 Jun 2022 13:19:33 +0200 Subject: [PATCH 12/49] tweak ci --- .github/workflows/CI.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 19369b6..f99801a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,12 +10,14 @@ jobs: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} runs-on: ${{ matrix.os }} strategy: - fail-fast: true + fail-fast: false matrix: version: - '1.0' - '1.3' - '1.5' + - '1.6' + - '1' - 'nightly' os: - ubuntu-latest From 8af3cd3d747a92b3a70ce69b1b0590fe43c6565a Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 23 Jun 2022 13:30:07 +0200 Subject: [PATCH 13/49] drop some inference tests below julia 1.3 --- test/runtests.jl | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index e799c7b..5bb0a41 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -361,8 +361,10 @@ end @inferred getproperties(nt) @inferred constructorof(typeof(nt)) - content = funny_numbers(NamedTuple,n) - @inferred reconstruct(nt, content) + if VERSION >= "v1.3" + content = funny_numbers(NamedTuple,n) + @inferred reconstruct(nt, content) + end #no_allocs_test(nt, content) for k in 0:n nt2 = funny_numbers(NamedTuple, k) @@ -376,7 +378,6 @@ end @inferred getproperties(funny_numbers(NamedTuple, 100)) @inferred setproperties(funny_numbers(NamedTuple, 100), funny_numbers(NamedTuple, 90)) - @inferred setproperties(funny_numbers(S,0), funny_numbers(NamedTuple, 0)) @inferred setproperties(funny_numbers(S,1), funny_numbers(NamedTuple, 1)) @inferred setproperties(funny_numbers(S,20), funny_numbers(NamedTuple, 18)) @@ -385,10 +386,12 @@ end @inferred constructorof(S1) @inferred constructorof(S20) @inferred constructorof(S40) - @inferred reconstruct(funny_numbers(S,0) , funny_numbers(Tuple,0)) - @inferred reconstruct(funny_numbers(S,1) , funny_numbers(Tuple,1)) - @inferred reconstruct(funny_numbers(S,20), funny_numbers(Tuple,20)) - @inferred reconstruct(funny_numbers(S,40), funny_numbers(Tuple,40)) + if VERSION >= v"1.3" + @inferred reconstruct(funny_numbers(S,0) , funny_numbers(Tuple,0)) + @inferred reconstruct(funny_numbers(S,1) , funny_numbers(Tuple,1)) + @inferred reconstruct(funny_numbers(S,20), funny_numbers(Tuple,20)) + @inferred reconstruct(funny_numbers(S,40), funny_numbers(Tuple,40)) + end @inferred getproperties(funny_numbers(S,0)) @inferred getproperties(funny_numbers(S,1)) From 779b210e8f947c7bd4921bf0db51f2e61feed303 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 23 Jun 2022 13:34:31 +0200 Subject: [PATCH 14/49] fix --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 5bb0a41..fc18070 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -361,7 +361,7 @@ end @inferred getproperties(nt) @inferred constructorof(typeof(nt)) - if VERSION >= "v1.3" + if VERSION >= v"1.3" content = funny_numbers(NamedTuple,n) @inferred reconstruct(nt, content) end From 98bdd4b1eaed04213e15397fabb84a83b7c7f40a Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Mon, 27 Jun 2022 17:37:19 +0300 Subject: [PATCH 15/49] add downstream CI --- .github/workflows/Downstream.yml | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/Downstream.yml diff --git a/.github/workflows/Downstream.yml b/.github/workflows/Downstream.yml new file mode 100644 index 0000000..2d9d3ab --- /dev/null +++ b/.github/workflows/Downstream.yml @@ -0,0 +1,51 @@ +name: IntegrationTest +on: + push: + pull_request: + +jobs: + test: + name: ${{ matrix.package.repo }}/${{ matrix.julia-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + julia-version: [1.6, 1, nightly] + os: [ubuntu-latest] + package: + - {repo: JuliaObjects/Accessors.jl} + - {repo: JuliaFolds/BangBang.jl} + - {repo: jw3126/Setfield.jl} + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.julia-version }} + arch: x64 + - uses: julia-actions/julia-buildpkg@latest + - name: Clone Downstream + uses: actions/checkout@v2 + with: + repository: ${{ matrix.package.repo }} + path: downstream + - name: Load this and run the downstream tests + shell: julia --color=yes --project=downstream {0} + run: | + using Pkg + try + # force it to use this PR's version of the package + Pkg.develop(PackageSpec(path=".")) # resolver may fail with main deps + Pkg.update() + Pkg.test(coverage=true) # resolver may fail with test time deps + catch err + err isa Pkg.Resolve.ResolverError || rethrow() + # If we can't resolve that means this is incompatible by SemVer and this is fine + # It means we marked this as a breaking change, so we don't need to worry about + # Mistakenly introducing a breaking change, as we have intentionally made one + @info "Not compatible with this release. No problem." exception=err + exit(0) # Exit immediately, as a success + end + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v1 + with: + file: lcov.info From 498eea84bdc67d721632d00e07688c4e98ea2b25 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Tue, 28 Jun 2022 09:43:49 +0200 Subject: [PATCH 16/49] Update .github/workflows/Downstream.yml --- .github/workflows/Downstream.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Downstream.yml b/.github/workflows/Downstream.yml index 2d9d3ab..805c696 100644 --- a/.github/workflows/Downstream.yml +++ b/.github/workflows/Downstream.yml @@ -16,6 +16,7 @@ jobs: - {repo: JuliaObjects/Accessors.jl} - {repo: JuliaFolds/BangBang.jl} - {repo: jw3126/Setfield.jl} + - {repo: rafaqz/Flatten.jl} steps: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@v1 From 16ba039f51b0c8ab143b6e2b04f95f959bb7e14f Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Wed, 22 Jun 2022 17:29:30 +0300 Subject: [PATCH 17/49] use properties, not fields Co-authored-by: Jan Weidner --- Project.toml | 2 +- src/ConstructionBase.jl | 33 +++++++++++++++++++++++++-------- test/runtests.jl | 24 ++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/Project.toml b/Project.toml index 9375eb9..65cdbad 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ConstructionBase" uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" authors = ["Takafumi Arakaki", "Rafael Schouten", "Jan Weidner"] -version = "1.3.0" +version = "1.3.1" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 0302fc0..2e67f8e 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -43,13 +43,31 @@ end getproperties(o::NamedTuple) = o getproperties(o::Tuple) = o -@generated function getproperties(obj) - fnames = fieldnames(obj) - fvals = map(fnames) do fname - Expr(:call, :getproperty, :obj, QuoteNode(fname)) +if VERSION >= v"1.7" + function getproperties(obj) + fnames = propertynames(obj) + NamedTuple{fnames}(getproperty.(Ref(obj), fnames)) + end +else + @generated function getproperties(obj) + if which(propertynames, Tuple{obj}).sig != Tuple{typeof(propertynames), Any} + # custom propertynames defined for this type + return quote + msg = """ + Different fieldnames and propertynames are only supported on Julia v1.7+. + For older julia versions, consider overloading + `ConstructionBase.getproperties(obj::$(typeof(obj))`. + See also https://github.com/JuliaObjects/ConstructionBase.jl/pull/60. + """ + error(msg) + end + end + fnames = fieldnames(obj) + fvals = map(fnames) do fname + :(obj.$fname) + end + :(NamedTuple{$fnames}(($(fvals...),))) end - fvals = Expr(:tuple, fvals...) - :(NamedTuple{$fnames}($fvals)) end ################################################################################ @@ -86,9 +104,8 @@ function validate_setproperties_result( end @noinline function validate_setproperties_result(nt_new, nt_old, obj, patch) O = typeof(obj) - P = typeof(patch) msg = """ - Failed to assign properties $(fieldnames(P)) to object with fields $(fieldnames(O)). + Failed to assign properties $(propertynames(patch)) to object with properties $(propertynames(obj)). You may want to overload ConstructionBase.setproperties(obj::$O, patch::NamedTuple) ConstructionBase.getproperties(obj::$O) diff --git a/test/runtests.jl b/test/runtests.jl index fc18070..da513d0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -238,6 +238,30 @@ end end end +# example of a struct with different fields and properties +struct FieldProps{NT <: NamedTuple{(:a, :b)}} + components::NT +end + +Base.propertynames(obj::FieldProps) = (:a, :b) +Base.getproperty(obj::FieldProps, name::Symbol) = getproperty(getfield(obj, :components), name) +ConstructionBase.constructorof(::Type{<:FieldProps}) = (a, b) -> FieldProps((a=a, b=b)) + +@testset "use properties, not fields" begin + x = FieldProps((a=1, b=:b)) + if VERSION >= v"1.7" + @test getproperties(x) == (a=1, b=:b) + @test setproperties(x, a="aaa") == FieldProps((a="aaa", b=:b)) + VERSION >= v"1.8-dev" ? + (@test_throws "Failed to assign properties (:c,) to object with properties (:a, :b)" setproperties(x, c=0)) : + (@test_throws ArgumentError setproperties(x, c=0)) + else + @test_throws ErrorException getproperties(x) + @test_throws ErrorException setproperties(x, a="aaa") + @test_throws ErrorException setproperties(x, c=0) + end +end + function funny_numbers(::Type{Tuple}, n)::Tuple types = [ Int128, Int16, Int32, Int64, Int8, From 12234d433ea78fd56cccdcc96d441468fe1e5b80 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Tue, 28 Jun 2022 11:59:31 +0200 Subject: [PATCH 18/49] less generated for NamedTupleConstructor --- src/ConstructionBase.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 2e67f8e..867d9c3 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -34,11 +34,8 @@ constructorof(::Type{<:NamedTuple{names}}) where names = struct NamedTupleConstructor{names} end -@generated function (::NamedTupleConstructor{names})(args...) where names - quote - Base.@_inline_meta - $(NamedTuple{names, Tuple{args...}})(args) - end +@inline function (::NamedTupleConstructor{names})(args...) where names + NamedTuple{names}(args) end getproperties(o::NamedTuple) = o From fa460765100a6cc376750df9eac8393fd672213a Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Tue, 28 Jun 2022 12:22:00 +0200 Subject: [PATCH 19/49] fix setproperties(obj, ::NTuple{N,Any}) --- src/ConstructionBase.jl | 1 + test/runtests.jl | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 867d9c3..15fde7d 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -154,6 +154,7 @@ setproperties_object(obj, patch::Tuple{}) = obj obj = $obj patch = $patch """ + throw(ArgumentError(msg)) end setproperties_object(obj, patch::NamedTuple{()}) = obj function setproperties_object(obj, patch) diff --git a/test/runtests.jl b/test/runtests.jl index da513d0..2bfa23f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -45,6 +45,7 @@ end @test setproperties((a=1,), ()) === (a=1,) @test setproperties((a=1,), NamedTuple()) === (a=1,) @test setproperties(AB(1,2), ()) === AB(1,2) + @test_throws ArgumentError setproperties(AB(1,2), (10,)) @test setproperties(AB(1,2), NamedTuple()) === AB(1,2) @test setproperties(AB(1,2), (a=2, b=3)) === AB(2,3) From 8cc0f98c1152ccd562a274da0ebdee305a6abe3b Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Tue, 28 Jun 2022 20:51:20 +0200 Subject: [PATCH 20/49] less @generated --- src/ConstructionBase.jl | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 75bb161..ee9d79b 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -46,13 +46,6 @@ end ################################################################################ getfields(x::Tuple) = x getfields(x::NamedTuple) = x -@generated function getfields(obj) - fnames = fieldnames(obj) - fvals = map(fnames) do fname - Expr(:call, :getfield, :obj, QuoteNode(fname)) - end - :(NamedTuple{$fnames}(($(fvals...),))) -end getproperties(o::NamedTuple) = o getproperties(o::Tuple) = o if VERSION >= v"1.7" @@ -60,7 +53,18 @@ if VERSION >= v"1.7" fnames = propertynames(obj) NamedTuple{fnames}(getproperty.(Ref(obj), fnames)) end + function getfields(obj::T) where {T} + fnames = fieldnames(T) + NamedTuple{fnames}(getfield.(Ref(obj), fnames)) + end else + @generated function getfields(obj) + fnames = fieldnames(obj) + fvals = map(fnames) do fname + Expr(:call, :getfield, :obj, QuoteNode(fname)) + end + :(NamedTuple{$fnames}(($(fvals...),))) + end function getproperties(obj) check_properties_are_fields(obj) getfields(obj) From 1b3ed8f7967571b5f72b3485189e266a2dc3618e Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Tue, 28 Jun 2022 20:57:03 +0200 Subject: [PATCH 21/49] make CI fail fast --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f99801a..7c14f88 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,7 +10,7 @@ jobs: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} runs-on: ${{ matrix.os }} strategy: - fail-fast: false + fail-fast: true matrix: version: - '1.0' From 2b1222f6581111b7dd5f104c750c4bc1b8e0932b Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 30 Jun 2022 16:06:31 +0200 Subject: [PATCH 22/49] clarify semantic vs raw in the docs --- docs/src/index.md | 15 +++++++++++++-- src/constructorof.md | 7 +++---- src/getfields.md | 23 +++++++++++++---------- src/getproperties.md | 37 ++++++++++++++++++++++--------------- src/setproperties.md | 33 +++++++++++++++++---------------- 5 files changed, 68 insertions(+), 47 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 5c15141..8eb1022 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,10 +1,21 @@ # ConstructionBase.jl -```@index -``` +[`ConstructionBase`](@ref) allows flexible construction and destructuring of objects. +There are two levels of under which this can be done: +### [The raw level](@id the-raw-level) +This is where `Base.fieldnames`, `Base.getfield`, `Base.setfield!` live. +This level is what an object is ultimately composed of including all private details. +At the raw level [`ConstructionBase`](@ref) adds [`constructorof`](@ref) and [`getfields`](@ref). +### [The semantic level](@id the-semantic-level) +This is where `Base.propertynames`, `Base.getproperty` and `Base.setproperty!` live. This level is typically the public interface of a type, it may hide private details and do magic tricks. +At the semantic level [`ConstructionBase`](@ref) adds [`setproperties`](@ref) and [`getproperties`](@ref). + ## Interface +```@index +``` + ```@docs ConstructionBase ConstructionBase.constructorof diff --git a/src/constructorof.md b/src/constructorof.md index e548966..42fef1e 100644 --- a/src/constructorof.md +++ b/src/constructorof.md @@ -32,14 +32,13 @@ julia> constructorof(S)(1,2,4) ERROR: AssertionError: a + b == checksum ``` Instead `constructor` can be any object that satisfies the following properties: -* It must be possible to reconstruct an object from the `Tuple` returned by -[`getfields`](@ref): +* It must be possible to reconstruct an object from the elements of [`getfields`](@ref): ```julia ctor = constructorof(typeof(obj)) @assert obj == ctor(getfields(obj)...) @assert typeof(obj) == typeof(ctor(getfields(obj)...)) ``` -* The other direction should hold for as many values of `args::Tuple` as possible: +* The other direction should hold for as many values of `args` as possible: ```julia ctor = constructorof(T) getfields(ctor(args...)) == args @@ -61,7 +60,7 @@ T{Float64, Int64}(1.0, 2) julia> constructorof(typeof(t))(10, 2) T{Int64, Int64}(10, 2) ``` - +`constructorof` belongs to [the raw level](@ref the-raw-level). `constructorof` is generated for all anonymous `Function`s lacking constructors, identified as having `gensym` `#` in their names. A custom struct `<: Function` with a `gensym` name may need to define `constructorof` manually. diff --git a/src/getfields.md b/src/getfields.md index 9da7448..d4f0d89 100644 --- a/src/getfields.md +++ b/src/getfields.md @@ -1,7 +1,8 @@ getfields(obj) -> NamedTuple getfields(obj::Tuple) -> Tuple -Return a `NamedTuple` containing fields of `obj`. +Return a `NamedTuple` containing the fields of `obj`. On `Tuples` `getfields` is +the identity function instead, since `Tuple` fields have no symbolic names. # Examples ```jldoctest @@ -24,25 +25,27 @@ julia> getfields((4,5,6)) # Specification +`getfields` belongs to the [the raw level](@ref the-raw-level). Semantically `getfields` boils down to `getfield` and `fieldnames`: ```julia -function getfields(obj) - pairs = (fnames => getfield(obj, fname) for fname in fieldnames(typeof(obj))) - (;pairs...) +function getfields(obj::T) where {T} + fnames = fieldnames(T) + NamedTuple{fnames}(getfield.(Ref(obj), fnames)) end ``` However the actual implementation can be more optimized. For builtin types, there can also be deviations from this semantics: * `getfields(::Tuple)::Tuple` since `Tuples` don't have symbolic fieldnames -* There are some types in `Base` that have `undef` fields. Since accessing these results -in an error, `getfields` instead just omits these. +* There are some types in `Base` that have `undef` fields. Since accessing these results in an error, `getfields` instead just omits these. # Implementation -The semantics of `getfields` should not be changed for user defined types. It should equivalent to +The semantics of `getfields` should not be changed for user defined types. It should +return the raw fields as a `NamedTuple` in the struct order. In other words it should be +equivalent to ```julia -function getfields(obj) - pairs = (fnames => getfield(obj, fname) for fname in fieldnames(typeof(obj))) - (;pairs...) +function getfields(obj::T) where {T} + fnames = fieldnames(T) + NamedTuple{fnames}(getfield.(Ref(obj), fnames)) end ``` even if that includes private fields of `obj`. diff --git a/src/getproperties.md b/src/getproperties.md index b70d009..61a6501 100644 --- a/src/getproperties.md +++ b/src/getproperties.md @@ -1,6 +1,8 @@ - getproperties(obj) + getproperties(obj)::NamedTuple + getproperties(obj::Tuple)::Tuple -Return the fields of `obj` as a `NamedTuple`. +Return the properties of `obj` as a `NamedTuple`. Since `Tuple` don't have symbolic properties, +`getproperties` is the identity function on tuples. # Examples ```jldoctest @@ -17,26 +19,31 @@ S(1, 2, 3) julia> getproperties(s) (a = 1, b = 2, c = 3) -``` - -# Implementation -`getproperties` is defined by default for all objects. However for a custom type `MyType`, -`getproperties(obj::MyType)` may be defined when objects may have undefined fields, -when it has calculated fields that should not be accessed or set manually, or -other conditions that do not meet the specification with the default implementation. +julia> getproperties((10,20)) +(10, 20) +``` ## Specification +`getproperties` belongs to [the semantic level](@ref the-semantic-level). `getproperties` guarantees a couple of invariants. When overloading it, the user is responsible for ensuring them: -1. Relation to `propertynames` and `fieldnames`: `getproperties` relates to `propertynames` and `getproperty`, not to `fieldnames` and `getfield`. - This means that any series `p₁, p₂, ..., pₙ` of `propertynames(obj)` that is not undefined should be returned by `getproperties`. -2. `getproperties` is defined in relation to `constructorof` so that: - ```julia - obj == constructorof(obj)(getproperties(obj)...) - ``` +1. `getproperties` should be consistent with `Base.propertynames`, `Base.getproperty`, `Base.setproperty!`. + Semantically it should be equivalent to: + ```julia + function getproperties(obj) + fnames = propertynames(obj) + NamedTuple{fnames}(getproperty.(Ref(obj), fnames)) + end + ``` 2. `getproperties` is defined in relation to `setproperties` so that: ```julia obj == setproperties(obj, getproperties(obj)) ``` + The only exception from this semantics is that undefined properties may be avoided + in the return value of `getproperties`. + +# Implementation + +`getproperties` is defined by default for all objects. It should be very rare that a custom type `MyType`, has to implement `getproperties(obj::MyType)`. Reasons to do so are undefined fields or performance considerations. diff --git a/src/setproperties.md b/src/setproperties.md index 11faa4b..7e44f13 100644 --- a/src/setproperties.md +++ b/src/setproperties.md @@ -1,6 +1,6 @@ setproperties(obj, patch::NamedTuple) -Return a copy of `obj` with attributes updates according to `patch`. +Return a copy of `obj` with properties updates according to `patch`. # Examples ```jldoctest @@ -44,23 +44,9 @@ julia> setproperties(o, a="A", c="cc") S("A", 2, "cc") ``` -# Implementation - -For a custom type `MyType`, a method `setproperties(obj::MyType, patch::NamedTuple)` -may be defined. - -* Prefer to overload [`constructorof`](@ref) whenever makes sense (e.g., no `getproperty` - method is defined). Default `setproperties` is defined in terms of `constructorof`. - -* If `getproperty` is customized, it may be a good idea to define `setproperties`. - -!!! warning - The signature `setproperties(obj::MyType; kw...)` should never be overloaded. - Instead `setproperties(obj::MyType, patch::NamedTuple)` should be overloaded. - ## Specification -`setproperties` guarantees a couple of invariants. When overloading it, the user is responsible for ensuring them: +`setproperties` belongs to [the semantic level](@ref the-semantic-level). If satisfies the following invariants: 1. Purity: `setproperties` is supposed to have no side effects. In particular `setproperties(obj, patch::NamedTuple)` may not mutate `obj`. 2. Relation to `propertynames` and `fieldnames`: `setproperties` relates to `propertynames` and `getproperty`, not to `fieldnames` and `getfield`. @@ -100,3 +86,18 @@ let obj′ = setproperties(obj, ($p₁=v₁, $p₂=v₂, ..., $pₙ=vₙ)), @assert obj′′.$pₙ == wₙ end ``` + +# Implementation + +For a custom type `MyType`, a method `setproperties(obj::MyType, patch::NamedTuple)` +may be defined. When doing so it is important to ensure compliance with the specification. + +* Prefer to overload [`constructorof`](@ref) whenever makes sense (e.g., no `getproperty` + method is defined). Default `setproperties` is defined in terms of `constructorof` and `getproperties`. + +* If `getproperty` is customized, it may be a good idea to define `setproperties`. + +!!! warning + The signature `setproperties(obj::MyType; kw...)` should never be overloaded. + Instead `setproperties(obj::MyType, patch::NamedTuple)` should be overloaded. + From 96807ee21a1396c8e77238ae02efca71be21f83b Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 1 Jul 2022 12:28:50 +0200 Subject: [PATCH 23/49] v1.4.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 65cdbad..a7866e5 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ConstructionBase" uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" authors = ["Takafumi Arakaki", "Rafael Schouten", "Jan Weidner"] -version = "1.3.1" +version = "1.3.2" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" From 0d1f5c14cc274f0408cc235f529a9b684dae48a1 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 1 Jul 2022 15:48:46 +0200 Subject: [PATCH 24/49] check for propertynames overload in setproperties --- Project.toml | 2 +- src/ConstructionBase.jl | 53 ++++++++++++++++++++++------------------- test/runtests.jl | 37 ++++++++++++++++++---------- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/Project.toml b/Project.toml index a7866e5..2b835d7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ConstructionBase" uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" authors = ["Takafumi Arakaki", "Rafael Schouten", "Jan Weidner"] -version = "1.3.2" +version = "1.4.0" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index ee9d79b..80a1af3 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -48,6 +48,29 @@ getfields(x::Tuple) = x getfields(x::NamedTuple) = x getproperties(o::NamedTuple) = o getproperties(o::Tuple) = o + +@generated function check_properties_are_fields(::Type{T}) where {T} + if is_propertynames_overloaded(T) + return quote + msg = """ + The function `Base.propertynames` was overloaded for type `$T`. + Please make sure the following methods are also overloaded for this type: + ```julia + ConstructionBase.setproperties + ConstructionBase.getproperties # optional in VERSION >= julia v1.7 + ``` + """ + error(msg) + end + else + :() + end +end + +function is_propertynames_overloaded(T::Type)::Bool + which(propertynames, Tuple{T}).sig !== Tuple{typeof(propertynames), Any} +end + if VERSION >= v"1.7" function getproperties(obj) fnames = propertynames(obj) @@ -69,22 +92,6 @@ else check_properties_are_fields(obj) getfields(obj) end - @generated function check_properties_are_fields(obj) - if which(propertynames, Tuple{obj}).sig != Tuple{typeof(propertynames), Any} - # custom propertynames defined for this type - return quote - msg = """ - Different fieldnames and propertynames are only supported on Julia v1.7+. - For older julia versions, consider overloading - `ConstructionBase.getproperties(obj::$(typeof(obj))`. - See also https://github.com/JuliaObjects/ConstructionBase.jl/pull/60. - """ - error(msg) - end - else - :() - end - end end ################################################################################ @@ -112,20 +119,17 @@ setproperties_namedtuple(obj, patch::Tuple{}) = obj end function setproperties_namedtuple(obj, patch) res = merge(obj, patch) - validate_setproperties_result(res, obj, obj, patch) + check_patch_properties_exist(res, obj, obj, patch) res end -function validate_setproperties_result( +function check_patch_properties_exist( nt_new::NamedTuple{fields}, nt_old::NamedTuple{fields}, obj, patch) where {fields} nothing end -@noinline function validate_setproperties_result(nt_new, nt_old, obj, patch) +@noinline function check_patch_properties_exist(nt_new, nt_old, obj, patch) O = typeof(obj) msg = """ Failed to assign properties $(propertynames(patch)) to object with properties $(propertynames(obj)). - You may want to overload - ConstructionBase.setproperties(obj::$O, patch::NamedTuple) - ConstructionBase.getproperties(obj::$O) """ throw(ArgumentError(msg)) end @@ -178,10 +182,11 @@ setproperties_object(obj, patch::Tuple{}) = obj end setproperties_object(obj, patch::NamedTuple{()}) = obj function setproperties_object(obj, patch) + check_properties_are_fields(typeof(obj)) nt = getproperties(obj) nt_new = merge(nt, patch) - validate_setproperties_result(nt_new, nt, obj, patch) - constructorof(typeof(obj))(Tuple(nt_new)...) + check_patch_properties_exist(nt_new, nt, obj, patch) + constructorof(typeof(obj))(nt_new...) end include("nonstandard.jl") diff --git a/test/runtests.jl b/test/runtests.jl index c43575b..0cfb291 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -79,14 +79,10 @@ end res = @test_throws ArgumentError setproperties(AB(1,2), (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(AB(1,2), 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(42, NamedTuple()) === 42 @test setproperties(42) === 42 @@ -268,22 +264,37 @@ struct FieldProps{NT <: NamedTuple{(:a, :b)}} components::NT end -Base.propertynames(obj::FieldProps) = (:a, :b) +Base.propertynames(::FieldProps) = (:a, :b) Base.getproperty(obj::FieldProps, name::Symbol) = getproperty(getfield(obj, :components), name) -ConstructionBase.constructorof(::Type{<:FieldProps}) = (a, b) -> FieldProps((a=a, b=b)) @testset "use properties, not fields" begin x = FieldProps((a=1, b=:b)) + @test constructorof(typeof(x)) === FieldProps + @test getfields(x) === (components=(a=1, b=:b),) + res = @test_throws ErrorException setproperties(x, c=0) + msg = sprint(showerror, res.value) + @test occursin("overload", msg) + @test occursin("setproperties", msg) + @test occursin("FieldProps", msg) + @test_throws ErrorException setproperties(x, components=(a=1,b=:b)) + msg = sprint(showerror, res.value) + @test occursin("overload", msg) + @test occursin("setproperties", msg) + @test occursin("FieldProps", msg) + @test_throws ErrorException setproperties(x, a="aaa") + msg = sprint(showerror, res.value) + @test occursin("overload", msg) + @test occursin("setproperties", msg) + @test occursin("FieldProps", msg) + # == FieldProps((a="aaa", b=:b) if VERSION >= v"1.7" @test getproperties(x) == (a=1, b=:b) - @test setproperties(x, a="aaa") == FieldProps((a="aaa", b=:b)) - VERSION >= v"1.8-dev" ? - (@test_throws "Failed to assign properties (:c,) to object with properties (:a, :b)" setproperties(x, c=0)) : - (@test_throws ArgumentError setproperties(x, c=0)) else - @test_throws ErrorException getproperties(x) - @test_throws ErrorException setproperties(x, a="aaa") - @test_throws ErrorException setproperties(x, c=0) + res = @test_throws ErrorException getproperties(x) + msg = sprint(showerror, res.value) + @test occursin("overload", msg) + @test occursin("getproperties", msg) + @test occursin("FieldProps", msg) end end From 27de07fc164d38aeabe4cf6d37d8f1d8299247d3 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 1 Jul 2022 16:33:03 +0200 Subject: [PATCH 25/49] fix --- src/ConstructionBase.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 80a1af3..54d804f 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -49,9 +49,10 @@ getfields(x::NamedTuple) = x getproperties(o::NamedTuple) = o getproperties(o::Tuple) = o -@generated function check_properties_are_fields(::Type{T}) where {T} - if is_propertynames_overloaded(T) +@generated function check_properties_are_fields(obj) + if is_propertynames_overloaded(obj) return quote + T = typeof(obj) msg = """ The function `Base.propertynames` was overloaded for type `$T`. Please make sure the following methods are also overloaded for this type: @@ -182,7 +183,7 @@ setproperties_object(obj, patch::Tuple{}) = obj end setproperties_object(obj, patch::NamedTuple{()}) = obj function setproperties_object(obj, patch) - check_properties_are_fields(typeof(obj)) + check_properties_are_fields(obj) nt = getproperties(obj) nt_new = merge(nt, patch) check_patch_properties_exist(nt_new, nt, obj, patch) From bf80a504c59787753d98b3a96dfbcfa87018a1c8 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 1 Jul 2022 16:40:04 +0200 Subject: [PATCH 26/49] probe ci --- .github/workflows/CI.yml | 2 +- src/ConstructionBase.jl | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7c14f88..f99801a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,7 +10,7 @@ jobs: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} runs-on: ${{ matrix.os }} strategy: - fail-fast: true + fail-fast: false matrix: version: - '1.0' diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 54d804f..317a668 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -64,7 +64,7 @@ getproperties(o::Tuple) = o error(msg) end else - :() + :(nothing) end end @@ -183,10 +183,10 @@ setproperties_object(obj, patch::Tuple{}) = obj end setproperties_object(obj, patch::NamedTuple{()}) = obj function setproperties_object(obj, patch) - check_properties_are_fields(obj) + check_properties_are_fields(obj)::Nothing nt = getproperties(obj) nt_new = merge(nt, patch) - check_patch_properties_exist(nt_new, nt, obj, patch) + check_patch_properties_exist(nt_new, nt, obj, patch)::Nothing constructorof(typeof(obj))(nt_new...) end From 3fe9b781f5e50687da70dbe3f9024699067aa9b5 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 1 Jul 2022 16:56:12 +0200 Subject: [PATCH 27/49] probe ci --- .github/workflows/CI.yml | 2 +- src/ConstructionBase.jl | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f99801a..7c14f88 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,7 +10,7 @@ jobs: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} runs-on: ${{ matrix.os }} strategy: - fail-fast: false + fail-fast: true matrix: version: - '1.0' diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 317a668..246ec66 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -182,13 +182,21 @@ setproperties_object(obj, patch::Tuple{}) = obj throw(ArgumentError(msg)) end setproperties_object(obj, patch::NamedTuple{()}) = obj + function setproperties_object(obj, patch) - check_properties_are_fields(obj)::Nothing + check_properties_are_fields_except_old_julia(obj)::Nothing nt = getproperties(obj) nt_new = merge(nt, patch) check_patch_properties_exist(nt_new, nt, obj, patch)::Nothing constructorof(typeof(obj))(nt_new...) end +if VERSION < v"1.3" + # on old julia versions check_properties_are_fields + # trips inference of setproperties + check_properties_are_fields_except_old_julia(_) = nothing +else + check_properties_are_fields_except_old_julia(obj) = check_properties_are_fields(obj) +end include("nonstandard.jl") include("functions.jl") From e712a316d53cb3a08d0ba47161dab56891231872 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 1 Jul 2022 17:03:16 +0200 Subject: [PATCH 28/49] probe ci --- src/ConstructionBase.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 246ec66..8a94c42 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -184,7 +184,7 @@ end setproperties_object(obj, patch::NamedTuple{()}) = obj function setproperties_object(obj, patch) - check_properties_are_fields_except_old_julia(obj)::Nothing + #check_properties_are_fields_except_old_julia(obj)::Nothing nt = getproperties(obj) nt_new = merge(nt, patch) check_patch_properties_exist(nt_new, nt, obj, patch)::Nothing From 7825e81ca124af7d3098dcb45635c878c63dad61 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 1 Jul 2022 17:15:28 +0200 Subject: [PATCH 29/49] probe ci --- src/ConstructionBase.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 8a94c42..3c483d9 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -184,11 +184,12 @@ end setproperties_object(obj, patch::NamedTuple{()}) = obj function setproperties_object(obj, patch) - #check_properties_are_fields_except_old_julia(obj)::Nothing + check_properties_are_fields_except_old_julia(obj)::Nothing nt = getproperties(obj) nt_new = merge(nt, patch) check_patch_properties_exist(nt_new, nt, obj, patch)::Nothing - constructorof(typeof(obj))(nt_new...) + args = Tuple(nt_new) # old julia inference prefers if we wrap in Tuple + constructorof(typeof(obj))(args...) end if VERSION < v"1.3" # on old julia versions check_properties_are_fields From cd06f8c1863d1f90faa1ba2770c386ca1058e061 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 1 Jul 2022 17:27:53 +0200 Subject: [PATCH 30/49] remove codecov --- .github/workflows/CI.yml | 5 ----- README.md | 1 - src/ConstructionBase.jl | 11 ++--------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7c14f88..d2be379 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -33,8 +33,3 @@ jobs: arch: ${{ matrix.arch }} - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-runtest@latest - - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 - with: - file: lcov.info - fail_ci_if_error: true diff --git a/README.md b/README.md index 3ad6f61..f624da6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaObjects.github.io/ConstructionBase.jl/stable) [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaObjects.github.io/ConstructionBase.jl/dev) [![Build Status](https://github.com/JuliaObjects/ConstructionBase.jl/workflows/CI/badge.svg)](https://github.com/JuliaObjects/ConstructionBase.jl/actions?query=workflow%3ACI) -[![Codecov](https://codecov.io/gh/JuliaObjects/ConstructionBase.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaObjects/ConstructionBase.jl) [![GitHub stars](https://img.shields.io/github/stars/JuliaObjects/ConstructionBase.jl?style=social)](https://github.com/JuliaObjects/ConstructionBase.jl) ConstructionBase is a very lightwight package, that provides primitive functions for construction of objects: diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 3c483d9..86a7be4 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -184,20 +184,13 @@ end setproperties_object(obj, patch::NamedTuple{()}) = obj function setproperties_object(obj, patch) - check_properties_are_fields_except_old_julia(obj)::Nothing + check_properties_are_fields(obj) nt = getproperties(obj) nt_new = merge(nt, patch) - check_patch_properties_exist(nt_new, nt, obj, patch)::Nothing + check_patch_properties_exist(nt_new, nt, obj, patch) args = Tuple(nt_new) # old julia inference prefers if we wrap in Tuple constructorof(typeof(obj))(args...) end -if VERSION < v"1.3" - # on old julia versions check_properties_are_fields - # trips inference of setproperties - check_properties_are_fields_except_old_julia(_) = nothing -else - check_properties_are_fields_except_old_julia(obj) = check_properties_are_fields(obj) -end include("nonstandard.jl") include("functions.jl") From 6e2deff3c4b84ae3f22753df853ff5d188b52142 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Mon, 15 Aug 2022 22:52:58 +0200 Subject: [PATCH 31/49] allow manual trigger of documenter action --- .github/workflows/Documenter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 172a79b..a9db185 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -5,6 +5,7 @@ on: tags: [v*] pull_request: branches: [master] + workflow_dispatch: jobs: Documenter: name: Documentation From 8eecb3334189586e1234e951b62cb73d26e503da Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Mon, 22 Aug 2022 10:12:07 +0200 Subject: [PATCH 32/49] fix worldage issue (#64) --- .../RunTestsNoIncrementalPrecompile.yml | 30 +++++++++++++++++++ src/ConstructionBase.jl | 9 +++--- src/functions.jl | 2 +- 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/RunTestsNoIncrementalPrecompile.yml diff --git a/.github/workflows/RunTestsNoIncrementalPrecompile.yml b/.github/workflows/RunTestsNoIncrementalPrecompile.yml new file mode 100644 index 0000000..3f831d8 --- /dev/null +++ b/.github/workflows/RunTestsNoIncrementalPrecompile.yml @@ -0,0 +1,30 @@ +name: CI +on: + - push + - pull_request +defaults: + run: + shell: bash +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + version: + - '1' + - 'nightly' + os: + - ubuntu-latest + arch: + - x64 + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/julia-buildpkg@latest + - name: Run tests without incremental precompilation + run: julia --compiled-modules=no --project -e "using Pkg; Pkg.test()" diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 86a7be4..af52cae 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -5,7 +5,6 @@ export setproperties export constructorof export getfields - # Use markdown files as docstring: for (name, path) in [ :ConstructionBase => joinpath(dirname(@__DIR__), "README.md"), @@ -49,6 +48,10 @@ getfields(x::NamedTuple) = x getproperties(o::NamedTuple) = o getproperties(o::Tuple) = o +function is_propertynames_overloaded(T::Type)::Bool + which(propertynames, Tuple{T}).sig !== Tuple{typeof(propertynames), Any} +end + @generated function check_properties_are_fields(obj) if is_propertynames_overloaded(obj) return quote @@ -68,10 +71,6 @@ getproperties(o::Tuple) = o end end -function is_propertynames_overloaded(T::Type)::Bool - which(propertynames, Tuple{T}).sig !== Tuple{typeof(propertynames), Any} -end - if VERSION >= v"1.7" function getproperties(obj) fnames = propertynames(obj) diff --git a/src/functions.jl b/src/functions.jl index 5c88b47..739aacd 100644 --- a/src/functions.jl +++ b/src/functions.jl @@ -3,6 +3,7 @@ # one for them based on the types of args passed to FunctionConstructor struct FunctionConstructor{F} end +_isgensym(s::Symbol) = occursin("#", string(s)) @generated function (fc::FunctionConstructor{F})(args...) where F T = getfield(parentmodule(F), nameof(F)) @@ -20,4 +21,3 @@ function ConstructionBase.constructorof(f::Type{F}) where F <: Function FunctionConstructor{F}() end -_isgensym(s::Symbol) = occursin("#", string(s)) From 7c1b2d06636d309d9e3668355ae4e84a3cff1e43 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Mon, 22 Aug 2022 11:39:21 +0200 Subject: [PATCH 33/49] v1.4.1 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 2b835d7..24e046a 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ConstructionBase" uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" authors = ["Takafumi Arakaki", "Rafael Schouten", "Jan Weidner"] -version = "1.4.0" +version = "1.4.1" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" From a756095dd0fbf5941a20bfbe7a024c2a1f9e39e7 Mon Sep 17 00:00:00 2001 From: Hendrik Ranocha Date: Thu, 1 Sep 2022 07:57:55 +0200 Subject: [PATCH 34/49] Add Invalidations.yml This is based on https://github.com/julia-actions/julia-invalidations. Adding such checks came up in https://discourse.julialang.org/t/potential-performance-regressions-in-julia-1-8-for-special-un-precompiled-type-dispatches-and-how-to-fix-them/86359. I suggest to add this check here since this package is widely used as a dependency. See also SciML/MuladdMacro.jl#26 and SciML/MuladdMacro.jl#29 --- .github/workflows/Invalidations.yml | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/Invalidations.yml diff --git a/.github/workflows/Invalidations.yml b/.github/workflows/Invalidations.yml new file mode 100644 index 0000000..4d0004e --- /dev/null +++ b/.github/workflows/Invalidations.yml @@ -0,0 +1,40 @@ +name: Invalidations + +on: + pull_request: + +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: always. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + evaluate: + # Only run on PRs to the default branch. + # In the PR trigger above branches can be specified only explicitly whereas this check should work for master, main, or any other default branch + if: github.base_ref == github.event.repository.default_branch + runs-on: ubuntu-latest + steps: + - uses: julia-actions/setup-julia@v1 + with: + version: '1' + - uses: actions/checkout@v3 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-invalidations@v1 + id: invs_pr + + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.repository.default_branch }} + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-invalidations@v1 + id: invs_default + + - name: Report invalidation counts + run: | + echo "Invalidations on default branch: ${{ steps.invs_default.outputs.total }} (${{ steps.invs_default.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY + echo "This branch: ${{ steps.invs_pr.outputs.total }} (${{ steps.invs_pr.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY + - name: Check if the PR does increase number of invalidations + if: steps.invs_pr.outputs.total > steps.invs_default.outputs.total + run: exit 1 From 65dcf63f9c57e45d5199caa130ae599236873a9f Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Fri, 16 Dec 2022 15:39:32 +0300 Subject: [PATCH 35/49] add intervalsets and staticarrays support --- Project.toml | 16 ++++++++++++++-- ext/IntervalSetsExt.jl | 8 ++++++++ ext/StaticArraysExt.jl | 15 +++++++++++++++ test/runtests.jl | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 ext/IntervalSetsExt.jl create mode 100644 ext/StaticArraysExt.jl diff --git a/Project.toml b/Project.toml index 24e046a..13543cd 100644 --- a/Project.toml +++ b/Project.toml @@ -1,16 +1,28 @@ name = "ConstructionBase" uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" authors = ["Takafumi Arakaki", "Rafael Schouten", "Jan Weidner"] -version = "1.4.1" +version = "1.5.0" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +[weakdeps] +IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[extensions] +IntervalSetsExt = "IntervalSets" +StaticArraysExt = "StaticArrays" + [compat] +IntervalSets = "0.5, 0.6, 0.7" +StaticArrays = "1" julia = "1" [extras] +IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["IntervalSets","StaticArrays","Test"] diff --git a/ext/IntervalSetsExt.jl b/ext/IntervalSetsExt.jl new file mode 100644 index 0000000..1208d8e --- /dev/null +++ b/ext/IntervalSetsExt.jl @@ -0,0 +1,8 @@ +module IntervalSetsExt + +using ConstructionBase +using IntervalSets + +ConstructionBase.constructorof(::Type{<:Interval{L, R}}) where {L, R} = Interval{L, R} + +end diff --git a/ext/StaticArraysExt.jl b/ext/StaticArraysExt.jl new file mode 100644 index 0000000..1490eaa --- /dev/null +++ b/ext/StaticArraysExt.jl @@ -0,0 +1,15 @@ +module StaticArraysExt + +using ConstructionBase +using StaticArrays + +# general static arrays need to keep the size parameter +ConstructionBase.constructorof(sa::Type{<:SArray{S}}) where {S} = SArray{S} +ConstructionBase.constructorof(sa::Type{<:MArray{S}}) where {S} = MArray{S} +ConstructionBase.constructorof(sa::Type{<:SizedArray{S}}) where {S} = SizedArray{S} + +# static vectors don't even need the explicit size specification +ConstructionBase.constructorof(::Type{<:SVector}) = SVector +ConstructionBase.constructorof(::Type{<:MVector}) = MVector + +end diff --git a/test/runtests.jl b/test/runtests.jl index 0cfb291..04fc695 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -467,3 +467,42 @@ end @inferred getproperties(funny_numbers(S,20)) @inferred getproperties(funny_numbers(S,40)) end + + +using StaticArrays, IntervalSets + +if isdefined(Base, :get_extension) # some 1.9 version + @testset "staticarrays" begin + sa = @SVector [2, 4, 6, 8] + sa2 = ConstructionBase.constructorof(typeof(sa))((3.0, 5.0, 7.0, 9.0)) + @test sa2 === @SVector [3.0, 5.0, 7.0, 9.0] + + ma = @MMatrix [2.0 4.0; 6.0 8.0] + ma2 = ConstructionBase.constructorof(typeof(ma))((1, 2, 3, 4)) + @test ma2 isa MArray{Tuple{2,2},Int,2,4} + @test all(ma2 .=== @MMatrix [1 3; 2 4]) + + sz = SizedArray{Tuple{2,2}}([1 2;3 4]) + sz2 = ConstructionBase.constructorof(typeof(sz))([:a :b; :c :d]) + @test sz2 == SizedArray{Tuple{2,2}}([:a :b; :c :d]) + @test typeof(sz2) <: SizedArray{Tuple{2,2},Symbol,2,2} + + for T in (SVector, MVector) + @test ConstructionBase.constructorof(T)((1, 2, 3))::T == T((1, 2, 3)) + @test ConstructionBase.constructorof(T{3})((1, 2, 3))::T == T((1, 2, 3)) + @test ConstructionBase.constructorof(T{3})((1, 2))::T == T((1, 2)) + @test ConstructionBase.constructorof(T{3, Symbol})((1, 2, 3))::T == T((1, 2, 3)) + @test ConstructionBase.constructorof(T{3, Symbol})((1, 2))::T == T((1, 2)) + @test ConstructionBase.constructorof(T{3, X} where {X})((1, 2, 3))::T == T((1, 2, 3)) + @test ConstructionBase.constructorof(T{3, X} where {X})((1, 2))::T == T((1, 2)) + @test ConstructionBase.constructorof(T{X, Symbol} where {X})((1, 2, 3))::T == T((1, 2, 3)) + end + end + + @testset "intervalsets" begin + @test constructorof(typeof(1..2))(0.5, 1.5) === 0.5..1.5 + @test constructorof(typeof(OpenInterval(1, 2)))(0.5, 1.5) === OpenInterval(0.5, 1.5) + @test setproperties(1..2, left=0.0) === 0.0..2.0 + @test setproperties(OpenInterval(1.0, 2.0), left=1, right=5) === OpenInterval(1, 5) + end +end From 6c69d23fbedd5c98788e7e644acf47b7a4b55765 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Sat, 17 Dec 2022 14:06:08 +0300 Subject: [PATCH 36/49] support named properties of staticvectors --- ext/StaticArraysExt.jl | 15 +++++++++++++++ test/runtests.jl | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/ext/StaticArraysExt.jl b/ext/StaticArraysExt.jl index 1490eaa..57ea7d2 100644 --- a/ext/StaticArraysExt.jl +++ b/ext/StaticArraysExt.jl @@ -12,4 +12,19 @@ ConstructionBase.constructorof(sa::Type{<:SizedArray{S}}) where {S} = SizedArray ConstructionBase.constructorof(::Type{<:SVector}) = SVector ConstructionBase.constructorof(::Type{<:MVector}) = MVector +# set properties by name: x, y, z, w +@generated function ConstructionBase.setproperties(obj::Union{SVector{N}, MVector{N}}, patch::NamedTuple{KS}) where {N, KS} + if KS == (:data,) + :( constructorof(typeof(obj))(only(patch)) ) + else + propnames = (:x, :y, :z, :w)[1:N] + KS ⊆ propnames || error("type $obj does not have properties $(join(KS, ", "))") + field_exprs = map(enumerate(propnames)) do (i, p) + from = p ∈ KS ? :patch : :obj + :( $from.$p ) + end + :( constructorof(typeof(obj))($(field_exprs...)) ) + end +end + end diff --git a/test/runtests.jl b/test/runtests.jl index 04fc695..07e977a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -497,6 +497,13 @@ if isdefined(Base, :get_extension) # some 1.9 version @test ConstructionBase.constructorof(T{3, X} where {X})((1, 2))::T == T((1, 2)) @test ConstructionBase.constructorof(T{X, Symbol} where {X})((1, 2, 3))::T == T((1, 2, 3)) end + + sv = SVector(1, 2) + @test SVector(3.0, 2.0) === @inferred setproperties(sv, x = 3.0) + @test SVector(3.0, 5.0) === @inferred setproperties(sv, x = 3.0, y = 5.0) + @test SVector(-1.0, -2.0) === @inferred setproperties(sv, data = (-1.0, -2)) + + @test_throws "does not have properties z" setproperties(sv, z = 3.0) end @testset "intervalsets" begin From 7e1d7d16467b499de8f667ac269cf8167e13dce1 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Fri, 27 Jan 2023 02:07:46 +0300 Subject: [PATCH 37/49] better exception --- ext/StaticArraysExt.jl | 1 + test/runtests.jl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/StaticArraysExt.jl b/ext/StaticArraysExt.jl index 57ea7d2..6bcc490 100644 --- a/ext/StaticArraysExt.jl +++ b/ext/StaticArraysExt.jl @@ -17,6 +17,7 @@ ConstructionBase.constructorof(::Type{<:MVector}) = MVector if KS == (:data,) :( constructorof(typeof(obj))(only(patch)) ) else + N <= 4 || error("type $obj does not have properties $(join(KS, ", "))") propnames = (:x, :y, :z, :w)[1:N] KS ⊆ propnames || error("type $obj does not have properties $(join(KS, ", "))") field_exprs = map(enumerate(propnames)) do (i, p) diff --git a/test/runtests.jl b/test/runtests.jl index 07e977a..97d5d97 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -502,8 +502,8 @@ if isdefined(Base, :get_extension) # some 1.9 version @test SVector(3.0, 2.0) === @inferred setproperties(sv, x = 3.0) @test SVector(3.0, 5.0) === @inferred setproperties(sv, x = 3.0, y = 5.0) @test SVector(-1.0, -2.0) === @inferred setproperties(sv, data = (-1.0, -2)) - @test_throws "does not have properties z" setproperties(sv, z = 3.0) + @test_throws "does not have properties z" setproperties(SVector(1, 2, 3, 4, 5), z = 3.0) end @testset "intervalsets" begin From 5d04874f6b56b28e3f7aa973d0b12ea151a4640a Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Fri, 27 Jan 2023 02:09:07 +0300 Subject: [PATCH 38/49] add @inferred --- test/runtests.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 97d5d97..294fd82 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -478,24 +478,24 @@ if isdefined(Base, :get_extension) # some 1.9 version @test sa2 === @SVector [3.0, 5.0, 7.0, 9.0] ma = @MMatrix [2.0 4.0; 6.0 8.0] - ma2 = ConstructionBase.constructorof(typeof(ma))((1, 2, 3, 4)) + ma2 = @inferred ConstructionBase.constructorof(typeof(ma))((1, 2, 3, 4)) @test ma2 isa MArray{Tuple{2,2},Int,2,4} @test all(ma2 .=== @MMatrix [1 3; 2 4]) sz = SizedArray{Tuple{2,2}}([1 2;3 4]) - sz2 = ConstructionBase.constructorof(typeof(sz))([:a :b; :c :d]) + sz2 = @inferred ConstructionBase.constructorof(typeof(sz))([:a :b; :c :d]) @test sz2 == SizedArray{Tuple{2,2}}([:a :b; :c :d]) @test typeof(sz2) <: SizedArray{Tuple{2,2},Symbol,2,2} for T in (SVector, MVector) - @test ConstructionBase.constructorof(T)((1, 2, 3))::T == T((1, 2, 3)) - @test ConstructionBase.constructorof(T{3})((1, 2, 3))::T == T((1, 2, 3)) - @test ConstructionBase.constructorof(T{3})((1, 2))::T == T((1, 2)) - @test ConstructionBase.constructorof(T{3, Symbol})((1, 2, 3))::T == T((1, 2, 3)) - @test ConstructionBase.constructorof(T{3, Symbol})((1, 2))::T == T((1, 2)) - @test ConstructionBase.constructorof(T{3, X} where {X})((1, 2, 3))::T == T((1, 2, 3)) - @test ConstructionBase.constructorof(T{3, X} where {X})((1, 2))::T == T((1, 2)) - @test ConstructionBase.constructorof(T{X, Symbol} where {X})((1, 2, 3))::T == T((1, 2, 3)) + @test @inferred(ConstructionBase.constructorof(T)((1, 2, 3)))::T == T((1, 2, 3)) + @test @inferred(ConstructionBase.constructorof(T{3})((1, 2, 3)))::T == T((1, 2, 3)) + @test @inferred(ConstructionBase.constructorof(T{3})((1, 2)))::T == T((1, 2)) + @test @inferred(ConstructionBase.constructorof(T{3, Symbol})((1, 2, 3)))::T == T((1, 2, 3)) + @test @inferred(ConstructionBase.constructorof(T{3, Symbol})((1, 2)))::T == T((1, 2)) + @test @inferred(ConstructionBase.constructorof(T{3, X} where {X})((1, 2, 3)))::T == T((1, 2, 3)) + @test @inferred(ConstructionBase.constructorof(T{3, X} where {X})((1, 2)))::T == T((1, 2)) + @test @inferred(ConstructionBase.constructorof(T{X, Symbol} where {X})((1, 2, 3)))::T == T((1, 2, 3)) end sv = SVector(1, 2) From 1c0b885347fd98e10c88f7e3eb5ceb02ebd0338c Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Thu, 26 Jan 2023 12:52:11 +0300 Subject: [PATCH 39/49] support numeric propertynames --- src/ConstructionBase.jl | 12 +++++++++++- test/runtests.jl | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index af52cae..546eae5 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -71,10 +71,20 @@ end end end +# names are consecutive integers: return tuple +# names are symbols: return namedtuple +# names are empty (object has no properties): also return namedtuple, for backwards compat and generally makes more sense +@inline tuple_or_ntuple(names::Tuple{}, vals::Tuple) = NamedTuple{names}(vals) +@inline tuple_or_ntuple(names::Tuple{Vararg{Symbol}}, vals::Tuple) = NamedTuple{names}(vals) +@inline function tuple_or_ntuple(names::Tuple{Vararg{Int}}, vals::Tuple) + @assert names === ntuple(identity, length(names)) + vals +end + if VERSION >= v"1.7" function getproperties(obj) fnames = propertynames(obj) - NamedTuple{fnames}(getproperty.(Ref(obj), fnames)) + tuple_or_ntuple(fnames, getproperty.(Ref(obj), fnames)) end function getfields(obj::T) where {T} fnames = fieldnames(T) diff --git a/test/runtests.jl b/test/runtests.jl index 0cfb291..235c4da 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -298,6 +298,21 @@ Base.getproperty(obj::FieldProps, name::Symbol) = getproperty(getfield(obj, :com end end +struct SProp + names +end +Base.propertynames(s::SProp) = getfield(s, :names) +Base.getproperty(s::SProp, prop::Symbol) = "ps$prop" +Base.getproperty(s::SProp, prop::Int) = "pi$prop" +Base.getproperty(s::SProp, prop::String) = "pstr$prop" + +@testset "properties can be numbered" begin + @test getproperties(SProp((:a, :b))) === (a="psa", b="psb") + @test getproperties(SProp((1, 2))) === ("pi1", "pi2") + # what should it return? + @test_broken getproperties(SProp(("a", "b"))) +end + function funny_numbers(::Type{Tuple}, n)::Tuple types = [ Int128, Int16, Int32, Int64, Int8, From 33bf7b9d2af8f9ef29e4f4dc69cbb27b6401aac7 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Fri, 27 Jan 2023 02:19:46 +0300 Subject: [PATCH 40/49] fix test --- test/runtests.jl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 235c4da..d4b6d05 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -298,6 +298,7 @@ Base.getproperty(obj::FieldProps, name::Symbol) = getproperty(getfield(obj, :com end end + struct SProp names end @@ -306,11 +307,15 @@ Base.getproperty(s::SProp, prop::Symbol) = "ps$prop" Base.getproperty(s::SProp, prop::Int) = "pi$prop" Base.getproperty(s::SProp, prop::String) = "pstr$prop" -@testset "properties can be numbered" begin - @test getproperties(SProp((:a, :b))) === (a="psa", b="psb") - @test getproperties(SProp((1, 2))) === ("pi1", "pi2") - # what should it return? - @test_broken getproperties(SProp(("a", "b"))) +if VERSION >= v"1.7" + # automatic getproperties() supported only on 1.7+ + + @testset "properties can be numbered" begin + @test getproperties(SProp((:a, :b))) === (a="psa", b="psb") + @test getproperties(SProp((1, 2))) === ("pi1", "pi2") + # what should it return? + @test_broken getproperties(SProp(("a", "b"))) + end end function funny_numbers(::Type{Tuple}, n)::Tuple From 25fc0c6911945961b784a5c373b94ce3ebcb2aaf Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Fri, 10 Feb 2023 19:33:27 +0300 Subject: [PATCH 41/49] fix constructorof(Expr) --- src/nonstandard.jl | 3 +++ test/runtests.jl | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/nonstandard.jl b/src/nonstandard.jl index 640306e..996eae6 100644 --- a/src/nonstandard.jl +++ b/src/nonstandard.jl @@ -52,3 +52,6 @@ constructorof(::Type{<:LinearAlgebra.Tridiagonal}) = tridiagonal_constructor linrange_constructor(start, stop, len, lendiv) = LinRange(start, stop, len) constructorof(::Type{<:LinRange}) = linrange_constructor + +### Expr: args get splatted +constructorof(::Type{<:Expr}) = (head, args) -> Expr(head, args...) diff --git a/test/runtests.jl b/test/runtests.jl index 0cfb291..0916b7b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -194,6 +194,10 @@ end @inferred constructorof(typeof(lr1))(getfields(lr2)...) end + @testset "Expr" begin + e = :(a + b) + @test e == @inferred constructorof(typeof(e))(getfields(e)...) + end end @testset "Anonymous function constructors" begin From 16e8066d06953503a80ce0553bda98ea7b34d48f Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Fri, 17 Feb 2023 11:49:19 +0300 Subject: [PATCH 42/49] fix type-stability on old Julias --- src/nonstandard.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nonstandard.jl b/src/nonstandard.jl index 996eae6..2ba642d 100644 --- a/src/nonstandard.jl +++ b/src/nonstandard.jl @@ -54,4 +54,5 @@ linrange_constructor(start, stop, len, lendiv) = LinRange(start, stop, len) constructorof(::Type{<:LinRange}) = linrange_constructor ### Expr: args get splatted -constructorof(::Type{<:Expr}) = (head, args) -> Expr(head, args...) +# ::Expr annotation is to make it type-stable on Julia 1.3- +constructorof(::Type{<:Expr}) = (head, args) -> Expr(head, args...)::Expr From 871a395f66d4aaf6292cef395764cc789195970d Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Thu, 23 Feb 2023 13:39:53 +0300 Subject: [PATCH 43/49] fix #72 --- .github/workflows/CI.yml | 2 ++ .github/workflows/Downstream.yml | 2 +- src/ConstructionBase.jl | 37 ++++++++++++++++++++++++-------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d2be379..d14d01c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -17,7 +17,9 @@ jobs: - '1.3' - '1.5' - '1.6' + - '1.7' - '1' + - '1.9-nightly' - 'nightly' os: - ubuntu-latest diff --git a/.github/workflows/Downstream.yml b/.github/workflows/Downstream.yml index 805c696..162e912 100644 --- a/.github/workflows/Downstream.yml +++ b/.github/workflows/Downstream.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - julia-version: [1.6, 1, nightly] + julia-version: [1.6, 1, 1.9-nightly, nightly] os: [ubuntu-latest] package: - {repo: JuliaObjects/Accessors.jl} diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 546eae5..482a5c2 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -48,13 +48,9 @@ getfields(x::NamedTuple) = x getproperties(o::NamedTuple) = o getproperties(o::Tuple) = o -function is_propertynames_overloaded(T::Type)::Bool - which(propertynames, Tuple{T}).sig !== Tuple{typeof(propertynames), Any} -end - -@generated function check_properties_are_fields(obj) - if is_propertynames_overloaded(obj) - return quote +if VERSION >= v"1.7" + function check_properties_are_fields(obj) + if propertynames(obj) != fieldnames(typeof(obj)) T = typeof(obj) msg = """ The function `Base.propertynames` was overloaded for type `$T`. @@ -65,9 +61,32 @@ end ``` """ error(msg) + else + :(nothing) + end + end +else + function is_propertynames_overloaded(T::Type)::Bool + which(propertynames, Tuple{T}).sig !== Tuple{typeof(propertynames), Any} + end + + @generated function check_properties_are_fields(obj) + if is_propertynames_overloaded(obj) + return quote + T = typeof(obj) + msg = """ + The function `Base.propertynames` was overloaded for type `$T`. + Please make sure the following methods are also overloaded for this type: + ```julia + ConstructionBase.setproperties + ConstructionBase.getproperties # optional in VERSION >= julia v1.7 + ``` + """ + error(msg) + end + else + :(nothing) end - else - :(nothing) end end From 376bffb570023c71bfb462f1d2062fa8eeb0729f Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Thu, 23 Feb 2023 14:12:46 +0300 Subject: [PATCH 44/49] simplify --- src/ConstructionBase.jl | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/ConstructionBase.jl b/src/ConstructionBase.jl index 482a5c2..6174be0 100644 --- a/src/ConstructionBase.jl +++ b/src/ConstructionBase.jl @@ -51,18 +51,10 @@ getproperties(o::Tuple) = o if VERSION >= v"1.7" function check_properties_are_fields(obj) if propertynames(obj) != fieldnames(typeof(obj)) - T = typeof(obj) - msg = """ - The function `Base.propertynames` was overloaded for type `$T`. - Please make sure the following methods are also overloaded for this type: - ```julia - ConstructionBase.setproperties - ConstructionBase.getproperties # optional in VERSION >= julia v1.7 - ``` - """ - error(msg) - else - :(nothing) + error(""" + The function `Base.propertynames` was overloaded for type `$(typeof(obj))`. + Please make sure `ConstructionBase.setproperties` is also overloaded for this type. + """) end end else From 375ae551680ffe640b6b89b741c4743ffe54f2ea Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Thu, 23 Feb 2023 14:14:33 +0300 Subject: [PATCH 45/49] bump --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 13543cd..7611b5c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ConstructionBase" uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" authors = ["Takafumi Arakaki", "Rafael Schouten", "Jan Weidner"] -version = "1.5.0" +version = "1.5.1" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" From 5f08dd3c4f28c4b0cdc22cf95723fd5e2721d389 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Thu, 23 Feb 2023 14:15:20 +0300 Subject: [PATCH 46/49] remove 1.9-nightly CI --- .github/workflows/CI.yml | 1 - .github/workflows/Downstream.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d14d01c..f70b68b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -19,7 +19,6 @@ jobs: - '1.6' - '1.7' - '1' - - '1.9-nightly' - 'nightly' os: - ubuntu-latest diff --git a/.github/workflows/Downstream.yml b/.github/workflows/Downstream.yml index 162e912..805c696 100644 --- a/.github/workflows/Downstream.yml +++ b/.github/workflows/Downstream.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - julia-version: [1.6, 1, 1.9-nightly, nightly] + julia-version: [1.6, 1, nightly] os: [ubuntu-latest] package: - {repo: JuliaObjects/Accessors.jl} From 852910cd854e22e6b990b2b6cc7b23fac2fa8408 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 23 Feb 2023 14:30:23 +0100 Subject: [PATCH 47/49] Revert "bump" This reverts commit 375ae551680ffe640b6b89b741c4743ffe54f2ea. --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 7611b5c..13543cd 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ConstructionBase" uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" authors = ["Takafumi Arakaki", "Rafael Schouten", "Jan Weidner"] -version = "1.5.1" +version = "1.5.0" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" From 4e161e74b6b0a37c069bb0e5262eb32492c365ad Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Thu, 23 Feb 2023 14:14:33 +0300 Subject: [PATCH 48/49] bump --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 13543cd..7611b5c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ConstructionBase" uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" authors = ["Takafumi Arakaki", "Rafael Schouten", "Jan Weidner"] -version = "1.5.0" +version = "1.5.1" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" From 1f0c664319ee31eeac3ecfdfcc9791eb22c4120d Mon Sep 17 00:00:00 2001 From: Max Horn Date: Mon, 24 Apr 2023 20:14:19 +0200 Subject: [PATCH 49/49] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f624da6..892bc0d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Build Status](https://github.com/JuliaObjects/ConstructionBase.jl/workflows/CI/badge.svg)](https://github.com/JuliaObjects/ConstructionBase.jl/actions?query=workflow%3ACI) [![GitHub stars](https://img.shields.io/github/stars/JuliaObjects/ConstructionBase.jl?style=social)](https://github.com/JuliaObjects/ConstructionBase.jl) -ConstructionBase is a very lightwight package, that provides primitive functions for construction of objects: +ConstructionBase is a very lightweight package, that provides primitive functions for construction of objects: ```julia setproperties(obj::MyType, patch::NamedTuple) constructorof(MyType)