Skip to content

Commit c77be4c

Browse files
authored
Merge pull request #46 from CSBiology/deepCopy
Deep copy improvements
2 parents 073da2d + fd57ba5 commit c77be4c

File tree

17 files changed

+3481
-68
lines changed

17 files changed

+3481
-68
lines changed

src/DynamicObj/DynObj.fs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,34 @@ module DynObj =
216216
/// Prints a formatted string containing all static and dynamic properties of the given DynamicObj
217217
/// </summary>
218218
/// <param name="dynObj">The DynamicObj for which to print a formatted string for</param>
219-
let print (dynObj:DynamicObj) = printfn "%s" (dynObj |> format)
219+
let print (dynObj:DynamicObj) = printfn "%s" (dynObj |> format)
220+
221+
/// <summary>
222+
/// function to deep copy a boxed object (if possible)
223+
///
224+
/// The following cases are handled (in this precedence):
225+
///
226+
/// - Basic F# types (bool, byte, sbyte, int16, uint16, int, uint, int64, uint64, nativeint, unativeint, float, float32, char, string, unit, decimal)
227+
///
228+
/// - ResizeArrays and Dictionaries containing any combination of basic F# types
229+
///
230+
/// - Dictionaries containing DynamicObj as keys or values in any combination with DynamicObj or basic F# types as keys or values
231+
///
232+
/// - array&lt;DynamicObj&gt;, list&lt;DynamicObj&gt;, ResizeArray&lt;DynamicObj&gt;: These collections of DynamicObj are copied as a new collection with recursively deep copied elements.
233+
///
234+
/// - System.ICloneable: If the property implements ICloneable, the Clone() method is called on the property.
235+
///
236+
/// - DynamicObj (and derived classes): properties that are themselves DynamicObj instances are deep copied recursively.
237+
/// if a derived class has static properties (e.g. instance properties), these will be copied as dynamic properties on the new instance.
238+
///
239+
/// Note on Classes that inherit from DynamicObj:
240+
///
241+
/// Classes that inherit from DynamicObj will match the `DynamicObj` typecheck if they do not implement ICloneable.
242+
/// The deep copied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
243+
/// It should be possible to 'recover' the original type by checking if the needed properties exist as dynamic properties,
244+
/// and then passing them to the class constructor if needed.
245+
/// </summary>
246+
/// <param name="o">The object that should be deep copied</param>
247+
/// <param name="includeInstanceProperties">Whether to include instance properties (= 'static' properties on the class) as dynamic properties on the new instance for matched DynamicObj.</param>
248+
let tryDeepCopyObj (includeInstanceProperties:bool) (o:DynamicObj) =
249+
CopyUtils.tryDeepCopyObj(o, includeInstanceProperties)

src/DynamicObj/DynamicObj.fs

Lines changed: 582 additions & 23 deletions
Large diffs are not rendered by default.

src/DynamicObj/FableJS.fs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,11 @@ module FableJS =
168168
let cloneICloneable (o:obj) : obj =
169169
jsNative
170170

171+
module Dictionaries =
172+
[<Emit("""$0 instanceof Map""")>]
173+
let isMap (o:obj) : bool =
174+
jsNative
175+
[<Emit("""$0 instanceof Dictionary""")>]
176+
let isDict (o:obj) : bool =
177+
jsNative
171178
#endif

src/DynamicObj/FablePy.fs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,4 +220,10 @@ module FablePy =
220220
[<Emit("""$0.System_ICloneable_Clone()""")>]
221221
let cloneICloneable (o:obj) : obj =
222222
nativeOnly
223+
224+
module Dictionaries =
225+
[<Emit("""isinstance($0, dict)""")>]
226+
let isDict (o:obj) : bool =
227+
nativeOnly
228+
223229
#endif

src/DynamicObj/Playground.fsx

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,10 @@
1313
open Fable.Pyxpecto
1414
open DynamicObj
1515

16-
type T(dyn:string, stat:string) as this=
17-
inherit DynamicObj()
16+
let r1 = ResizeArray([1; 2])
17+
let r2 = ResizeArray([1; 2])
18+
let r3 = r1
1819

19-
do
20-
this.SetProperty("Dyn", dyn)
21-
22-
member this.Stat = stat
23-
24-
let first = T("dyn1", "stat1")
25-
let second = T("dyn2", "stat2")
26-
27-
let _ = second.ShallowCopyDynamicPropertiesTo(first)
28-
29-
first |> DynObj.print
30-
second |> DynObj.print
20+
printfn "%A" (LanguagePrimitives.PhysicalEquality r1 r2)
21+
printfn "%A" (LanguagePrimitives.PhysicalEquality r2 r2)
22+
printfn "%A" (LanguagePrimitives.PhysicalEquality r3 r1)

