From 79d86a933cf5a30fcf8fe61cce5f76aa034ecf4a Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sun, 26 Oct 2025 00:03:43 -0400 Subject: [PATCH] preliminary implementation of inheritable method binding definitions and properly choosing the appropriate implementing method --- .../src/lib/Mocha.Core/KnownAttributeGuids.cs | 2 + .../lib/Mocha.Core/KnownRelationshipGuids.cs | 5 +- mocha-dotnet/src/lib/Mocha.Core/Oms.cs | 30 ++++++- .../Mocha.Core.Tests/InheritanceTests.cs | 89 ++++++++++++++++++- 4 files changed, 123 insertions(+), 3 deletions(-) diff --git a/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs b/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs index c4ea2ce..180a6ca 100755 --- a/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs @@ -50,6 +50,8 @@ namespace Mocha.Core } public static class Boolean { + // {989b5a95-d5bb-470c-b067-487316d22da2} + public static Guid Abstract { get; } = new Guid("{868d682b-f77b-4ed4-85a9-337f338635c1}"); public static Guid EvaluateWorkSet { get; } = new Guid("{62c28f9e-5ce8-4ce5-8a56-1e80f1af7f6a}"); public static Guid MethodIsOfTypeSpecified { get; } = new Guid("{6e9df667-0f95-4320-a4be-5cdb00f1d4ee}"); public static Guid DisplayVersionInBadge { get; } = new Guid("{BE5966A4-C4CA-49A6-B504-B6E8759F392D}"); diff --git a/mocha-dotnet/src/lib/Mocha.Core/KnownRelationshipGuids.cs b/mocha-dotnet/src/lib/Mocha.Core/KnownRelationshipGuids.cs index 1a76312..5430fb3 100755 --- a/mocha-dotnet/src/lib/Mocha.Core/KnownRelationshipGuids.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/KnownRelationshipGuids.cs @@ -114,6 +114,8 @@ namespace Mocha.Core public static Guid Method_Binding__executes__Method { get; } = new Guid("{B782A592-8AF5-4228-8296-E3D0B24C70A8}"); public static Guid Method__has_return_type__Class { get; } = new Guid("{1241c599-e55d-4dcf-9200-d0e48c217ef8}"); + public static Guid Method__implements__Method { get; } = new Guid("{83c992d7-03ec-483f-b6f1-225083f201e3}"); + public static Guid Method__implemented_by__Method { get; } = new Guid("{f19e7779-a6b6-4914-a5cc-3c48fa1c9491}"); public static Guid Method_Binding__has__Parameter_Assignment { get; } = new Guid("{24938109-94f1-463a-9314-c49e667cf45b}"); public static Guid Parameter_Assignment__for__Method_Binding { get; } = new Guid("{19c4a5db-fd26-44b8-b431-e081e6ffff8a}"); @@ -481,5 +483,6 @@ namespace Mocha.Core public static Guid Invoke_Web_Service_Method__uses__Executable_returning_Element { get; } = new Guid("{020e26ba-565d-47a4-bdcc-f0050ff6b848}"); public static Guid Invoke_Web_Service_Method__correlated_instances_from__Executable_returning_Instance_Set { get; } = new Guid("{8f1406b4-f48f-45fb-a6ef-2ad51998735f}"); public static Guid Invoke_Web_Service_Method__has_error__Executable_returning_Element { get; } = new Guid("{86aa5338-d913-4044-af8d-7aa4e1a9ddbc}"); - } + + } } diff --git a/mocha-dotnet/src/lib/Mocha.Core/Oms.cs b/mocha-dotnet/src/lib/Mocha.Core/Oms.cs index e196e45..42e15d6 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/Oms.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/Oms.cs @@ -1347,9 +1347,9 @@ public abstract class Oms { if (ClassImplementations.TryGet(parentClassId, out MethodImplementation? impl)) { + InstanceHandle forClass = GetRelatedInstance(methodOrMethodBinding, GetInstance(KnownRelationshipGuids.Method__for__Class)); if (targetInstance == null) { - InstanceHandle forClass = GetRelatedInstance(methodOrMethodBinding, GetInstance(KnownRelationshipGuids.Method__for__Class)); if (forClass != InstanceHandle.Empty) { IInstanceReference? irTarget = context.GetWorkData(forClass); @@ -1366,6 +1366,29 @@ public abstract class Oms //sthrow new NullReferenceException(String.Format("Attempt to call instance method `{0}` without an instance", methodOrMethodBinding)); } + bool is_abstract = GetAttributeValue(methodOrMethodBinding, GetInstance(KnownAttributeGuids.Boolean.Abstract)); + if (is_abstract) + { + InstanceHandle forClassInstance = context.GetWorkData(forClass); + InstanceHandle parentClass2 = GetParentClass(forClassInstance); + string parentClassName = GetAttributeValue(parentClass2, GetInstance(KnownAttributeGuids.Text.Name)); + + IEnumerable methodsImplementingMethod = GetRelatedInstances(methodOrMethodBinding, GetInstance(KnownRelationshipGuids.Method__implemented_by__Method)); + foreach (InstanceHandle ih in methodsImplementingMethod) + { + InstanceHandle forClassInstance2 = GetRelatedInstance(ih, GetInstance(KnownRelationshipGuids.Method__for__Class)); + if (parentClass2 == forClassInstance2) + { + // call implementing method instead + retval = Execute(context, ih, targetInstance); + if (retval != InstanceHandle.Empty) + { + return retval.GetValueOrDefault(); + } + } + } + throw new InvalidOperationException(String.Format("No method implementation for abstract method {0} on class {1} ({2})", GetInstanceKey(methodOrMethodBinding), parentClassName.Replace(' ', '_') + "--IS", GetInstanceKey(parentClass2))); + } if (targetInstance == null) { @@ -2809,4 +2832,9 @@ public abstract class Oms // find AttributeImplementation whose ClassGuid matches return ClassImplementations.Get(classGuid); } + + public InstanceHandle GetInstance(object method__implements__Method) + { + throw new NotImplementedException(); + } } diff --git a/mocha-dotnet/tests/Mocha.Core.Tests/InheritanceTests.cs b/mocha-dotnet/tests/Mocha.Core.Tests/InheritanceTests.cs index c8f99cc..53629db 100644 --- a/mocha-dotnet/tests/Mocha.Core.Tests/InheritanceTests.cs +++ b/mocha-dotnet/tests/Mocha.Core.Tests/InheritanceTests.cs @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Mocha.NET. If not, see . +using Mocha.Core.Oop; +using Mocha.Core.Oop.Methods; using Mocha.Testing; namespace Mocha.Core.Tests; @@ -47,8 +49,93 @@ public class InheritanceTests : OmsTestsBase InstanceHandle ihTextAttribute = Oms.GetInstance(KnownInstanceGuids.Classes.TextAttribute); InstanceHandle ihClass = Oms.GetInstance(KnownInstanceGuids.Classes.Class); - + InstanceHandle ihParentClass = Oms.GetParentClass(ihTextAttribute); Assert.That(ihParentClass, Is.EqualTo(ihClass)); } + + [Test] + public void Unimplemented_Abstract_Method_Call_throws_Exception() + { + // this is what we want to achieve: + // Abstract Base Class (e.g. Layout) defines attribute `Widget Name` which should be overridden by subclasses + // Abstract Base Class also defines a single Get Attribute method which returns `Widget Name` + + // Concrete Subclass (e.g. Stylized Header Layout) overrides `Widget Name` attribute by re-defining it on the subclass + // Calling `Abstract Base Class@get Widget Name` with the subclass as `this` parm SHOULD return the overridden attribute value + + // The Method should only be defined ONCE, and changes based on the overridden attributes of the subclasses + + InstanceHandle c_TestClass = Oms.GetInstance(TEST_CLASS_GUID); + + // `Test Class@get Widget Name(BA)` which is Abstract + BuildAttributeMethod m_TestMethod = Oms.MethodBuilder.CreateBuildAttributeMethod(c_TestClass, "get", "Widget Name", Core.Oop.AccessModifier.Public, false, Oms.GetInstance(KnownAttributeGuids.Text.Name), "DO_NOT_USE"); + Oms.SetAttributeValue(m_TestMethod, Oms.GetInstance(KnownAttributeGuids.Boolean.Abstract), true); + ReturnAttributeMethodBinding ramb = m_TestMethod.CreateMethodBinding(Oms); + + InstanceHandle c_TestClass2 = Oms.GetInstance(TEST_CLASS2_GUID); + Oms.AddSuperClass(c_TestClass2, c_TestClass); + + // i_TestClass1 is an instance of c_TestClass2, which by inheritance includes c_TestClass + InstanceHandle i_TestClass1 = Oms.CreateInstanceOf(c_TestClass2); + + OmsContext context = Oms.CreateContext(); + Assert.That(delegate () + { + // set the c_TestClass to i_TestClass1 + // this call SHOULD succeed given that c_TestClass is a superclass of c_TestClass2, but I don't think this is enforced (yet) + context.SetWorkData(c_TestClass, i_TestClass1); + + // this should throw an exception since c_TestClass2 does not yet implement the m_TestMethod defined as abstract on c_TestClass + string nom = Oms.ExecuteReturningAttributeValue(context, ramb); + }, Throws.InvalidOperationException); + + } + + [Test] + public void Overridden_Abstract_Method_Call_does_not_throw_Exception() + { + // this is what we want to achieve: + // Abstract Base Class (e.g. Layout) defines attribute `Widget Name` which should be overridden by subclasses + // Abstract Base Class also defines a single Get Attribute method which returns `Widget Name` + + // Concrete Subclass (e.g. Stylized Header Layout) overrides `Widget Name` attribute by re-defining it on the subclass + // Calling `Abstract Base Class@get Widget Name` with the subclass as `this` parm SHOULD return the overridden attribute value + + // The Method should only be defined ONCE, and changes based on the overridden attributes of the subclasses + + InstanceHandle c_TestClass = Oms.GetInstance(TEST_CLASS_GUID); + + // `Test Class@get Widget Name(BA)` which is Abstract + BuildAttributeMethod m_TestMethod = Oms.MethodBuilder.CreateBuildAttributeMethod(c_TestClass, "get", "Widget Name", Core.Oop.AccessModifier.Public, false, Oms.GetInstance(KnownAttributeGuids.Text.Name), "DO_NOT_USE"); + Oms.SetAttributeValue(m_TestMethod, Oms.GetInstance(KnownAttributeGuids.Boolean.Abstract), true); + ReturnAttributeMethodBinding ramb = m_TestMethod.CreateMethodBinding(Oms); + + InstanceHandle c_TestClass2 = Oms.GetInstance(TEST_CLASS2_GUID); + Oms.AddSuperClass(c_TestClass2, c_TestClass); + + // `Test Class 2@get Widget Name(BA)` which is overriding the Abstract defined above + string NOM_TEST_VALUE = "Hello World Test Class"; + BuildAttributeMethod m_TestMethod2 = Oms.MethodBuilder.CreateBuildAttributeMethod(c_TestClass2, "get", "Widget Name", Core.Oop.AccessModifier.Public, false, Oms.GetInstance(KnownAttributeGuids.Text.Name), NOM_TEST_VALUE); + ReturnAttributeMethodBinding ramb2 = m_TestMethod2.CreateMethodBinding(Oms); + Oms.AssignRelationship(m_TestMethod2, Oms.GetInstance(KnownRelationshipGuids.Method__implements__Method), m_TestMethod); + + // i_TestClass1 is an instance of c_TestClass2, which by inheritance includes c_TestClass + InstanceHandle i_TestClass1 = Oms.CreateInstanceOf(c_TestClass2); + + OmsContext context = Oms.CreateContext(); + string nom = ""; + Assert.That(delegate () + { + // set the c_TestClass to i_TestClass1 + // this call SHOULD succeed given that c_TestClass is a superclass of c_TestClass2, but I don't think this is enforced (yet) + context.SetWorkData(c_TestClass, i_TestClass1); + + // this should throw an exception since c_TestClass2 does not yet implement the m_TestMethod defined as abstract on c_TestClass + nom = Oms.ExecuteReturningAttributeValue(context, ramb); + }, Throws.Nothing); + + Assert.That(nom, Is.EqualTo(NOM_TEST_VALUE)); + + } } \ No newline at end of file