Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reference implementation of Inheritance view #52

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions src/DynamicObj/DynamicObj.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

#if !FABLE_COMPILER
inherit DynamicObject()
#endif


let mutable properties = new Dictionary<string, obj>()

Expand All @@ -24,14 +24,29 @@
and internal set(value) = properties <- value

/// <summary>
/// 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.
/// </summary>
/// <param name="dynamicProperties">The dictionary with the dynamic properties</param>
static member ofDict (dynamicProperties: Dictionary<string,obj>) =
static member ofDictInPlace (dynamicProperties: Dictionary<string,obj>) =
let obj = DynamicObj()
obj.Properties <- dynamicProperties
obj

#endif

/// <summary>
/// Creates a new DynamicObj from a Dictionary by deep copying the fields of the dictionary.
/// </summary>
/// <param name="dynamicProperties">The dictionary with the dynamic properties</param>
static member ofDict (dynamicProperties: Dictionary<string,obj>) =
let obj = DynamicObj()
dynamicProperties
|> Seq.iter (fun kv ->
let copy = CopyUtils.tryDeepCopyObj(kv.Value,true)
obj.SetProperty(kv.Key,copy)
)
obj

/// <summary>
/// Returns Some(PropertyHelper) if a static property with the given name exists, otherwise None.
/// </summary>
Expand Down Expand Up @@ -75,7 +90,7 @@
| Some pi -> Some pi
| None -> this.TryGetDynamicPropertyHelper propertyName

/// <summary>

Check warning on line 93 in src/DynamicObj/DynamicObj.fs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

This XML comment is incomplete: no documentation for parameter 'propertyName'

Check warning on line 93 in src/DynamicObj/DynamicObj.fs

View workflow job for this annotation

GitHub Actions / test (windows-latest)

This XML comment is incomplete: no documentation for parameter 'propertyName'
/// Returns Some(boxed property value) if a dynamic (or static) property with the given name exists, otherwise None.
/// </summary>
/// <param name="name">the name of the property to get</param>
Expand Down Expand Up @@ -1022,4 +1037,13 @@
box newDyn
| _ -> o

tryDeepCopyObj o
tryDeepCopyObj o


#if !FABLE_COMPILER
[<RequireQualifiedAccess>]
module Helper =

let setProperties (dynObj: DynamicObj) (properties: Dictionary<string,obj>) =
dynObj.Properties <- properties
#endif
1 change: 1 addition & 0 deletions src/DynamicObj/FableJS.fs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ module FableJS =
getPropertyHelpers o
|> Array.map (fun h -> h.Name)


module Interfaces =

[<Emit("""$0["System.ICloneable.Clone"] != undefined && (typeof $0["System.ICloneable.Clone"]) === 'function'""")>]
Expand Down
2 changes: 2 additions & 0 deletions tests/DynamicObject.Tests/DynamicObj/TryGetPropertyHelper.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ 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"
Expect.isTrue b.IsStatic "Properties should be static"
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
]
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ 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"
Expect.isTrue b.IsStatic "Properties should be static"
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()
Expand Down
1 change: 1 addition & 0 deletions tests/DynamicObject.Tests/DynamicObject.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<Compile Include="DynamicObj\Main.fs" />
<Compile Include="ReflectionUtils.fs" />
<Compile Include="HashUtils.fs" />
<Compile Include="InheritanceView.fs" />
<Compile Include="Inheritance.fs" />
<Compile Include="Interface.fs" />
<Compile Include="DynObj.fs" />
Expand Down
176 changes: 176 additions & 0 deletions tests/DynamicObject.Tests/InheritanceView.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
module InheritanceView.Tests

open System
open Fable.Pyxpecto
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.


[<AttachMembers>]
type BaseType(?baseOn : BaseType)
#if !FABLE_COMPILER
as self
#endif
=

inherit DynamicObj()

let _ =

match baseOn with
| Some dynOb ->
#if !FABLE_COMPILER
DynamicObj.Helper.setProperties self dynOb.Properties
#endif
#if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT
do Fable.Core.JsInterop.emitJsStatement "" """
const protoType = Object.getPrototypeOf(this);
Object.setPrototypeOf(baseOn, protoType);
return baseOn;
"""
#endif
#if FABLE_COMPILER_PYTHON
()
#endif
| None -> ()

#if FABLE_COMPILER_PYTHON
do Fable.Core.PyInterop.emitPyStatement "" """
def __new__(cls, base_on: "BaseType | None" = 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

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)

[<AttachMembers>]
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 = 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 = 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 = BaseType()
let name = "John"
p1.SetFirstName(name)

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 "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)
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 "OnDerived_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_baseType
tests_derivedType
]
1 change: 1 addition & 0 deletions tests/DynamicObject.Tests/Main.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
]
Expand Down
Loading