tests/CSharpTests/CSharpTests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net8.0</TargetFramework>
55
<IsPackable>false</IsPackable>
66
</PropertyGroup>
77

tests/DynamicObject.Tests/CopyUtils.tryDeepCopyObj/Dictionaries.fs

Lines changed: 2563 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
module DeepCopyDynamicObj
2+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
module DeepCopyDynamicObjCollections
2+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
module DeepCopyICloneable
2+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module CopyUtils.Tests
2+
3+
open System
4+
open Fable.Pyxpecto
5+
open DynamicObj
6+
open Fable.Core
7+
8+
let main = testList "CopyUtils.tryDeepCopyObj" [
9+
DeepCopyPrimitives.tests_DeepCopyPrimitives
10+
DeepCopyResizeArrays.tests_DeepCopyResizeArrays
11+
DeepCopyDictionaries.tests_DeepCopyDictionaries
12+
]
13+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
module DeepCopyPrimitives
2+
3+
open System
4+
open Fable.Pyxpecto
5+
open DynamicObj
6+
open Fable.Core
7+
open TestUtils
8+
9+
let tests_DeepCopyPrimitives = testList "Primitives" [
10+
testCase "bool" <| fun _ ->
11+
let original, copy = constructDeepCopiedObj true
12+
Expect.equal copy original "Expected values of copy and original to be equal"
13+
testCase "byte" <| fun _ ->
14+
let original, copy = constructDeepCopiedObj 1uy
15+
Expect.equal copy original "Expected values of copy and original to be equal"
16+
testCase "sbyte" <| fun _ ->
17+
let original, copy = constructDeepCopiedObj 1y
18+
Expect.equal copy original "Expected values of copy and original to be equal"
19+
testCase "int16" <| fun _ ->
20+
let original, copy = constructDeepCopiedObj 1s
21+
Expect.equal copy original "Expected values of copy and original to be equal"
22+
testCase "uint16" <| fun _ ->
23+
let original, copy = constructDeepCopiedObj 1us
24+
Expect.equal copy original "Expected values of copy and original to be equal"
25+
testCase "int" <| fun _ ->
26+
let original, copy = constructDeepCopiedObj 1
27+
Expect.equal copy original "Expected values of copy and original to be equal"
28+
testCase "uint" <| fun _ ->
29+
let original, copy = constructDeepCopiedObj 1u
30+
Expect.equal copy original "Expected values of copy and original to be equal"
31+
testCase "int64" <| fun _ ->
32+
let original, copy = constructDeepCopiedObj 1L
33+
Expect.equal copy original "Expected values of copy and original to be equal"
34+
testCase "uint64" <| fun _ ->
35+
let original, copy = constructDeepCopiedObj 1uL
36+
Expect.equal copy original "Expected values of copy and original to be equal"
37+
#if !FABLE_COMPILER
38+
testCase "nativeint" <| fun _ ->
39+
let original, copy = constructDeepCopiedObj (System.IntPtr(1))
40+
Expect.equal copy original "Expected values of copy and original to be equal"
41+
testCase "unativeint" <| fun _ ->
42+
let original, copy = constructDeepCopiedObj (System.UIntPtr(1u))
43+
Expect.equal copy original "Expected values of copy and original to be equal"
44+
#endif
45+
testCase "float" <| fun _ ->
46+
let original, copy = constructDeepCopiedObj 1.0
47+
Expect.equal copy original "Expected values of copy and original to be equal"
48+
testCase "float32" <| fun _ ->
49+
let original, copy = constructDeepCopiedObj 1.0f
50+
Expect.equal copy original "Expected values of copy and original to be equal"
51+
testCase "char" <| fun _ ->
52+
let original, copy = constructDeepCopiedObj 'A'
53+
Expect.equal copy original "Expected values of copy and original to be equal"
54+
testCase "string" <| fun _ ->
55+
let original, copy = constructDeepCopiedObj "Hi"
56+
Expect.equal copy original "Expected values of copy and original to be equal"
57+
testCase "unit" <| fun _ ->
58+
let original, copy = constructDeepCopiedObj ()
59+
Expect.equal copy original "Expected values of copy and original to be equal"
60+
]
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
module DeepCopyResizeArrays
2+
3+
open System
4+
open Fable.Pyxpecto
5+
open DynamicObj
6+
open Fable.Core
7+
open TestUtils
8+
9+
let tests_DeepCopyResizeArrays = testList "ResizeArrays" [
10+
testCase "bool" <| fun _ ->
11+
let arr = ResizeArray([true; false])
12+
let original, copy = constructDeepCopiedObj arr
13+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
14+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
15+
arr[0] <- false
16+
Expect.sequenceEqual original (ResizeArray([false; false])) "Original schould have been mutated"
17+
Expect.sequenceEqual copy (ResizeArray([true; false])) "Clone should not be affected by original mutation"
18+
testCase "byte" <| fun _ ->
19+
let arr = ResizeArray([1uy; 2uy])
20+
let original, copy = constructDeepCopiedObj arr
21+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
22+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
23+
arr[0] <- 2uy
24+
Expect.sequenceEqual original (ResizeArray([2uy; 2uy])) "Original schould have been mutated"
25+
Expect.sequenceEqual copy (ResizeArray([1uy; 2uy])) "Clone should not be affected by original mutation"
26+
testCase "sbyte" <| fun _ ->
27+
let arr = ResizeArray([1y; 2y])
28+
let original, copy = constructDeepCopiedObj arr
29+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
30+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
31+
arr[0] <- 2y
32+
Expect.sequenceEqual original (ResizeArray([2y; 2y])) "Original schould have been mutated"
33+
Expect.sequenceEqual copy (ResizeArray([1y; 2y])) "Clone should not be affected by original mutation"
34+
testCase "int16" <| fun _ ->
35+
let arr = ResizeArray([1s; 2s])
36+
let original, copy = constructDeepCopiedObj arr
37+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
38+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
39+
arr[0] <- 2s
40+
Expect.sequenceEqual original (ResizeArray([2s; 2s])) "Original schould have been mutated"
41+
Expect.sequenceEqual copy (ResizeArray([1s; 2s])) "Clone should not be affected by original mutation"
42+
testCase "uint16" <| fun _ ->
43+
let arr = ResizeArray([1us; 2us])
44+
let original, copy = constructDeepCopiedObj arr
45+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
46+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
47+
arr[0] <- 2us
48+
Expect.sequenceEqual original (ResizeArray([2us; 2us])) "Original schould have been mutated"
49+
Expect.sequenceEqual copy (ResizeArray([1us; 2us])) "Clone should not be affected by original mutation"
50+
testCase "int" <| fun _ ->
51+
let arr = ResizeArray([1; 2])
52+
let original, copy = constructDeepCopiedObj arr
53+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
54+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
55+
arr[0] <- 2
56+
Expect.sequenceEqual original (ResizeArray([2; 2])) "Original schould have been mutated"
57+
Expect.sequenceEqual copy (ResizeArray([1; 2])) "Clone should not be affected by original mutation"
58+
testCase "uint" <| fun _ ->
59+
let arr = ResizeArray([1u; 2u])
60+
let original, copy = constructDeepCopiedObj arr
61+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
62+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
63+
arr[0] <- 2u
64+
Expect.sequenceEqual original (ResizeArray([2u; 2u])) "Original schould have been mutated"
65+
Expect.sequenceEqual copy (ResizeArray([1u; 2u])) "Clone should not be affected by original mutation"
66+
testCase "int64" <| fun _ ->
67+
let arr = ResizeArray([1L; 2L])
68+
let original, copy = constructDeepCopiedObj arr
69+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
70+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
71+
arr[0] <- 2L
72+
Expect.sequenceEqual original (ResizeArray([2L; 2L])) "Original schould have been mutated"
73+
Expect.sequenceEqual copy (ResizeArray([1L; 2L])) "Clone should not be affected by original mutation"
74+
testCase "uint64" <| fun _ ->
75+
let arr = ResizeArray([1uL; 2uL])
76+
let original, copy = constructDeepCopiedObj arr
77+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
78+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
79+
arr[0] <- 2uL
80+
Expect.sequenceEqual original (ResizeArray([2uL; 2uL])) "Original schould have been mutated"
81+
Expect.sequenceEqual copy (ResizeArray([1uL; 2uL])) "Clone should not be affected by original mutation"
82+
testCase "float" <| fun _ ->
83+
let arr = ResizeArray([1.0; 2.0])
84+
let original, copy = constructDeepCopiedObj arr
85+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
86+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
87+
arr[0] <- 2.0
88+
Expect.sequenceEqual original (ResizeArray([2.0; 2.0])) "Original schould have been mutated"
89+
Expect.sequenceEqual copy (ResizeArray([1.0; 2.0])) "Clone should not be affected by original mutation"
90+
testCase "float32" <| fun _ ->
91+
let arr = ResizeArray([1.0f; 2.0f])
92+
let original, copy = constructDeepCopiedObj arr
93+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
94+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
95+
arr[0] <- 2.0f
96+
Expect.sequenceEqual original (ResizeArray([2.0f; 2.0f])) "Original schould have been mutated"
97+
Expect.sequenceEqual copy (ResizeArray([1.0f; 2.0f])) "Clone should not be affected by original mutation"
98+
testCase "char" <| fun _ ->
99+
let arr = ResizeArray(['A'; 'B'])
100+
let original, copy = constructDeepCopiedObj arr
101+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
102+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
103+
arr[0] <- 'B'
104+
Expect.sequenceEqual original (ResizeArray(['B'; 'B'])) "Original schould have been mutated"
105+
Expect.sequenceEqual copy (ResizeArray(['A'; 'B'])) "Clone should not be affected by original mutation"
106+
testCase "string" <| fun _ ->
107+
let arr = ResizeArray(["Hi"; "Bye"])
108+
let original, copy = constructDeepCopiedObj arr
109+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
110+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
111+
arr[0] <- "Bye"
112+
Expect.sequenceEqual original (ResizeArray(["Bye"; "Bye"])) "Original schould have been mutated"
113+
Expect.sequenceEqual copy (ResizeArray(["Hi"; "Bye"])) "Clone should not be affected by original mutation"
114+
testCase "unit" <| fun _ ->
115+
let arr = ResizeArray([(); ()])
116+
let original, copy = constructDeepCopiedObj arr
117+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
118+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
119+
// transpilation fun
120+
let arr2 = ResizeArray([()])
121+
arr.Add(arr2[0])
122+
Expect.sequenceEqual original (ResizeArray([(); (); ()])) "Original schould have been mutated"
123+
Expect.sequenceEqual copy (ResizeArray([(); ()])) "Clone should not be affected by original mutation"
124+
125+
// some cases are not transpilable
126+
127+
#if !FABLE_COMPILER_PYTHON
128+
testCase "decimal" <| fun _ ->
129+
let arr = ResizeArray([1.0M; 2.0M])
130+
let original, copy = constructDeepCopiedObj arr
131+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
132+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
133+
arr[0] <- 2.0M
134+
Expect.sequenceEqual original (ResizeArray([2.0M; 2.0M])) "Original schould have been mutated"
135+
Expect.sequenceEqual copy (ResizeArray([1.0M; 2.0M])) "Clone should not be affected by original mutation"
136+
#endif
137+
138+
#if !FABLE_COMPILER
139+
testCase "nativeint" <| fun _ ->
140+
let original, copy = constructDeepCopiedObj (ResizeArray([System.IntPtr(1); System.IntPtr(2)]))
141+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
142+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
143+
testCase "unativeint" <| fun _ ->
144+
let original, copy = constructDeepCopiedObj (ResizeArray([System.UIntPtr(1u); System.UIntPtr(2u)]))
145+
Expect.sequenceEqual copy original "Expected values of copy and original to be equal"
146+
Expect.notReferenceEqual copy original "Expected values of copy and original to be not reference equal"
147+
#endif
148+
]

