From 2190f148e13ed6aab310c2e867f178c7a551ec1e Mon Sep 17 00:00:00 2001 From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com> Date: Sat, 23 Aug 2025 18:50:07 +0100 Subject: [PATCH] Test for interface dispatch (#109) --- WoofWare.PawPrint.Test/TestPureCases.fs | 5 + .../sourcesPure/InterfaceDispatch.cs | 336 ++++++++++++++++++ 2 files changed, 341 insertions(+) create mode 100644 WoofWare.PawPrint.Test/sourcesPure/InterfaceDispatch.cs diff --git a/WoofWare.PawPrint.Test/TestPureCases.fs b/WoofWare.PawPrint.Test/TestPureCases.fs index e95405c..7f34783 100644 --- a/WoofWare.PawPrint.Test/TestPureCases.fs +++ b/WoofWare.PawPrint.Test/TestPureCases.fs @@ -56,6 +56,11 @@ module TestPureCases = ExpectedReturnCode = 0 NativeImpls = MockEnv.make () } + { + FileName = "InterfaceDispatch.cs" + ExpectedReturnCode = 0 + NativeImpls = MockEnv.make () + } ] let cases : EndToEndTestCase list = diff --git a/WoofWare.PawPrint.Test/sourcesPure/InterfaceDispatch.cs b/WoofWare.PawPrint.Test/sourcesPure/InterfaceDispatch.cs new file mode 100644 index 0000000..99e3b62 --- /dev/null +++ b/WoofWare.PawPrint.Test/sourcesPure/InterfaceDispatch.cs @@ -0,0 +1,336 @@ +using System; + +public class InterfaceDispatchTests +{ + public static int Main(string[] argv) + { + int result = 0; + + result |= TestBasicInterface(); + result |= TestExplicitImplementation() << 1; + result |= TestMultipleInterfaces() << 2; + result |= TestInterfaceInheritance() << 3; + result |= TestDiamondInheritance() << 4; + result |= TestGenericInterface() << 5; + result |= TestCovariantInterface() << 6; + result |= TestReimplementation() << 7; + result |= TestStructInterface() << 8; + result |= TestNullDispatch() << 9; + result |= TestSharedMethodSignature() << 10; + + return result; + } + + // Test 1: Basic interface dispatch + static int TestBasicInterface() + { + ISimple obj = new SimpleImpl(); + return obj.GetValue() == 42 ? 0 : 1; + } + + // Test 2: Explicit interface implementation + static int TestExplicitImplementation() + { + var obj = new ExplicitImpl(); + IExplicit iface = obj; + + // Direct call should return 10, interface call should return 20 + if (obj.GetValue() != 10) return 1; + if (iface.GetValue() != 20) return 1; + + return 0; + } + + // Test 3: Multiple interfaces + static int TestMultipleInterfaces() + { + var obj = new MultiImpl(); + IFirst first = obj; + ISecond second = obj; + + if (first.GetFirst() != 1) return 1; + if (second.GetSecond() != 2) return 1; + + return 0; + } + + // Test 4: Interface inheritance + static int TestInterfaceInheritance() + { + IDerived obj = new DerivedImpl(); + IBase baseIface = obj; + + if (baseIface.GetBase() != 100) return 1; + if (obj.GetDerived() != 200) return 1; + + return 0; + } + + // Test 5: Diamond inheritance pattern + static int TestDiamondInheritance() + { + var obj = new DiamondImpl(); + ILeft left = obj; + IRight right = obj; + IDiamond diamond = obj; + + if (left.GetValue() != 300) return 1; + if (right.GetValue() != 300) return 1; + if (diamond.GetDiamondValue() != 400) return 1; + + return 0; + } + + // Test 6: Generic interface dispatch + static int TestGenericInterface() + { + IGeneric intObj = new GenericImpl(); + IGeneric strObj = new GenericImpl(); + + if (intObj.Process(5) != 3) return 1; + if (strObj.Process("test") != 5) return 1; + + return 0; + } + + // Test 7: Covariant interface dispatch + static int TestCovariantInterface() + { + ICovariant strCov = new CovariantImpl(); + ICovariant objCov = strCov; // Covariance allows this + + object result = objCov.Get(); + if (!(result is string s && s == "covariant")) return 1; + + return 0; + } + + // Test 8: Interface reimplementation in derived class + static int TestReimplementation() + { + BaseClass baseObj = new DerivedClass(); + IReimpl iface = baseObj; + + // Should call derived implementation + if (iface.Method() != 500) return 1; + + // Now test with base reference + BaseClass pureBase = new BaseClass(); + IReimpl baseIface = pureBase; + if (baseIface.Method() != 600) return 1; + + return 0; + } + + // Test 9: Struct implementing interface + static int TestStructInterface() + { + StructImpl s = new StructImpl { Value = 700 }; + ISimple boxed = s; // Boxing happens here + + if (boxed.GetValue() != 700) return 1; + + // Verify boxing created a copy + s.Value = 800; + if (boxed.GetValue() != 700) return 1; // Should still be 700 + + return 0; + } + + // Test 10: Null dispatch (should throw) + static int TestNullDispatch() + { + ISimple nullRef = null; + try + { + nullRef.GetValue(); + return 1; // Should have thrown + } + catch (NullReferenceException) + { + return 0; // Expected + } + } + + // Test 11: Same method signature on multiple unrelated interfaces + static int TestSharedMethodSignature() + { + var obj = new SharedMethodImpl(); + IReader reader = obj; + IScanner scanner = obj; + + // Both interfaces should be satisfied by the single implementation + if (reader.Read() != "shared") return 1; + if (scanner.Read() != "shared") return 1; + + // Also test with explicit + implicit combination + var mixed = new MixedSharedImpl(); + IReader readerMixed = mixed; + IScanner scannerMixed = mixed; + + if (readerMixed.Read() != "explicit-reader") return 1; + if (scannerMixed.Read() != "implicit-scanner") return 1; + if (mixed.Read() != "implicit-scanner") return 1; // Public method + + return 0; + } + + // Test interfaces and implementations + + interface ISimple + { + int GetValue(); + } + + class SimpleImpl : ISimple + { + public int GetValue() => 42; + } + + interface IExplicit + { + int GetValue(); + } + + class ExplicitImpl : IExplicit + { + public int GetValue() => 10; + int IExplicit.GetValue() => 20; + } + + interface IFirst + { + int GetFirst(); + } + + interface ISecond + { + int GetSecond(); + } + + class MultiImpl : IFirst, ISecond + { + public int GetFirst() => 1; + public int GetSecond() => 2; + } + + interface IBase + { + int GetBase(); + } + + interface IDerived : IBase + { + int GetDerived(); + } + + class DerivedImpl : IDerived + { + public int GetBase() => 100; + public int GetDerived() => 200; + } + + interface ICommon + { + int GetValue(); + } + + interface ILeft : ICommon + { + } + + interface IRight : ICommon + { + } + + interface IDiamond : ILeft, IRight + { + int GetDiamondValue(); + } + + class DiamondImpl : IDiamond + { + public int GetValue() => 300; + public int GetDiamondValue() => 400; + } + + interface IGeneric + { + int Process(T value); + } + + class GenericImpl : IGeneric + { + public int Process(T value) + { + if (typeof(T) == typeof(int)) + { + return 3; + } + + if (typeof(T) == typeof(string)) + { + return 5; + } + + return 0; + } + } + + interface ICovariant + { + T Get(); + } + + class CovariantImpl : ICovariant + { + public string Get() => "covariant"; + } + + interface IReimpl + { + int Method(); + } + + class BaseClass : IReimpl + { + public virtual int Method() => 600; + } + + class DerivedClass : BaseClass, IReimpl + { + public override int Method() => 500; + } + + struct StructImpl : ISimple + { + public int Value; + public int GetValue() => Value; + } + + interface IReader + { + string Read(); + } + + interface IScanner + { + string Read(); // Same signature as IReader.Read() + } + + // Single implicit implementation satisfies both interfaces + class SharedMethodImpl : IReader, IScanner + { + public string Read() => "shared"; + } + + // Mixed: explicit for one, implicit for the other + class MixedSharedImpl : IReader, IScanner + { + // Explicit implementation for IReader + string IReader.Read() => "explicit-reader"; + + // Implicit implementation (public) - satisfies IScanner + public string Read() => "implicit-scanner"; + } +}