Skip to content

Commit a268e00

Browse files
authored
Merge pull request #54 from JuliaObjects/fieldvalues
implement and document getfields
2 parents 518a6b8 + cd06f8c commit a268e00

13 files changed

+340
-141
lines changed

.github/workflows/CI.yml

+1-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
1111
runs-on: ${{ matrix.os }}
1212
strategy:
13-
fail-fast: false
13+
fail-fast: true
1414
matrix:
1515
version:
1616
- '1.0'
@@ -33,8 +33,3 @@ jobs:
3333
arch: ${{ matrix.arch }}
3434
- uses: julia-actions/julia-buildpkg@latest
3535
- uses: julia-actions/julia-runtest@latest
36-
- uses: julia-actions/julia-processcoverage@v1
37-
- uses: codecov/codecov-action@v1
38-
with:
39-
file: lcov.info
40-
fail_ci_if_error: true

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ConstructionBase"
22
uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
33
authors = ["Takafumi Arakaki", "Rafael Schouten", "Jan Weidner"]
4-
version = "1.3.1"
4+
version = "1.4.0"
55

66
[deps]
77
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaObjects.github.io/ConstructionBase.jl/stable)
44
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaObjects.github.io/ConstructionBase.jl/dev)
55
[![Build Status](https://github.com/JuliaObjects/ConstructionBase.jl/workflows/CI/badge.svg)](https://github.com/JuliaObjects/ConstructionBase.jl/actions?query=workflow%3ACI)
6-
[![Codecov](https://codecov.io/gh/JuliaObjects/ConstructionBase.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaObjects/ConstructionBase.jl)
76
[![GitHub stars](https://img.shields.io/github/stars/JuliaObjects/ConstructionBase.jl?style=social)](https://github.com/JuliaObjects/ConstructionBase.jl)
87

98
ConstructionBase is a very lightwight package, that provides primitive functions for construction of objects:

docs/Manifest.toml

+102-42
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,149 @@
11
# This file is machine-generated - editing it directly is not advised
22

3-
[[Base64]]
3+
julia_version = "1.7.0"
4+
manifest_format = "2.0"
5+
6+
[[deps.ANSIColoredPrinters]]
7+
git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c"
8+
uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9"
9+
version = "0.0.1"
10+
11+
[[deps.Artifacts]]
12+
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
13+
14+
[[deps.Base64]]
415
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
516

6-
[[Dates]]
17+
[[deps.CompilerSupportLibraries_jll]]
18+
deps = ["Artifacts", "Libdl"]
19+
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
20+
21+
[[deps.ConstructionBase]]
22+
deps = ["LinearAlgebra"]
23+
path = ".."
24+
uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
25+
version = "1.3.0"
26+
27+
[[deps.Dates]]
728
deps = ["Printf"]
829
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
930

10-
[[Distributed]]
11-
deps = ["Random", "Serialization", "Sockets"]
12-
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
13-
14-
[[DocStringExtensions]]
15-
deps = ["LibGit2", "Markdown", "Pkg", "Test"]
16-
git-tree-sha1 = "0513f1a8991e9d83255e0140aace0d0fc4486600"
31+
[[deps.DocStringExtensions]]
32+
deps = ["LibGit2"]
33+
git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
1734
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
18-
version = "0.8.0"
35+
version = "0.8.6"
1936

20-
[[Documenter]]
21-
deps = ["Base64", "DocStringExtensions", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"]
22-
git-tree-sha1 = "1b6ae3796f60311e39cd1770566140d2c056e87f"
37+
[[deps.Documenter]]
38+
deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"]
39+
git-tree-sha1 = "7d9a46421aef53cbd6b8ecc40c3dcbacbceaf40e"
2340
uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
24-
version = "0.23.3"
41+
version = "0.27.15"
42+
43+
[[deps.Future]]
44+
deps = ["Random"]
45+
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"
2546

26-
[[InteractiveUtils]]
47+
[[deps.IOCapture]]
48+
deps = ["Logging", "Random"]
49+
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
50+
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
51+
version = "0.2.2"
52+
53+
[[deps.InteractiveUtils]]
2754
deps = ["Markdown"]
2855
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
2956

30-
[[JSON]]
57+
[[deps.JSON]]
3158
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
32-
git-tree-sha1 = "b34d7cef7b337321e97d22242c3c2b91f476748e"
59+
git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e"
3360
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
34-
version = "0.21.0"
61+
version = "0.21.3"
3562

36-
[[LibGit2]]
63+
[[deps.LibGit2]]
64+
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
3765
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
3866

39-
[[Logging]]
67+
[[deps.Libdl]]
68+
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
69+
70+
[[deps.LinearAlgebra]]
71+
deps = ["Libdl", "libblastrampoline_jll"]
72+
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
73+
74+
[[deps.Logging]]
4075
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
4176

42-
[[Markdown]]
77+
[[deps.MacroTools]]
78+
deps = ["Markdown", "Random"]
79+
git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf"
80+
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
81+
version = "0.5.9"
82+
83+
[[deps.Markdown]]
4384
deps = ["Base64"]
4485
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
4586

46-
[[Mmap]]
87+
[[deps.Mmap]]
4788
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
4889

49-
[[Parsers]]
50-
deps = ["Dates", "Test"]
51-
git-tree-sha1 = "ef0af6c8601db18c282d092ccbd2f01f3f0cd70b"
52-
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
53-
version = "0.3.7"
90+
[[deps.NetworkOptions]]
91+
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
92+
93+
[[deps.OpenBLAS_jll]]
94+
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
95+
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
5496

55-
[[Pkg]]
56-
deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
57-
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
97+
[[deps.Parsers]]
98+
deps = ["Dates"]
99+
git-tree-sha1 = "85b5da0fa43588c75bb1ff986493443f821c70b7"
100+
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
101+
version = "2.2.3"
58102

59-
[[Printf]]
103+
[[deps.Printf]]
60104
deps = ["Unicode"]
61105
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
62106

63-
[[REPL]]
64-
deps = ["InteractiveUtils", "Markdown", "Sockets"]
107+
[[deps.REPL]]
108+
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
65109
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
66110

67-
[[Random]]
68-
deps = ["Serialization"]
111+
[[deps.Random]]
112+
deps = ["SHA", "Serialization"]
69113
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
70114

71-
[[SHA]]
115+
[[deps.Requires]]
116+
deps = ["UUIDs"]
117+
git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
118+
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
119+
version = "1.3.0"
120+
121+
[[deps.SHA]]
72122
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
73123

74-
[[Serialization]]
124+
[[deps.Serialization]]
75125
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
76126

77-
[[Sockets]]
127+
[[deps.Setfield]]
128+
deps = ["ConstructionBase", "Future", "MacroTools", "Requires"]
129+
git-tree-sha1 = "38d88503f695eb0301479bc9b0d4320b378bafe5"
130+
uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46"
131+
version = "0.8.2"
132+
133+
[[deps.Sockets]]
78134
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
79135

80-
[[Test]]
81-
deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
136+
[[deps.Test]]
137+
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
82138
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
83139

84-
[[UUIDs]]
140+
[[deps.UUIDs]]
85141
deps = ["Random", "SHA"]
86142
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
87143

88-
[[Unicode]]
144+
[[deps.Unicode]]
89145
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
146+
147+
[[deps.libblastrampoline_jll]]
148+
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
149+
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

docs/Project.toml

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[deps]
2+
ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
23
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
34
Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46"

docs/src/index.md

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
# ConstructionBase.jl
22

3-
```@index
4-
```
3+
[`ConstructionBase`](@ref) allows flexible construction and destructuring of objects.
4+
There are two levels of under which this can be done:
5+
### [The raw level](@id the-raw-level)
6+
This is where `Base.fieldnames`, `Base.getfield`, `Base.setfield!` live.
7+
This level is what an object is ultimately composed of including all private details.
8+
At the raw level [`ConstructionBase`](@ref) adds [`constructorof`](@ref) and [`getfields`](@ref).
9+
### [The semantic level](@id the-semantic-level)
10+
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.
11+
At the semantic level [`ConstructionBase`](@ref) adds [`setproperties`](@ref) and [`getproperties`](@ref).
12+
513

614
## Interface
715

16+
```@index
17+
```
18+
819
```@docs
920
ConstructionBase
1021
ConstructionBase.constructorof
22+
ConstructionBase.getfields
1123
ConstructionBase.getproperties
1224
ConstructionBase.setproperties
1325
```

src/ConstructionBase.jl

+50-22
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ module ConstructionBase
33
export getproperties
44
export setproperties
55
export constructorof
6+
export getfields
7+
68

79
# Use markdown files as docstring:
810
for (name, path) in [
911
:ConstructionBase => joinpath(dirname(@__DIR__), "README.md"),
1012
:constructorof => joinpath(@__DIR__, "constructorof.md"),
13+
:getfields => joinpath(@__DIR__, "getfields.md"),
1114
:getproperties => joinpath(@__DIR__, "getproperties.md"),
1215
:setproperties => joinpath(@__DIR__, "setproperties.md"),
1316
]
@@ -38,33 +41,58 @@ struct NamedTupleConstructor{names} end
3841
NamedTuple{names}(args)
3942
end
4043

44+
################################################################################
45+
#### getfields
46+
################################################################################
47+
getfields(x::Tuple) = x
48+
getfields(x::NamedTuple) = x
4149
getproperties(o::NamedTuple) = o
4250
getproperties(o::Tuple) = o
51+
52+
@generated function check_properties_are_fields(obj)
53+
if is_propertynames_overloaded(obj)
54+
return quote
55+
T = typeof(obj)
56+
msg = """
57+
The function `Base.propertynames` was overloaded for type `$T`.
58+
Please make sure the following methods are also overloaded for this type:
59+
```julia
60+
ConstructionBase.setproperties
61+
ConstructionBase.getproperties # optional in VERSION >= julia v1.7
62+
```
63+
"""
64+
error(msg)
65+
end
66+
else
67+
:(nothing)
68+
end
69+
end
70+
71+
function is_propertynames_overloaded(T::Type)::Bool
72+
which(propertynames, Tuple{T}).sig !== Tuple{typeof(propertynames), Any}
73+
end
74+
4375
if VERSION >= v"1.7"
4476
function getproperties(obj)
4577
fnames = propertynames(obj)
4678
NamedTuple{fnames}(getproperty.(Ref(obj), fnames))
4779
end
80+
function getfields(obj::T) where {T}
81+
fnames = fieldnames(T)
82+
NamedTuple{fnames}(getfield.(Ref(obj), fnames))
83+
end
4884
else
49-
@generated function getproperties(obj)
50-
if which(propertynames, Tuple{obj}).sig != Tuple{typeof(propertynames), Any}
51-
# custom propertynames defined for this type
52-
return quote
53-
msg = """
54-
Different fieldnames and propertynames are only supported on Julia v1.7+.
55-
For older julia versions, consider overloading
56-
`ConstructionBase.getproperties(obj::$(typeof(obj))`.
57-
See also https://github.com/JuliaObjects/ConstructionBase.jl/pull/60.
58-
"""
59-
error(msg)
60-
end
61-
end
85+
@generated function getfields(obj)
6286
fnames = fieldnames(obj)
6387
fvals = map(fnames) do fname
64-
:(obj.$fname)
88+
Expr(:call, :getfield, :obj, QuoteNode(fname))
6589
end
6690
:(NamedTuple{$fnames}(($(fvals...),)))
6791
end
92+
function getproperties(obj)
93+
check_properties_are_fields(obj)
94+
getfields(obj)
95+
end
6896
end
6997

7098
################################################################################
@@ -92,20 +120,17 @@ setproperties_namedtuple(obj, patch::Tuple{}) = obj
92120
end
93121
function setproperties_namedtuple(obj, patch)
94122
res = merge(obj, patch)
95-
validate_setproperties_result(res, obj, obj, patch)
123+
check_patch_properties_exist(res, obj, obj, patch)
96124
res
97125
end
98-
function validate_setproperties_result(
126+
function check_patch_properties_exist(
99127
nt_new::NamedTuple{fields}, nt_old::NamedTuple{fields}, obj, patch) where {fields}
100128
nothing
101129
end
102-
@noinline function validate_setproperties_result(nt_new, nt_old, obj, patch)
130+
@noinline function check_patch_properties_exist(nt_new, nt_old, obj, patch)
103131
O = typeof(obj)
104132
msg = """
105133
Failed to assign properties $(propertynames(patch)) to object with properties $(propertynames(obj)).
106-
You may want to overload
107-
ConstructionBase.setproperties(obj::$O, patch::NamedTuple)
108-
ConstructionBase.getproperties(obj::$O)
109134
"""
110135
throw(ArgumentError(msg))
111136
end
@@ -157,11 +182,14 @@ setproperties_object(obj, patch::Tuple{}) = obj
157182
throw(ArgumentError(msg))
158183
end
159184
setproperties_object(obj, patch::NamedTuple{()}) = obj
185+
160186
function setproperties_object(obj, patch)
187+
check_properties_are_fields(obj)
161188
nt = getproperties(obj)
162189
nt_new = merge(nt, patch)
163-
validate_setproperties_result(nt_new, nt, obj, patch)
164-
constructorof(typeof(obj))(Tuple(nt_new)...)
190+
check_patch_properties_exist(nt_new, nt, obj, patch)
191+
args = Tuple(nt_new) # old julia inference prefers if we wrap in Tuple
192+
constructorof(typeof(obj))(args...)
165193
end
166194

167195
include("nonstandard.jl")

0 commit comments

Comments
 (0)