tests/DynamicObject.Tests/DynamicObject.Tests.fsproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99

1010
<ItemGroup>
1111
<Compile Include="TestUtils.fs" />
12+
<Compile Include="CopyUtils.tryDeepCopyObj\Primitives.fs" />
13+
<Compile Include="CopyUtils.tryDeepCopyObj\ResizeArrays.fs" />
14+
<Compile Include="CopyUtils.tryDeepCopyObj\Dictionaries.fs" />
15+
<Compile Include="CopyUtils.tryDeepCopyObj\DynamicObjCollections.fs" />
16+
<Compile Include="CopyUtils.tryDeepCopyObj\ICloneable.fs" />
17+
<Compile Include="CopyUtils.tryDeepCopyObj\DynamicObj.fs" />
18+
<Compile Include="CopyUtils.tryDeepCopyObj\Main.fs" />
1219
<Compile Include="DynamicObj\RemoveProperty.fs" />
1320
<Compile Include="DynamicObj\SetProperty.fs" />
1421
<Compile Include="DynamicObj\GetHashcode.fs" />

tests/DynamicObject.Tests/Main.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ open Fable.Pyxpecto
44

55
let all = testSequenced <| testList "DynamicObj" [
66
ReflectionUtils.Tests.main
7+
CopyUtils.Tests.main
78
DynamicObj.Tests.main
89
DynObj.Tests.main
910
Inheritance.Tests.main

0 commit comments

Comments
 (0)