Skip to content

implement and document getfields #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jul 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -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.4.0"

[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
144 changes: 102 additions & 42 deletions docs/Manifest.toml
Original file line number Diff line number Diff line change
@@ -1,89 +1,149 @@
# This file is machine-generated - editing it directly is not advised

[[Base64]]
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"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[Dates]]
[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[deps.ConstructionBase]]
deps = ["LinearAlgebra"]
path = ".."
uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
version = "1.3.0"

[[deps.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.DocStringExtensions]]
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.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.23.3"
version = "0.27.15"

[[deps.Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

[[InteractiveUtils]]
[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[JSON]]
[[deps.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.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[Logging]]
[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[Markdown]]
[[deps.MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.9"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[Mmap]]
[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[Parsers]]
deps = ["Dates", "Test"]
git-tree-sha1 = "ef0af6c8601db18c282d092ccbd2f01f3f0cd70b"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "0.3.7"
[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

[[Pkg]]
deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
[[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 = ["InteractiveUtils", "Markdown", "Sockets"]
[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[SHA]]
[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.3.0"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Serialization]]
[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[Sockets]]
[[deps.Setfield]]
deps = ["ConstructionBase", "Future", "MacroTools", "Requires"]
git-tree-sha1 = "38d88503f695eb0301479bc9b0d4320b378bafe5"
uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46"
version = "0.8.2"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[Test]]
deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
[[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"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[deps]
ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46"
16 changes: 14 additions & 2 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
# 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
ConstructionBase.getfields
ConstructionBase.getproperties
ConstructionBase.setproperties
```
Expand Down
72 changes: 50 additions & 22 deletions src/ConstructionBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ module ConstructionBase
export getproperties
export setproperties
export constructorof
export getfields


# Use markdown files as docstring:
for (name, path) in [
:ConstructionBase => joinpath(dirname(@__DIR__), "README.md"),
:constructorof => joinpath(@__DIR__, "constructorof.md"),
:getfields => joinpath(@__DIR__, "getfields.md"),
:getproperties => joinpath(@__DIR__, "getproperties.md"),
:setproperties => joinpath(@__DIR__, "setproperties.md"),
]
Expand Down Expand Up @@ -38,33 +41,58 @@ struct NamedTupleConstructor{names} end
NamedTuple{names}(args)
end

################################################################################
#### getfields
################################################################################
getfields(x::Tuple) = x
getfields(x::NamedTuple) = x
getproperties(o::NamedTuple) = o
getproperties(o::Tuple) = o

@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
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)
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 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
@generated function getfields(obj)
fnames = fieldnames(obj)
fvals = map(fnames) do fname
:(obj.$fname)
Expr(:call, :getfield, :obj, QuoteNode(fname))
end
:(NamedTuple{$fnames}(($(fvals...),)))
end
function getproperties(obj)
check_properties_are_fields(obj)
getfields(obj)
end
end

################################################################################
Expand Down Expand Up @@ -92,20 +120,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
Expand Down Expand Up @@ -157,11 +182,14 @@ 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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aplavin I added a check as you suggested.

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)
args = Tuple(nt_new) # old julia inference prefers if we wrap in Tuple
constructorof(typeof(obj))(args...)
end

include("nonstandard.jl")
Expand Down
Loading