Add missing %python:maybecall to operator overloads.
This ensures NotImplemented is returned on error so that the Python interpreter will handle the operators correctly instead of throwing an exception. NotImplemented was not being returned for non-builtin wrappers when the operator overload did not have a function overload. See PEP 207 and https://docs.python.org/3/library/constants.html#NotImplemented Mentioned in SF patch #303 and SF bug #1208.
This commit is contained in:
parent
f12d14d1be
commit
687cf9c9c1
4 changed files with 98 additions and 6 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Operator overloading is implemented in the <tt>pyopers.swg</tt> library file.
|
||||
In particular overloaded operators are marked with the <tt>python:maybecall</tt> feature, also known as <tt>%pythonmaybecall</tt>.
|
||||
This feature forces SWIG to generate code that return an instance of Python's <tt>NotImplemented</tt>
|
||||
instead of raising an exception when the comparison fails, that is, on any kind of error.
|
||||
This follows the guidelines in <a href="https://www.python.org/dev/peps/pep-0207/">PEP 207 - Rich Comparisons</a> and <a href="https://docs.python.org/3/library/constants.html#NotImplemented">NotImplemented Python constant</a>.
|
||||
</p>
|
||||
|
||||
<H3><a name="Python_nn25">36.3.12 C++ namespaces</a></H3>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue