diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3bb1e24..9aef1a4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -17,23 +17,30 @@ jobs: matrix: version: - '1.7' - - '1.9' - '1.10' + - '1.12' + - 'pre' - 'nightly' os: - ubuntu-latest - windows-latest - macos-latest arch: - - x64 + - 'default' + - 'x86' + exclude: + - os: macos-latest + version: '1.7' + - os: macos-latest + arch: 'x86' steps: - - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} show-versioninfo: true - - uses: julia-actions/cache@v1 + - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 with: diff --git a/Project.toml b/Project.toml index bbc21d7..a1b05dd 100644 --- a/Project.toml +++ b/Project.toml @@ -1,13 +1,10 @@ name = "MutatePlainDataArray" uuid = "3b0f367b-da20-4531-811a-c13cc92422b5" authors = ["Haoran Ni and contributors"] -version = "0.3.0" +version = "0.4.0" [compat] julia = "1.7" -[extras] -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[targets] -test = ["Test"] +[workspace] +projects = ["test"] diff --git a/README.md b/README.md index f01b32a..0941def 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ Enable mutating immutable plain data fields using `aref` wrapper, allowing mutating immutable plain data fields using the following syntax: ```julia - aref(v)[i].a.b._i._j[] = val + aref(v)[i].a.b[] = val ``` -The nested fields can be accessed using either the field name, or the field index prefixed with `_`. -Except for the wrapped vector, every field in the chain must be immutable. The final type to be mutated must be bits type. +The nested fields can be accessed using either the field name, or the field index with `getproperty`. +The wrapped vector must implement the strided arrays interface and must have `isbitstype` element type. Examples: -```julia +```julia-repl julia> using MutatePlainDataArray julia> a = [1, 2, 3]; @@ -25,28 +25,23 @@ julia> a 2 3 -julia> b = [(tup=(1, 2.5), s="a"), (tup=(2, 4.5), s="b")]; +julia> b = [(;tup=(1, 2.5), s=4), (;tup=(2, 4.5), s=5)]; -julia> aref(b)[1].tup._2[] = Inf +julia> aref(b)[1].tup.:2[] = Inf Inf julia> b -2-element Vector{NamedTuple{(:tup, :s), Tuple{Tuple{Int64, Float64}, String}}}: - (tup = (1, Inf), s = "a") - (tup = (2, 4.5), s = "b") +2-element Vector{@NamedTuple{tup::Tuple{Int64, Float64}, s::Int64}}: + (tup = (1, Inf), s = 4) + (tup = (2, 4.5), s = 5) -julia> aref(b)[2]._1._1[] *= 100 +julia> (aref(b)[2].:1).:1[] *= 100 200 julia> b -2-element Vector{NamedTuple{(:tup, :s), Tuple{Tuple{Int64, Float64}, String}}}: - (tup = (1, Inf), s = "a") - (tup = (200, 4.5), s = "b") - -julia> aref(b)[1].s[] = "invalid" -ERROR: The field type String (field s in NamedTuple{(:tup, :s), Tuple{Tuple{Int64, Float64}, String}}) is not immutable. -Stacktrace: - ... +2-element Vector{@NamedTuple{tup::Tuple{Int64, Float64}, s::Int64}}: + (tup = (1, Inf), s = 4) + (tup = (200, 4.5), s = 5) ``` The mutation provided by this package is diff --git a/benchmark/Manifest.toml b/benchmark/Manifest.toml deleted file mode 100644 index a3101b7..0000000 --- a/benchmark/Manifest.toml +++ /dev/null @@ -1,267 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -julia_version = "1.7.2" -manifest_format = "2.0" - -[[deps.ArgTools]] -uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" - -[[deps.Artifacts]] -uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" - -[[deps.BangBang]] -deps = ["Compat", "ConstructionBase", "Future", "InitialValues", "LinearAlgebra", "Requires", "Setfield", "Tables", "ZygoteRules"] -git-tree-sha1 = "b15a6bc52594f5e4a3b825858d1089618871bf9d" -uuid = "198e06fe-97b7-11e9-32a5-e1d131e6ad66" -version = "0.3.36" - -[[deps.Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" - -[[deps.BenchmarkTools]] -deps = ["JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"] -git-tree-sha1 = "4c10eee4af024676200bc7752e536f858c6b8f93" -uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -version = "1.3.1" - -[[deps.Compat]] -deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] -git-tree-sha1 = "b153278a25dd42c65abbf4e62344f9d22e59191b" -uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "3.43.0" - -[[deps.CompilerSupportLibraries_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" - -[[deps.ConstructionBase]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "f74e9d5388b8620b4cee35d4c5a618dd4dc547f4" -uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" -version = "1.3.0" - -[[deps.DataAPI]] -git-tree-sha1 = "fb5f5316dd3fd4c5e7c30a24d50643b73e37cd40" -uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" -version = "1.10.0" - -[[deps.DataValueInterfaces]] -git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" -uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" -version = "1.0.0" - -[[deps.Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" - -[[deps.DelimitedFiles]] -deps = ["Mmap"] -uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" - -[[deps.Distributed]] -deps = ["Random", "Serialization", "Sockets"] -uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" - -[[deps.Downloads]] -deps = ["ArgTools", "LibCURL", "NetworkOptions"] -uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" - -[[deps.Future]] -deps = ["Random"] -uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" - -[[deps.InitialValues]] -git-tree-sha1 = "4da0f88e9a39111c2fa3add390ab15f3a44f3ca3" -uuid = "22cec73e-a1b8-11e9-2c92-598750a2cf9c" -version = "0.3.1" - -[[deps.InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" - -[[deps.IteratorInterfaceExtensions]] -git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" -uuid = "82899510-4779-5014-852e-03e436cf321d" -version = "1.0.0" - -[[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e" -uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.3" - -[[deps.LibCURL]] -deps = ["LibCURL_jll", "MozillaCACerts_jll"] -uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" - -[[deps.LibCURL_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] -uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" - -[[deps.LibGit2]] -deps = ["Base64", "NetworkOptions", "Printf", "SHA"] -uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" - -[[deps.LibSSH2_jll]] -deps = ["Artifacts", "Libdl", "MbedTLS_jll"] -uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" - -[[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" - -[[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" - -[[deps.MbedTLS_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" - -[[deps.Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" - -[[deps.MozillaCACerts_jll]] -uuid = "14a3606d-f60d-562e-9121-12d972cd8159" - -[[deps.MutatePlainDataArray]] -deps = ["Compat"] -path = ".." -uuid = "3b0f367b-da20-4531-811a-c13cc92422b5" -version = "0.1.0" - -[[deps.NetworkOptions]] -uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" - -[[deps.OpenBLAS_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] -uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" - -[[deps.OrderedCollections]] -git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c" -uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.4.1" - -[[deps.Parsers]] -deps = ["Dates"] -git-tree-sha1 = "1285416549ccfcdf0c50d4997a94331e88d68413" -uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.3.1" - -[[deps.Pkg]] -deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] -uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" - -[[deps.Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" - -[[deps.Profile]] -deps = ["Printf"] -uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" - -[[deps.REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] -uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" - -[[deps.Random]] -deps = ["SHA", "Serialization"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" - -[[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" - -[[deps.Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" - -[[deps.Setfield]] -deps = ["ConstructionBase", "Future", "MacroTools", "Requires"] -git-tree-sha1 = "38d88503f695eb0301479bc9b0d4320b378bafe5" -uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46" -version = "0.8.2" - -[[deps.SharedArrays]] -deps = ["Distributed", "Mmap", "Random", "Serialization"] -uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" - -[[deps.Sockets]] -uuid = "6462fe0b-24de-5631-8697-dd941f90decc" - -[[deps.SparseArrays]] -deps = ["LinearAlgebra", "Random"] -uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - -[[deps.Statistics]] -deps = ["LinearAlgebra", "SparseArrays"] -uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" - -[[deps.TOML]] -deps = ["Dates"] -uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" - -[[deps.TableTraits]] -deps = ["IteratorInterfaceExtensions"] -git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" -uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" -version = "1.0.1" - -[[deps.Tables]] -deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits", "Test"] -git-tree-sha1 = "5ce79ce186cc678bbb5c5681ca3379d1ddae11a1" -uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -version = "1.7.0" - -[[deps.Tar]] -deps = ["ArgTools", "SHA"] -uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" - -[[deps.Test]] -deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[[deps.UUIDs]] -deps = ["Random", "SHA"] -uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" - -[[deps.Unicode]] -uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" - -[[deps.Zlib_jll]] -deps = ["Libdl"] -uuid = "83775a58-1f1d-513f-b197-d71354ab007a" - -[[deps.ZygoteRules]] -deps = ["MacroTools"] -git-tree-sha1 = "8c1a8e4dfacb1fd631745552c8db35d0deb09ea0" -uuid = "700de1a5-db45-46bc-99cf-38207098b444" -version = "0.2.2" - -[[deps.libblastrampoline_jll]] -deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] -uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" - -[[deps.nghttp2_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" - -[[deps.p7zip_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" diff --git a/benchmark/Project.toml b/benchmark/Project.toml index a95500f..793c965 100644 --- a/benchmark/Project.toml +++ b/benchmark/Project.toml @@ -3,3 +3,6 @@ BangBang = "198e06fe-97b7-11e9-32a5-e1d131e6ad66" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" MutatePlainDataArray = "3b0f367b-da20-4531-811a-c13cc92422b5" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" + +[sources] +MutatePlainDataArray = {path = ".."} diff --git a/benchmark/runbenchmarks.jl b/benchmark/runbenchmarks.jl index 9dd352d..85f46d8 100644 --- a/benchmark/runbenchmarks.jl +++ b/benchmark/runbenchmarks.jl @@ -17,9 +17,9 @@ struct BAI1 c::Int d::Int bab::BAB - m::Matrix{Float64} + m::Float64 end -BAI1() = BAI1(0, 0, 0, 0, BAB(), zeros(100, 100)) +BAI1() = BAI1(0, 0, 0, 0, BAB(), 100) function inbounds_setinner!_mutate(v, i) diff --git a/src/MutatePlainDataArray.jl b/src/MutatePlainDataArray.jl index 9b2df30..5ffaca2 100644 --- a/src/MutatePlainDataArray.jl +++ b/src/MutatePlainDataArray.jl @@ -6,9 +6,18 @@ export aref #--------------------------------------- # Common functions. #--------------------------------------- -unsafe_pointer(v) = pointer(v) -unsafe_pointer(v, i) = pointer(v, i) -unsafe_pointer(v, i1, i2, is...) = pointer(v, CartesianIndex((i1, i2, is...))) + +# A is a strided array +# This doesn't do bounds checking +function indices_to_byte_offset(A::AbstractArray{T,N}, ind::CartesianIndex{N}) where {T, N} + offset::Int = 0 + for d in 1:N + stride_in_bytes = stride(A, d) * Base.elsize(typeof(A)) + first_idx = first(axes(A, d)) + offset += (ind[d] - first_idx) * stride_in_bytes + end + offset +end function field_offset_type_impl(::Type{T}, ::Val{S}) where {T,S} if !isstructtype(T) @@ -18,16 +27,12 @@ function field_offset_type_impl(::Type{T}, ::Val{S}) where {T,S} fs = fieldnames(T) findex = findfirst(==(S), fs) if isnothing(findex) - # Cannot find the field name. - s = string(S) - if startswith(s, '_') - # Try to parse it as a number. - findex = tryparse(Int, s[2:end]) + if VERSION ≥ v"1.12" + throw(FieldError(T, S)) + else + error("Cannot find field $S in type $T.") end end - if isnothing(findex) - error("Cannot find field $S in type $T.") - end elseif S isa Integer findex = S else @@ -43,7 +48,7 @@ end Given struct `type` and the field name or index `S`, return the offset and type of the field. If `S` is an integer, it is represents the index of the field. -If `S` is a symbol, the function will first try to find the field with that name. If the name is not found, it will try to parse it with form `_index` where index is the field index. +If `S` is a symbol, the function will try to find the field with that name. """ @generated function field_offset_type(::Type{T}, ::Val{S}) where {T,S} offset, type = field_offset_type_impl(T, Val(S)) @@ -58,8 +63,8 @@ end struct ARef{T} r::T function ARef(v::AbstractArray{ET,N}) where {ET,N} - if ismutabletype(ET) - error("Element type $ET is not immutable.") + if !isbitstype(ET) + error("Element type $ET is not isbitstype.") end new{typeof(v)}(v) end @@ -70,11 +75,11 @@ end Wraps an array, allowing mutating immutable plain data fields using the following syntax: ```julia - aref(v)[i].a.b._i._j[] = val + aref(v)[i].a.b[] = val ``` -The nested fields can be accessed using either the field name, or the field index prefixed with `_`. -Except for the wrapped vector, every field in the chain must be immutable. The final type to be mutated must be bits type. +The nested fields can be accessed using either the field name, or the field index with `getproperty`. +The wrapped vector must implement the strided arrays interface and must have `isbitstype` element type. Examples: ```julia-repl @@ -89,73 +94,64 @@ julia> a 2 3 -julia> b = [(tup=(1, 2.5), s="a"), (tup=(2, 4.5), s="b")]; +julia> b = [(;tup=(1, 2.5), s=4), (;tup=(2, 4.5), s=5)]; -julia> aref(b)[1].tup._2[] = Inf +julia> aref(b)[1].tup.:2[] = Inf Inf julia> b -2-element Vector{NamedTuple{(:tup, :s), Tuple{Tuple{Int64, Float64}, String}}}: - (tup = (1, Inf), s = "a") - (tup = (2, 4.5), s = "b") +2-element Vector{@NamedTuple{tup::Tuple{Int64, Float64}, s::Int64}}: + (tup = (1, Inf), s = 4) + (tup = (2, 4.5), s = 5) -julia> aref(b)[2]._1._1[] *= 100 +julia> (aref(b)[2].:1).:1[] *= 100 200 julia> b -2-element Vector{NamedTuple{(:tup, :s), Tuple{Tuple{Int64, Float64}, String}}}: - (tup = (1, Inf), s = "a") - (tup = (200, 4.5), s = "b") - -julia> aref(b)[1].s[] = "invalid" -ERROR: The field type String (field s in NamedTuple{(:tup, :s), Tuple{Tuple{Int64, Float64}, String}}) is not immutable. -Stacktrace: - ... +2-element Vector{@NamedTuple{tup::Tuple{Int64, Float64}, s::Int64}}: + (tup = (1, Inf), s = 4) + (tup = (200, 4.5), s = 5) ``` """ aref(v::AbstractArray) = ARef(v) -struct ElementRef{T, ET} - # Keep the reference to original vector to keep it alive. - r::T - # The actual pointer to the element. - p::Ptr{ET} +struct ElementRef{T, RET, ET} + # Keep the cconvert of the original vector to keep it alive. + rcconv::T + # The offset of pointer to the element. + offset::Int end - Base.@propagate_inbounds function Base.getindex(v::ARef{T}, indices...) where T - @boundscheck checkbounds(v.r, indices...) - ElementRef(v.r, unsafe_pointer(v.r, indices...)) + ind = CartesianIndices(v.r)[indices...]::CartesianIndex + rcconv = Base.cconvert(Ptr{Base.eltype(v.r)}, v.r) + ElementRef{typeof(rcconv), Base.eltype(v.r), Base.eltype(v.r)}(rcconv, indices_to_byte_offset(v.r, ind)) end -function Base.getindex(r::ElementRef{T,ET}) where {T, ET} - if isbitstype(ET) - rr = getfield(r, :r) - GC.@preserve rr unsafe_load(getfield(r, :p)) - else - error("Type $ET is not bits type.") +function Base.getindex(r::ElementRef{T,RET,ET}) where {T, RET, ET} + rcconv = getfield(r, :rcconv) + GC.@preserve rcconv begin + p = Ptr{ET}(Base.unsafe_convert(Ptr{RET}, rcconv)) + p += getfield(r, :offset) + unsafe_load(p) end end -function Base.setindex!(r::ElementRef{T,ET}, val) where {T, ET} - if isbitstype(ET) - rr = getfield(r, :r) - GC.@preserve rr unsafe_store!(getfield(r, :p), convert(ET, val)) - else - error("Type $ET is not bits type.") +function Base.setindex!(r::ElementRef{T,RET,ET}, val) where {T, RET, ET} + rcconv = getfield(r, :rcconv) + GC.@preserve rcconv begin + p = Ptr{ET}(Base.unsafe_convert(Ptr{RET}, rcconv)) + p += getfield(r, :offset) + unsafe_store!(p, convert(ET, val)) end end -@generated function chain_offsettype(::ElementRef{T,ET}, ::Val{S}) where {T, ET, S} +function Base.getproperty(r::ElementRef{T,RET,ET}, S::Symbol) where {T, RET, ET} offset, type = field_offset_type(ET, Val(S)) - if ismutabletype(type) - error("The field type $type (field $S in $ET) is not immutable.") - end - :(($offset, $type)) + ElementRef{T, RET, type}(getfield(r, :rcconv), getfield(r, :offset) + offset) end - -function Base.getproperty(r::ElementRef{T,ET}, name::Symbol) where {T, ET} - offset, type = chain_offsettype(r, Val(name)) - ElementRef(getfield(r, :r), Base.unsafe_convert(Ptr{type}, getfield(r, :p)) + offset) +function Base.getproperty(r::ElementRef{T,RET,ET}, S::Int) where {T, RET, ET} + offset, type = field_offset_type(ET, Val(S)) + ElementRef{T, RET, type}(getfield(r, :rcconv), getfield(r, :offset) + offset) end "This function should not be called." function Base.setproperty!(::ElementRef{T,ET}, ::Symbol, x) where {T, ET} @@ -168,10 +164,10 @@ end #--------------------------------------- atype(::ARef{T}) where T = T atype(::Type{ARef{T}}) where T = T -atype(::ElementRef{T,ET}) where {T,ET} = T -atype(::Type{ElementRef{T,ET}}) where {T,ET} = T -eltype(::ElementRef{T,ET}) where {T,ET} = ET -eltype(::Type{ElementRef{T,ET}}) where {T,ET} = ET +atype(::ElementRef{T,RET,ET}) where {T,RET,ET} = T +atype(::Type{ElementRef{T,RET,ET}}) where {T,RET,ET} = T +eltype(::ElementRef{T,RET,ET}) where {T,RET,ET} = ET +eltype(::Type{ElementRef{T,RET,ET}}) where {T,RET,ET} = ET end diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 0000000..8e687a0 --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,3 @@ +[deps] +MutatePlainDataArray = "3b0f367b-da20-4531-811a-c13cc92422b5" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/runtests.jl b/test/runtests.jl index c2f72ee..29b01ac 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -26,6 +26,10 @@ mutable struct TBM a1::TAB a2::TAB end +struct TIB + x::Int + a::TAB +end struct TC _2 ::Val{1} @@ -34,6 +38,13 @@ struct TC end TC() = TC(Val(1), Val(2), Val(3)) +struct TAN + x::Int + s::String + TAN(x) = new(x) + TAN(x, y) = new(x, y) +end + @testset "MutatePlainDataArray.jl" begin @test isbitstype(TAB) @test !isbitstype(TAI) && !ismutabletype(TAI) @@ -49,6 +60,7 @@ TC() = TC(Val(1), Val(2), Val(3)) v3 = [TAI(1, "a"), TAI(2, "b")] v4 = [TAM(1, 2), TAM(3, 4)] v5 = [TAB(1, 2) TAB(3, 4); TAB(5, 6) TAB(7, 8)] + v6 = [TAN(1, "a"), TAN(2, "b")] t1 = (1, 2) t2 = (TAB(1, 2), TAB(3, 4)) t3 = (TAI(1, "a"), TAI(2, "b")) @@ -56,9 +68,10 @@ TC() = TC(Val(1), Val(2), Val(3)) @test aref(v1) isa Any @test aref(v2) isa Any - @test aref(v3) isa Any + @test_throws ErrorException aref(v3) @test_throws ErrorException aref(v4) @test aref(v5) isa Any + @test_throws ErrorException aref(v6) @test_throws MethodError aref(t1) @test_throws MethodError aref(t2) @test_throws MethodError aref(t3) @@ -70,14 +83,14 @@ TC() = TC(Val(1), Val(2), Val(3)) end @testset "aref indexing" begin - v1 = [TAI(1, "a"), TAI(2, "b")] + v1 = [TAB(1, 2), TAB(3, 4)] r1 = aref(v1) @test r1[1] isa MutatePlainDataArray.ElementRef @test_throws BoundsError r1[0] @test_throws Exception r1[1] = TAI(3, "c") - v2 = zeros(5, 5) + v2 = rand(5, 5) # Currently, multi-indexing array directly is not supported. view2 = view(v2, 1:2:5, :) r2 = aref(v2) @@ -90,79 +103,76 @@ TC() = TC(Val(1), Val(2), Val(3)) @test rview2[3,4] isa MutatePlainDataArray.ElementRef @test_throws BoundsError rview2[4,4] GC.@preserve v2 begin - @test getfield(rview2[3,5], :p) == getfield(r2[25], :p) - @test getfield(r2[1], :p) + sizeof(Float64) * 2 == getfield(rview2[2,1], :p) + @test rview2[3,5][] === r2[25][] + @test r2[3][] === rview2[2,1][] end end @testset "Field type chaining" begin - v1 = [TBI(1, TAI(2, "a")), TBI(3, TAI(4, "b"))] + v1 = [TIB(1 , TAB(3, 4)), TIB(5, TAB(7, 8))] r1 = aref(v1) - - @test MutatePlainDataArray.atype(r1) == Vector{TBI} e1 = r1[1] - @test MutatePlainDataArray.atype(e1) == Vector{TBI} - @test MutatePlainDataArray.eltype(e1) == TBI + + if VERSION ≥ v"1.12" + @test_throws FieldError e1.foo + else + @test_throws ErrorException e1.foo + end let e12 = e1.x - @test MutatePlainDataArray.atype(e12) == Vector{TBI} - @test MutatePlainDataArray.eltype(e12) == Int @test_throws ErrorException e12.x - @test_throws ErrorException e12._1 + @test_throws ErrorException e12.:1 end - let e12 = e1._1 + let e12 = e1.:1 @test e12 == e1.x end let e12 = e1.a - @test MutatePlainDataArray.atype(e12) == Vector{TBI} - @test MutatePlainDataArray.eltype(e12) == TAI + @test MutatePlainDataArray.eltype(e12) == TAB let e13 = e12.x - @test MutatePlainDataArray.atype(e13) == Vector{TBI} @test MutatePlainDataArray.eltype(e13) == Int end - # Cannot chain a mutable type. - @test_throws ErrorException e12.a - @test_throws ErrorException e12._2 end - @test_throws BoundsError e1._0 - @test_throws BoundsError e1._3 + @test_throws BoundsError e1.:0 + @test_throws BoundsError e1.:3 v2 = [TC(), TC()] r2 = aref(v2) e2 = r2[2] - @test MutatePlainDataArray.atype(e2) == Vector{TC} @test MutatePlainDataArray.eltype(e2) == TC - @test MutatePlainDataArray.eltype(e2._1) == Val{1} + @test MutatePlainDataArray.eltype(e2.:1) == Val{1} # "_2" is the name of the first field. @test MutatePlainDataArray.eltype(e2._2) == Val{1} - @test MutatePlainDataArray.eltype(e2._3) == Val{3} + @test MutatePlainDataArray.eltype(e2.:3) == Val{3} @test MutatePlainDataArray.eltype(e2.__1) == Val{2} end @testset "Field mutation" begin - v1 = [TBI(1, TAI(2, "a")), TBI(3, TAI(4, "b"))] + v1 = [TIB(1 , TAB(3, 4)), TIB(5, TAB(7, 8))] r1 = aref(v1) - @test_throws ErrorException r1[1][] = TBI(5, TAI(6, "c")) - @test_throws ErrorException r1[1].a[] = TAI(6, "c") - @test_throws ErrorException r1[1].a.s[] = "c" + r1[1][] = TIB(5 , TAB(6, 7)) + @test v1 == [TIB(5 , TAB(6, 7)), TIB(5, TAB(7, 8))] + r1[1].a[] = TAB(6, 8) + @test v1 == [TIB(5 , TAB(6, 8)), TIB(5, TAB(7, 8))] + r1[1].a.y[] = 10.5 + @test v1 == [TIB(5 , TAB(6, 10.5)), TIB(5, TAB(7, 8))] @test_throws ErrorException r1[1].x = -1 @test_throws ErrorException r1[1].a.x = -1 r1[1].x[] = 50 - @test v1 == [TBI(50, TAI(2, "a")), TBI(3, TAI(4, "b"))] + @test v1 == [TIB(50 , TAB(6, 10.5)), TIB(5, TAB(7, 8))] r1[1].x[] *= 2 - @test v1 == [TBI(100, TAI(2, "a")), TBI(3, TAI(4, "b"))] + @test v1 == [TIB(100 , TAB(6, 10.5)), TIB(5, TAB(7, 8))] r1[2].x[] <<= 1 - @test v1 == [TBI(100, TAI(2, "a")), TBI(6, TAI(4, "b"))] + @test v1 == [TIB(100 , TAB(6, 10.5)), TIB(10, TAB(7, 8))] r1[1].a.x[] = 200 - @test v1 == [TBI(100, TAI(200, "a")), TBI(6, TAI(4, "b"))] + @test v1 == [TIB(100 , TAB(200, 10.5)), TIB(10, TAB(7, 8))] r1[1].a.x[] /= 2 - @test v1 == [TBI(100, TAI(100, "a")), TBI(6, TAI(4, "b"))] + @test v1 == [TIB(100 , TAB(100, 10.5)), TIB(10, TAB(7, 8))] r1[2].a.x[] %= 3 - @test v1 == [TBI(100, TAI(100, "a")), TBI(6, TAI(1, "b"))] + @test v1 == [TIB(100 , TAB(100, 10.5)), TIB(10, TAB(1, 8))] @test_throws MethodError r1[1].a.x[] = "some string" end end