diff --git a/CHANGES.current b/CHANGES.current
index 584fa6cb8..f9177f1a6 100644
--- a/CHANGES.current
+++ b/CHANGES.current
@@ -7,6 +7,27 @@ the issue number to the end of the URL: https://github.com/swig/swig/issues/
Version 4.0.0 (in progress)
===========================
+2017-06-19: wsfulton
+ [Python] Fix handling of rich comparisons when wrapping overloaded operators:
+
+ operator< operator<= operator> operator>= operator== operator!=
+
+ Previously a TypeError was always thrown if the type was not correct. NotImplemented
+ is now returned from these wrapped functions if the type being compared with is
+ not correct. The subsequent behaviour varies between different versions of Python
+ and the comparison function being used, but is now consistent with normal Python
+ behaviour. For example, for the first 4 operator overloads above, a TypeError
+ 'unorderable types' is thrown in Python 3, but Python 2 will return True or False.
+ NotImplemented should be returned when the comparison cannot be done, see PEP 207 and
+ https://docs.python.org/3/library/constants.html#NotImplemented
+
+ Note that the bug was only present when overloaded operators did not also have a
+ function overload.
+
+ Fixes SF bug #1208 (3441262) and SF patch #303.
+
+ *** POTENTIAL INCOMPATIBILITY ***
+
2017-06-17: fabrice102
[Go] Fix Go callback example. Fixes github #600, #955, #1000.
diff --git a/Doc/Manual/Python.html b/Doc/Manual/Python.html
index 9b3afbdd9..1b73c08b7 100644
--- a/Doc/Manual/Python.html
+++ b/Doc/Manual/Python.html
@@ -1894,6 +1894,14 @@ Also, be aware that certain operators don't map cleanly to Python. For instance
overloaded assignment operators don't map to Python semantics and will be ignored.
+
+Operator overloading is implemented in the pyopers.swg library file.
+In particular overloaded operators are marked with the python:maybecall feature, also known as %pythonmaybecall.
+This feature forces SWIG to generate code that return an instance of Python's NotImplemented
+instead of raising an exception when the comparison fails, that is, on any kind of error.
+This follows the guidelines in PEP 207 - Rich Comparisons and NotImplemented Python constant.
+
+
diff --git a/Examples/test-suite/python/python_richcompare_runme.py b/Examples/test-suite/python/python_richcompare_runme.py
index a68da2f98..f7c7a673b 100644
--- a/Examples/test-suite/python/python_richcompare_runme.py
+++ b/Examples/test-suite/python/python_richcompare_runme.py
@@ -1,4 +1,12 @@
import python_richcompare
+import sys
+
+def check_unorderable_types(exception):
+ if str(exception).find("unorderable types") == -1:
+ raise RuntimeError("A TypeError 'unorderable types' exception was expected"), None, sys.exc_info()[2]
+
+def is_new_style_class(cls):
+ return hasattr(cls, "__class__")
base1 = python_richcompare.BaseClass(1)
base2 = python_richcompare.BaseClass(2)
@@ -65,6 +73,18 @@ if (b1 == a1):
raise RuntimeError(
"Comparing equivalent instances of different subclasses, == returned True")
+# Check comparison to other objects
+#-------------------------------------------------------------------------------
+if (base1 == 42) :
+ raise RuntimeError("Comparing class to incompatible type, == returned True")
+if not (base1 != 42) :
+ raise RuntimeError("Comparing class to incompatible type, != returned False")
+
+if (a1 == 42) :
+ raise RuntimeError("Comparing class (with overloaded operator ==) to incompatible type, == returned True")
+if not (a1 != 42) :
+ raise RuntimeError("Comparing class (with overloaded operator ==) to incompatible type, != returned False")
+
# Check inequalities
#-------------------------------------------------------------------------
@@ -80,6 +100,42 @@ if not (a2 >= b2):
if not (a2 <= b2):
raise RuntimeError("operator<= failed")
+# Check inequalities to other objects
+#-------------------------------------------------------------------------------
+if is_new_style_class(python_richcompare.BaseClass):
+ # Skip testing -classic option
+ if sys.version_info[0:2] < (3, 0):
+ if (base1 < 42):
+ raise RuntimeError("Comparing class to incompatible type, < returned True")
+ if (base1 <= 42):
+ raise RuntimeError("Comparing class to incompatible type, <= returned True")
+ if not (base1 > 42):
+ raise RuntimeError("Comparing class to incompatible type, > returned False")
+ if not (base1 >= 42):
+ raise RuntimeError("Comparing class to incompatible type, >= returned False")
+ else:
+ # Python 3 throws: TypeError: unorderable types
+ try:
+ res = base1 < 42
+ raise RuntimeError("Failed to throw")
+ except TypeError,e:
+ check_unorderable_types(e)
+ try:
+ res = base1 <= 42
+ raise RuntimeError("Failed to throw")
+ except TypeError,e:
+ check_unorderable_types(e)
+ try:
+ res = base1 > 42
+ raise RuntimeError("Failed to throw")
+ except TypeError,e:
+ check_unorderable_types(e)
+ try:
+ res = base1 >= 42
+ raise RuntimeError("Failed to throw")
+ except TypeError,e:
+ check_unorderable_types(e)
+
# Check inequalities used for ordering
#-------------------------------------------------------------------------
diff --git a/Source/Modules/python.cxx b/Source/Modules/python.cxx
index cf81827cb..802c36cc7 100644
--- a/Source/Modules/python.cxx
+++ b/Source/Modules/python.cxx
@@ -2573,8 +2573,8 @@ public:
if (GetFlag(n, "feature:python:maybecall")) {
Append(f->code, "fail:\n");
- Append(f->code, "Py_INCREF(Py_NotImplemented);\n");
- Append(f->code, "return Py_NotImplemented;\n");
+ Append(f->code, " Py_INCREF(Py_NotImplemented);\n");
+ Append(f->code, " return Py_NotImplemented;\n");
} else {
Node *sibl = n;
while (Getattr(sibl, "sym:previousSibling"))
@@ -2586,7 +2586,7 @@ public:
Delete(fulldecl);
} while ((sibl = Getattr(sibl, "sym:nextSibling")));
Append(f->code, "fail:\n");
- Printf(f->code, "SWIG_SetErrorMsg(PyExc_NotImplementedError,"
+ Printf(f->code, " SWIG_SetErrorMsg(PyExc_NotImplementedError,"
"\"Wrong number or type of arguments for overloaded function '%s'.\\n\"" "\n\" Possible C/C++ prototypes are:\\n\"%s);\n", symname, protoTypes);
Printf(f->code, "return %s;\n", builtin_ctor ? "-1" : "0");
Delete(protoTypes);
@@ -3211,10 +3211,17 @@ public:
if (need_cleanup) {
Printv(f->code, cleanup, NIL);
}
- if (builtin_ctor)
+ if (builtin_ctor) {
Printv(f->code, " return -1;\n", NIL);
- else
- Printv(f->code, " return NULL;\n", NIL);
+ } else {
+ if (GetFlag(n, "feature:python:maybecall")) {
+ Append(f->code, " PyErr_Clear();\n");
+ Append(f->code, " Py_INCREF(Py_NotImplemented);\n");
+ Append(f->code, " return Py_NotImplemented;\n");
+ } else {
+ Printv(f->code, " return NULL;\n", NIL);
+ }
+ }
if (funpack) {