Skip to content

Commit

Permalink
Merge pull request #54 from JuliaObjects/fieldvalues
Browse files Browse the repository at this point in the history
implement and document getfields
  • Loading branch information
jw3126 authored Jul 4, 2022
2 parents 518a6b8 + cd06f8c commit a268e00
Show file tree
Hide file tree
Showing 13 changed files with 340 additions and 141 deletions.
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)
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

2 comments on commit a268e00

@jw3126
Copy link
Member Author

@jw3126 jw3126 commented on a268e00 Jul 4, 2022

Choose a reason for hiding this comment

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

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

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

Registration pull request created: JuliaRegistries/General/63602

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.4.0 -m "<description of version>" a268e005efd272ddaf5a821b6fc01f28d02ec5ad
git push origin v1.4.0

Please sign in to comment.