From b6fc37ed21f0e64000612f0733ea05b15c79448f Mon Sep 17 00:00:00 2001 From: HLWeil Date: Fri, 31 Jan 2025 17:54:29 +0100 Subject: [PATCH 1/3] start testing typed view on generic object --- src/DynamicObj/DynamicObj.fs | 2 +- src/DynamicObj/FableJS.fs | 4 + src/DynamicObj/FablePy.fs | 3 + .../DynamicObject.Tests.fsproj | 3 +- .../{Inheritance.fs => InheritanceStatic.fs} | 2 +- tests/DynamicObject.Tests/InheritanceView.fs | 93 +++++++++++++++++++ tests/DynamicObject.Tests/Main.fs | 1 + 7 files changed, 105 insertions(+), 3 deletions(-) rename tests/DynamicObject.Tests/{Inheritance.fs => InheritanceStatic.fs} (99%) create mode 100644 tests/DynamicObject.Tests/InheritanceView.fs diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 7c19ec8..02dadbc 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -21,7 +21,7 @@ type DynamicObj() = /// member this.Properties with get() = properties - and internal set(value) = properties <- value + and set(value) = properties <- value /// /// Creates a new DynamicObj from a Dictionary containing dynamic properties. diff --git a/src/DynamicObj/FableJS.fs b/src/DynamicObj/FableJS.fs index 16c384e..857a106 100644 --- a/src/DynamicObj/FableJS.fs +++ b/src/DynamicObj/FableJS.fs @@ -158,6 +158,10 @@ module FableJS = getPropertyHelpers o |> Array.map (fun h -> h.Name) + [] + let baseObjectOn (o:obj) : unit = jsNative + + module Interfaces = [] diff --git a/src/DynamicObj/FablePy.fs b/src/DynamicObj/FablePy.fs index ceb22a7..4a63bdf 100644 --- a/src/DynamicObj/FablePy.fs +++ b/src/DynamicObj/FablePy.fs @@ -211,6 +211,9 @@ module FablePy = getPropertyHelpers o |> Array.map (fun h -> h.Name) + [] + let baseObjectOn (o:obj) : unit = nativeOnly + module Interfaces = [] diff --git a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj index 30c418e..a16396a 100644 --- a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj +++ b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj @@ -34,7 +34,8 @@ - + + diff --git a/tests/DynamicObject.Tests/Inheritance.fs b/tests/DynamicObject.Tests/InheritanceStatic.fs similarity index 99% rename from tests/DynamicObject.Tests/Inheritance.fs rename to tests/DynamicObject.Tests/InheritanceStatic.fs index e497578..c859e0a 100644 --- a/tests/DynamicObject.Tests/Inheritance.fs +++ b/tests/DynamicObject.Tests/InheritanceStatic.fs @@ -150,7 +150,7 @@ let tests_print = testList "Print" [ Expect.isTrue print "Print failed for issue 14" ] -let main = testList "Inheritance" [ +let main = testList "InheritanceStatic" [ tests_set tests_remove tests_getProperties diff --git a/tests/DynamicObject.Tests/InheritanceView.fs b/tests/DynamicObject.Tests/InheritanceView.fs new file mode 100644 index 0000000..e572dc2 --- /dev/null +++ b/tests/DynamicObject.Tests/InheritanceView.fs @@ -0,0 +1,93 @@ +module InheritanceView.Tests + +open System +open Fable.Pyxpecto +open DynamicObj +open Fable.Core + +let id = "id" + +let firstName = "firstName" + +[] +type Person(?baseOn : Person) + #if !FABLE_COMPILER + as self + #endif + = + + inherit DynamicObj() + + let _ = + + match baseOn with + | Some dynOb -> + #if !FABLE_COMPILER + self.Properties <- dynOb.Properties + #endif + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT + FableJS.baseObjectOn dynOb + #endif + #if FABLE_COMPILER_PYTHON + () + #endif + | None -> () + + #if FABLE_COMPILER_PYTHON + do Fable.Core.PyInterop.emitPyStatement "" """ + def __new__(cls, base_on: "Person | None" = None): + if base_on is not None: + return base_on + + return super().__new__(cls) + """ + #endif + + + //#if FABLE_COMPILER_PYTHON + //member this.__new__(cls, baseOn : Person) = + // Fable.Core.PyInterop.emitPyStatement (cls,baseOn) """if $1 is not None: + // return $1 + + // return super().__new__($0) + // """ + + //#endif + + member this.GetID() = this.GetPropertyValue(id) + + member this.SetID(value) = this.SetProperty(id, value) + + member this.GetFirstName() = this.GetPropertyValue(firstName) + + member this.SetFirstName(value) = this.SetProperty(firstName, value) + +let tests_baseOn = testList "BaseOn" [ + + testCase "AnotherPerson" <| fun _ -> + let p1 = Person() + let name = "John" + p1.SetFirstName(name) + let id = "123" + p1.SetID(id) + Expect.equal (p1.GetFirstName()) name "P1: First name should be set" + Expect.equal (p1.GetID()) id "P1: ID should be set" + let p2 = Person(baseOn = p1) + Expect.equal (p2.GetFirstName()) name "P2: First name should be set" + Expect.equal (p2.GetID()) id "P2: ID should be set" + + testCase "InheritedMutability" <| fun _ -> + let p1 = Person() + let name = "John" + p1.SetFirstName(name) + + let p2 = Person(baseOn = p1) + let newName = "Jane" + p2.SetFirstName(newName) + Expect.equal (p2.GetFirstName()) newName "P2: First name should be set" + Expect.equal (p1.GetFirstName()) newName "P1: First name should be set" +] + +let main = testList "InheritanceView" [ + tests_baseOn +] \ No newline at end of file diff --git a/tests/DynamicObject.Tests/Main.fs b/tests/DynamicObject.Tests/Main.fs index beed87f..9894ee0 100644 --- a/tests/DynamicObject.Tests/Main.fs +++ b/tests/DynamicObject.Tests/Main.fs @@ -9,6 +9,7 @@ let all = testSequenced <| testList "DynamicObj" [ DynamicObj.Tests.main DynObj.Tests.main Inheritance.Tests.main + InheritanceView.Tests.main Interface.Tests.main Serialization.Tests.main ] From 000cc9455840269173694a58f09eeb4245570651 Mon Sep 17 00:00:00 2001 From: Heinrich Lukas Weil Date: Sun, 2 Feb 2025 21:15:26 +0100 Subject: [PATCH 2/3] add derived type test case for inheritance view --- tests/DynamicObject.Tests/InheritanceView.fs | 73 +++++++++++++++++--- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/tests/DynamicObject.Tests/InheritanceView.fs b/tests/DynamicObject.Tests/InheritanceView.fs index e572dc2..f1aba65 100644 --- a/tests/DynamicObject.Tests/InheritanceView.fs +++ b/tests/DynamicObject.Tests/InheritanceView.fs @@ -6,11 +6,17 @@ open DynamicObj open Fable.Core let id = "id" - let firstName = "firstName" +let lastName = "lastName" + +// We introduce and test here the concept of an inheritanceView: +// A derived type that is inherits a base type. +// If the constructor of the derived type is called with a base type as input, no new object is created, but the base type is used as the base object. +// The derived type can therefore be seen as a view on the base type, with additional methods to access the dynamic properties of the base type. + [] -type Person(?baseOn : Person) +type BaseType(?baseOn : BaseType) #if !FABLE_COMPILER as self #endif @@ -35,7 +41,7 @@ type Person(?baseOn : Person) #if FABLE_COMPILER_PYTHON do Fable.Core.PyInterop.emitPyStatement "" """ - def __new__(cls, base_on: "Person | None" = None): + def __new__(cls, base_on: "BaseType | None" = None): if base_on is not None: return base_on @@ -45,7 +51,7 @@ type Person(?baseOn : Person) //#if FABLE_COMPILER_PYTHON - //member this.__new__(cls, baseOn : Person) = + //member this.__new__(cls, baseOn : BaseType) = // Fable.Core.PyInterop.emitPyStatement (cls,baseOn) """if $1 is not None: // return $1 @@ -62,32 +68,79 @@ type Person(?baseOn : Person) member this.SetFirstName(value) = this.SetProperty(firstName, value) -let tests_baseOn = testList "BaseOn" [ +[] +type DerivedType(?baseOn : BaseType) = + + inherit BaseType(?baseOn = baseOn) + + member this.GetLastName() = this.GetPropertyValue(lastName) + + member this.SetLastName(value) = this.SetProperty(lastName, value) + + +let tests_baseType = testList "BaseType" [ testCase "AnotherPerson" <| fun _ -> - let p1 = Person() + let p1 = BaseType() let name = "John" p1.SetFirstName(name) let id = "123" p1.SetID(id) Expect.equal (p1.GetFirstName()) name "P1: First name should be set" Expect.equal (p1.GetID()) id "P1: ID should be set" - let p2 = Person(baseOn = p1) + let p2 = BaseType(baseOn = p1) Expect.equal (p2.GetFirstName()) name "P2: First name should be set" Expect.equal (p2.GetID()) id "P2: ID should be set" testCase "InheritedMutability" <| fun _ -> - let p1 = Person() + let p1 = BaseType() let name = "John" p1.SetFirstName(name) - let p2 = Person(baseOn = p1) + let p2 = BaseType(baseOn = p1) + let newName = "Jane" + p2.SetFirstName(newName) + Expect.equal (p2.GetFirstName()) newName "P2: First name should be set" + Expect.equal (p1.GetFirstName()) newName "P1: First name should be set" +] + +let tests_derivedType = testList "DerivedType" [ + + testCase "AnotherPerson" <| fun _ -> + let p1 = DerivedType() + let name = "John" + p1.SetFirstName(name) + let id = "123" + p1.SetID(id) + let lastName = "Doe" + p1.SetLastName(lastName) + Expect.equal (p1.GetFirstName()) name "P1: First name should be set" + Expect.equal (p1.GetID()) id "P1: ID should be set" + Expect.equal (p1.GetLastName()) lastName "P1: Last name should be set" + let p2 = DerivedType(baseOn = p1) + Expect.equal (p2.GetFirstName()) name "P2: First name should be set" + Expect.equal (p2.GetID()) id "P2: ID should be set" + Expect.equal (p2.GetLastName()) lastName "P2: Last name should be set" + + testCase "InheritedMutability" <| fun _ -> + let p1 = DerivedType() + let firstName = "John" + p1.SetFirstName(firstName) + let lastName = "Doe" + p1.SetLastName(lastName) + + let p2 = DerivedType(baseOn = p1) let newName = "Jane" p2.SetFirstName(newName) + let newLastName = "Smith" + p2.SetLastName(newLastName) Expect.equal (p2.GetFirstName()) newName "P2: First name should be set" + Expect.equal (p2.GetLastName()) newLastName "P2: Last name should be set" Expect.equal (p1.GetFirstName()) newName "P1: First name should be set" + Expect.equal (p1.GetLastName()) newLastName "P1: Last name should be set" ] let main = testList "InheritanceView" [ - tests_baseOn + tests_baseType + tests_derivedType ] \ No newline at end of file From 4e21f8d9b03edf3f525fc2df8dedd97303e675ab Mon Sep 17 00:00:00 2001 From: HLWeil Date: Mon, 3 Feb 2025 14:36:16 +0100 Subject: [PATCH 3/3] finish up inheritanceView --- src/DynamicObj/DynamicObj.fs | 34 ++++++++-- src/DynamicObj/FableJS.fs | 3 - src/DynamicObj/FablePy.fs | 3 - .../DynamicObj/TryGetPropertyHelper.fs | 2 + .../DynamicObj/TryGetStaticPropertyHelper.fs | 2 + .../DynamicObject.Tests.fsproj | 2 +- .../{InheritanceStatic.fs => Inheritance.fs} | 2 +- tests/DynamicObject.Tests/InheritanceView.fs | 64 ++++++++++++++----- 8 files changed, 82 insertions(+), 30 deletions(-) rename tests/DynamicObject.Tests/{InheritanceStatic.fs => Inheritance.fs} (99%) diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 02dadbc..28f25df 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -12,7 +12,7 @@ type DynamicObj() = #if !FABLE_COMPILER inherit DynamicObject() - #endif + let mutable properties = new Dictionary() @@ -21,17 +21,32 @@ type DynamicObj() = /// member this.Properties with get() = properties - and set(value) = properties <- value + and internal set(value) = properties <- value /// - /// Creates a new DynamicObj from a Dictionary containing dynamic properties. + /// Creates a new DynamicObj from a Dictionary containing dynamic properties. This method is not Fable-compatible. /// /// The dictionary with the dynamic properties - static member ofDict (dynamicProperties: Dictionary) = + static member ofDictInPlace (dynamicProperties: Dictionary) = let obj = DynamicObj() obj.Properties <- dynamicProperties obj + #endif + + /// + /// Creates a new DynamicObj from a Dictionary by deep copying the fields of the dictionary. + /// + /// The dictionary with the dynamic properties + static member ofDict (dynamicProperties: Dictionary) = + let obj = DynamicObj() + dynamicProperties + |> Seq.iter (fun kv -> + let copy = CopyUtils.tryDeepCopyObj(kv.Value,true) + obj.SetProperty(kv.Key,copy) + ) + obj + /// /// Returns Some(PropertyHelper) if a static property with the given name exists, otherwise None. /// @@ -1022,4 +1037,13 @@ and CopyUtils = box newDyn | _ -> o - tryDeepCopyObj o \ No newline at end of file + tryDeepCopyObj o + + +#if !FABLE_COMPILER +[] +module Helper = + + let setProperties (dynObj: DynamicObj) (properties: Dictionary) = + dynObj.Properties <- properties +#endif \ No newline at end of file diff --git a/src/DynamicObj/FableJS.fs b/src/DynamicObj/FableJS.fs index 857a106..90166cb 100644 --- a/src/DynamicObj/FableJS.fs +++ b/src/DynamicObj/FableJS.fs @@ -158,9 +158,6 @@ module FableJS = getPropertyHelpers o |> Array.map (fun h -> h.Name) - [] - let baseObjectOn (o:obj) : unit = jsNative - module Interfaces = diff --git a/src/DynamicObj/FablePy.fs b/src/DynamicObj/FablePy.fs index 4a63bdf..ceb22a7 100644 --- a/src/DynamicObj/FablePy.fs +++ b/src/DynamicObj/FablePy.fs @@ -211,9 +211,6 @@ module FablePy = getPropertyHelpers o |> Array.map (fun h -> h.Name) - [] - let baseObjectOn (o:obj) : unit = nativeOnly - module Interfaces = [] diff --git a/tests/DynamicObject.Tests/DynamicObj/TryGetPropertyHelper.fs b/tests/DynamicObject.Tests/DynamicObj/TryGetPropertyHelper.fs index 1d7acb4..dcb5e8b 100644 --- a/tests/DynamicObject.Tests/DynamicObj/TryGetPropertyHelper.fs +++ b/tests/DynamicObject.Tests/DynamicObj/TryGetPropertyHelper.fs @@ -19,6 +19,7 @@ let tests_TryGetPropertyHelper = testList "TryGetPropertyHelper" [ Expect.isTrue b.IsMutable "Properties should be mutable" Expect.isFalse b.IsImmutable "Properties should not be immutable" + #if !FABLE_COMPILER // Properties field is dotnet only as js and py use native properties testCase "Existing static property" <| fun _ -> let a = DynamicObj() let b = Expect.wantSome (a.TryGetPropertyHelper("Properties")) "Value should exist" @@ -26,4 +27,5 @@ let tests_TryGetPropertyHelper = testList "TryGetPropertyHelper" [ Expect.isFalse b.IsDynamic "Properties should not be dynamic" Expect.isTrue b.IsMutable "Properties should be mutable" Expect.isFalse b.IsImmutable "Properties should not be immutable" + #endif ] \ No newline at end of file diff --git a/tests/DynamicObject.Tests/DynamicObj/TryGetStaticPropertyHelper.fs b/tests/DynamicObject.Tests/DynamicObj/TryGetStaticPropertyHelper.fs index 666bb20..0f14ffb 100644 --- a/tests/DynamicObject.Tests/DynamicObj/TryGetStaticPropertyHelper.fs +++ b/tests/DynamicObject.Tests/DynamicObj/TryGetStaticPropertyHelper.fs @@ -10,6 +10,7 @@ let tests_TryGetStaticPropertyHelper = testList "TryGetStaticPropertyHelper" [ let b = a.TryGetStaticPropertyHelper("a") Expect.isNone b "Value should not exist" + #if !FABLE_COMPILER // Properties field is dotnet only as js and py use native properties testCase "Properties dictionary is static property" <| fun _ -> let a = DynamicObj() let b = Expect.wantSome (a.TryGetStaticPropertyHelper("Properties")) "Value should exist" @@ -17,6 +18,7 @@ let tests_TryGetStaticPropertyHelper = testList "TryGetStaticPropertyHelper" [ Expect.isFalse b.IsDynamic "Properties should not be dynamic" Expect.isTrue b.IsMutable "Properties should be mutable" Expect.isFalse b.IsImmutable "Properties should not be immutable" + #endif testCase "dynamic property not retrieved as static" <| fun _ -> let a = DynamicObj() diff --git a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj index a16396a..fec2ad1 100644 --- a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj +++ b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj @@ -35,7 +35,7 @@ - + diff --git a/tests/DynamicObject.Tests/InheritanceStatic.fs b/tests/DynamicObject.Tests/Inheritance.fs similarity index 99% rename from tests/DynamicObject.Tests/InheritanceStatic.fs rename to tests/DynamicObject.Tests/Inheritance.fs index c859e0a..e497578 100644 --- a/tests/DynamicObject.Tests/InheritanceStatic.fs +++ b/tests/DynamicObject.Tests/Inheritance.fs @@ -150,7 +150,7 @@ let tests_print = testList "Print" [ Expect.isTrue print "Print failed for issue 14" ] -let main = testList "InheritanceStatic" [ +let main = testList "Inheritance" [ tests_set tests_remove tests_getProperties diff --git a/tests/DynamicObject.Tests/InheritanceView.fs b/tests/DynamicObject.Tests/InheritanceView.fs index f1aba65..991c1bb 100644 --- a/tests/DynamicObject.Tests/InheritanceView.fs +++ b/tests/DynamicObject.Tests/InheritanceView.fs @@ -18,7 +18,7 @@ let lastName = "lastName" [] type BaseType(?baseOn : BaseType) #if !FABLE_COMPILER - as self + as self #endif = @@ -29,10 +29,14 @@ type BaseType(?baseOn : BaseType) match baseOn with | Some dynOb -> #if !FABLE_COMPILER - self.Properties <- dynOb.Properties + DynamicObj.Helper.setProperties self dynOb.Properties #endif #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT - FableJS.baseObjectOn dynOb + do Fable.Core.JsInterop.emitJsStatement "" """ + const protoType = Object.getPrototypeOf(this); + Object.setPrototypeOf(baseOn, protoType); + return baseOn; + """ #endif #if FABLE_COMPILER_PYTHON () @@ -42,24 +46,17 @@ type BaseType(?baseOn : BaseType) #if FABLE_COMPILER_PYTHON do Fable.Core.PyInterop.emitPyStatement "" """ def __new__(cls, base_on: "BaseType | None" = None): - if base_on is not None: + if base_on is not None and isinstance(base_on, cls): return base_on + if base_on is not None: + base_on.__class__ = cls + return base_on + return super().__new__(cls) """ #endif - - //#if FABLE_COMPILER_PYTHON - //member this.__new__(cls, baseOn : BaseType) = - // Fable.Core.PyInterop.emitPyStatement (cls,baseOn) """if $1 is not None: - // return $1 - - // return super().__new__($0) - // """ - - //#endif - member this.GetID() = this.GetPropertyValue(id) member this.SetID(value) = this.SetProperty(id, value) @@ -106,7 +103,40 @@ let tests_baseType = testList "BaseType" [ let tests_derivedType = testList "DerivedType" [ - testCase "AnotherPerson" <| fun _ -> + testCase "OnBase_AnotherPerson" <| fun _ -> + let p1 = BaseType() + let name = "John" + p1.SetFirstName(name) + let id = "123" + p1.SetID(id) + let lN = "Doe" + p1.SetProperty(lastName,lN) + Expect.equal (p1.GetFirstName()) name "P1: First name should be set" + Expect.equal (p1.GetID()) id "P1: ID should be set" + Expect.equal (p1.GetPropertyValue(lastName)) lN "P1: Last name should be set" + let p2 = DerivedType(baseOn = p1) + Expect.equal (p2.GetFirstName()) name "P2: First name should be set" + Expect.equal (p2.GetID()) id "P2: ID should be set" + Expect.equal (p2.GetLastName()) lN "P2: Last name should be set" + + testCase "OnBase_InheritedMutability" <| fun _ -> + let p1 = BaseType() + let name = "John" + p1.SetFirstName(name) + let lN = "Doe" + p1.SetProperty(lastName,lN) + + let p2 = DerivedType(baseOn = p1) + let newName = "Jane" + p2.SetFirstName(newName) + let newLastName = "Smith" + p2.SetLastName(newLastName) + Expect.equal (p2.GetFirstName()) newName "P2: First name should be set" + Expect.equal (p2.GetLastName()) newLastName "P2: Last name should be set" + Expect.equal (p1.GetFirstName()) newName "P1: First name should be set" + Expect.equal (p1.GetPropertyValue(lastName)) newLastName "P1: Last name should be set" + + testCase "OnDerived_AnotherPerson" <| fun _ -> let p1 = DerivedType() let name = "John" p1.SetFirstName(name) @@ -122,7 +152,7 @@ let tests_derivedType = testList "DerivedType" [ Expect.equal (p2.GetID()) id "P2: ID should be set" Expect.equal (p2.GetLastName()) lastName "P2: Last name should be set" - testCase "InheritedMutability" <| fun _ -> + testCase "OnDerived_InheritedMutability" <| fun _ -> let p1 = DerivedType() let firstName = "John" p1.SetFirstName(firstName)