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. +

+

36.3.12 C++ namespaces

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) {