Skip to content

Commit fd4748b

Browse files
committed
feat: Implement recursivecopy for structs
Only supports mutable structs, and has to have extra logic to dispatch on basic types...
1 parent 5493a98 commit fd4748b

File tree

1 file changed

+58
-5
lines changed

1 file changed

+58
-5
lines changed

src/utils.jl

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,32 @@ unrolled_foreach!(f, ::Tuple{}) = nothing
33

44
"""
55
```julia
6-
recursivecopy(a::Union{AbstractArray{T, N}, AbstractVectorOfArray{T, N}})
6+
recursivecopy(a)
77
```
88
99
A recursive `copy` function. Acts like a `deepcopy` on arrays of arrays, but
10-
like `copy` on arrays of scalars.
11-
"""
12-
function recursivecopy(a)
13-
deepcopy(a)
10+
like `copy` on arrays of scalars. For struct types, recursively copies each
11+
field, creating new instances while preserving the struct type.
12+
13+
## Examples
14+
15+
```julia
16+
# Basic array copying
17+
arr = [[1, 2], [3, 4]]
18+
copied = recursivecopy(arr) # New arrays at each level
19+
20+
# Struct copying
21+
struct MyStruct
22+
data::Vector{Float64}
23+
metadata::String
1424
end
25+
original = MyStruct([1.0, 2.0], "test")
26+
copied = recursivecopy(original) # New struct with new vector
27+
```
28+
29+
This is particularly useful for SciML ecosystem objects like `NonlinearSolution`
30+
where you need to copy the entire solution structure including all internal arrays.
31+
"""
1532
function recursivecopy(a::Union{StaticArraysCore.SVector, StaticArraysCore.SMatrix,
1633
StaticArraysCore.SArray, Number})
1734
copy(a)
@@ -35,6 +52,42 @@ function recursivecopy(a::AbstractVectorOfArray)
3552
return b
3653
end
3754

55+
function _is_basic_julia_type(T)
56+
# Check if this is a built-in Julia type that we should not handle as a user struct
57+
# We check the module to identify Core/Base types vs user-defined types
58+
mod = Base.parentmodule(T)
59+
return T <: AbstractString || T <: Number || T <: Symbol || T <: Tuple ||
60+
T <: UnitRange || T <: StepRange || T <: Regex ||
61+
T === Nothing || T === Missing ||
62+
mod === Core || mod === Base
63+
end
64+
65+
function recursivecopy(s::T) where {T}
66+
# Only handle user-defined immutable structs. Many basic Julia types (String, Symbol,
67+
# Tuple, etc.) are technically structs but should use copy() or return as-is.
68+
if Base.isstructtype(T) && !_is_basic_julia_type(T)
69+
if Base.ismutabletype(T)
70+
error("recursivecopy for mutable structs is not currently implemented. Use deepcopy instead.")
71+
else
72+
# Handle immutable structs only
73+
field_values = ntuple(fieldcount(T)) do i
74+
field_value = getfield(s, i)
75+
recursivecopy(field_value)
76+
end
77+
return T(field_values...)
78+
end
79+
elseif _is_basic_julia_type(T)
80+
# For basic Julia types, use copy if available, otherwise return as-is (for immutable types)
81+
if hasmethod(copy, Tuple{T})
82+
return copy(s)
83+
else
84+
return s # Immutable basic types like Symbol, Nothing, Missing don't need copying
85+
end
86+
else
87+
deepcopy(s)
88+
end
89+
end
90+
3891
"""
3992
```julia
4093
recursivecopy!(b::AbstractArray{T, N}, a::AbstractArray{T, N})

0 commit comments

Comments
 (0)