From 344ca5fbc6a4bc2bec47a7795a3313eaf7040477 Mon Sep 17 00:00:00 2001 From: William S Fulton Date: Sat, 27 Jan 2024 14:41:23 +0000 Subject: [PATCH] Friend operator overloading fix for Python builtin Don't generate calls to SWIGPY_BINARYFUNC_CLOSURE(_wrap___lshift__) for the 'global' function wrappers for the friend functions. Only occurred when a friend class is within a namespace due to the renames in pyopers.swg, such as: %rename(__lshift__) *::operator<<; The rename was intended for member functions but was also being attached to friends that are within a namespace. --- CHANGES.current | 4 + Examples/test-suite/common.mk | 1 + .../test-suite/friends_operator_overloading.i | 114 ++++++++++++++++++ .../friends_operator_overloading_runme.py | 41 +++++++ Source/Modules/python.cxx | 3 +- 5 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 Examples/test-suite/friends_operator_overloading.i create mode 100644 Examples/test-suite/python/friends_operator_overloading_runme.py diff --git a/CHANGES.current b/CHANGES.current index aeb3706e2..cb49fe6d5 100644 --- a/CHANGES.current +++ b/CHANGES.current @@ -7,6 +7,10 @@ the issue number to the end of the URL: https://github.com/swig/swig/issues/ Version 4.2.1 (in progress) =========================== +2024-01-27: wsfulton + [Python] Fix compilation error when wrapping two or more classes that + have the same friend operator overload when the classes are in a namespace. + 2024-01-15: wsfulton https://sourceforge.net/p/swig/bugs/960/ https://sourceforge.net/p/swig/bugs/807/ diff --git a/Examples/test-suite/common.mk b/Examples/test-suite/common.mk index 0f6010bc0..25280c1df 100644 --- a/Examples/test-suite/common.mk +++ b/Examples/test-suite/common.mk @@ -263,6 +263,7 @@ CPP_TEST_CASES += \ features \ fragments \ friends \ + friends_operator_overloading \ friends_template \ funcptr_cpp \ functors \ diff --git a/Examples/test-suite/friends_operator_overloading.i b/Examples/test-suite/friends_operator_overloading.i new file mode 100644 index 000000000..bbf503360 --- /dev/null +++ b/Examples/test-suite/friends_operator_overloading.i @@ -0,0 +1,114 @@ +%module friends_operator_overloading + +// Tests friend operators within a namespace +// Demonstrates how to turn friend operators into member functions (required for some languages - tests includes a Python runtime test) +// Note that by default the friend functions result in global function wrappers (overloaded as there are friends from two different classes) +// Testcase highlighted a compilation problem with Python builtin wrappers +// Tests only the languages that don't ignore operator<< + +%warnfilter(SWIGWARN_LANG_IDENTIFIER, // Warning 503: Can't wrap 'operator <<' unless renamed to a valid identifier. + SWIGWARN_IGNORE_OPERATOR_LSHIFT) operator<<; // Warning 373: operator<< ignored + +%inline %{ +// Remove this define to test the equivalent implementation using member methods instead of friends +#define FRIENDS + +// Debugging/tracing using printf +#include +//#define myprintf(a, b) printf(a, b) +#define myprintf(a, b) + +namespace shifting { + +class ShiftA { + int val; +public: + ShiftA(int val = 0) : val(val) {} +#if !defined(FRIENDS) + ShiftA operator<<(const ShiftA& gd) { + ShiftA ret(val - gd.getVal()); + myprintf("member operator << (GeoData) %d\n", ret.getVal()); + return ret; + } + ShiftA operator<<(int amount) { + ShiftA ret(val - amount); + myprintf("member operator << (int) %d\n", ret.getVal()); + return ret; + } +#else + friend ShiftA operator<<(const ShiftA& this_, const ShiftA& gd) { + ShiftA ret(this_.val - gd.getVal()); + myprintf("friend operator << (GeoData) %d\n", ret.getVal()); + return ret; + } + friend ShiftA operator<<(const ShiftA& this_, int amount) { + ShiftA ret(this_.val - amount); + myprintf("friend operator << (int) %d\n", ret.getVal()); + return ret; + } +#if defined(SWIG) +%extend { + ShiftA operator<<(const ShiftA& gd) { return *$self << gd; } + ShiftA operator<<(int amount) { return *$self << amount; } +} +#endif +#endif + int getVal() const { return val; } +}; + +class ShiftB { + int val; +public: + ShiftB(int val = 0) : val(val) {} +#if !defined(FRIENDS) + ShiftB operator<<(const ShiftB& gd) { + ShiftB ret(val - gd.getVal()); + myprintf("member operator << (GeoData) %d\n", ret.getVal()); + return ret; + } + ShiftB operator<<(int amount) { + ShiftB ret(val - amount); + myprintf("member operator << (int) %d\n", ret.getVal()); + return ret; + } +#else + friend ShiftB operator<<(const ShiftB& this_, const ShiftB& gd) { + ShiftB ret(this_.val - gd.getVal()); + myprintf("friend operator << (GeoData) %d\n", ret.getVal()); + return ret; + } + friend ShiftB operator<<(const ShiftB& this_, int amount) { + ShiftB ret(this_.val - amount); + myprintf("friend operator << (int) %d\n", ret.getVal()); + return ret; + } +#if defined(SWIG) +%extend { + ShiftB operator<<(const ShiftB& gd) { return *$self << gd; } + ShiftB operator<<(int amount) { return *$self << amount; } +} +#endif +#endif + int getVal() const { return val; } +}; + +void sanity_checker_ShiftA() { + ShiftA gd1(20); + ShiftA gd2(100); + ShiftA gd3(gd2 << gd1); + myprintf("gd3 %d\n", gd3.getVal()); + ShiftA gd4(gd2 << 30); + myprintf("gd4 %d\n", gd4.getVal()); + +} +void sanity_checker_ShiftB() { + ShiftB gd1(20); + ShiftB gd2(100); + ShiftB gd3(gd2 << gd1); + myprintf("gd3 %d\n", gd3.getVal()); + ShiftB gd4(gd2 << 30); + myprintf("gd4 %d\n", gd4.getVal()); +} + +} +%} diff --git a/Examples/test-suite/python/friends_operator_overloading_runme.py b/Examples/test-suite/python/friends_operator_overloading_runme.py new file mode 100644 index 000000000..2b0975537 --- /dev/null +++ b/Examples/test-suite/python/friends_operator_overloading_runme.py @@ -0,0 +1,41 @@ +import friends_operator_overloading + +friends_operator_overloading.sanity_checker_ShiftA() +friends_operator_overloading.sanity_checker_ShiftB() + +sa1 = friends_operator_overloading.ShiftA(200) +sa2 = friends_operator_overloading.ShiftA(1000) +sb1 = friends_operator_overloading.ShiftB(200) +sb2 = friends_operator_overloading.ShiftB(1000) + +# Shift operator via members +sa3 = sa2 << sa1 +val = sa3.getVal() +if val != 800: + raise RuntimeError("Wrong val: {}".format(val)) + +sa4 = sa2 << 300 +val = sa4.getVal() +if val != 700: + raise RuntimeError("Wrong val: {}".format(val)) + +sb3 = sb2 << sb1 +val = sb3.getVal() +if val != 800: + raise RuntimeError("Wrong val: {}".format(val)) + +sb4 = sb2 << 300 +val = sb4.getVal() +if val != 700: + raise RuntimeError("Wrong val: {}".format(val)) + +# Shift operator via global wrapper +shift = friends_operator_overloading.__lshift__(sa2, sa1) +val = shift.getVal() +if val != 800: + raise RuntimeError("Wrong val: {}".format(val)) + +shift = friends_operator_overloading.__lshift__(sb2, sb1) +val = shift.getVal() +if val != 800: + raise RuntimeError("Wrong val: {}".format(val)) diff --git a/Source/Modules/python.cxx b/Source/Modules/python.cxx index 69e47d6d0..56e888f8a 100644 --- a/Source/Modules/python.cxx +++ b/Source/Modules/python.cxx @@ -2718,6 +2718,7 @@ public: int constructor = (!Cmp(nodeType, "constructor")); int destructor = (!Cmp(nodeType, "destructor")); String *storage = Getattr(n, "storage"); + int isfriend = Strstr(storage, "friend") != NULL; /* Only the first constructor is handled as init method. Others constructor can be emitted via %rename */ int handled_as_init = 0; @@ -3374,7 +3375,7 @@ public: if (in_class && builtin) { /* Handle operator overloads for builtin types */ String *slot = Getattr(n, "feature:python:slot"); - if (slot) { + if (slot && !isfriend) { String *func_type = Getattr(n, "feature:python:slot:functype"); String *closure_decl = getClosure(func_type, wrapper_name, overname ? 0 : funpack); String *feature_name = NewStringf("feature:python:%s", slot);