summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorda-woods <dw-git@d-woods.co.uk>2022-05-03 14:26:52 +0100
committerGitHub <noreply@github.com>2022-05-03 15:26:52 +0200
commitfe98838ca28dff29d6b6d8c074c43290c9554ead (patch)
treecd08414c5110b0536473822dc459b8810dae9a1f
parent4060346cf79cf05cdc1b40a43b7c940d376afbce (diff)
downloadcython-fe98838ca28dff29d6b6d8c074c43290c9554ead.tar.gz
Mark reverse operators with METHOD_COEXIST (GH-4753)
This means that reverse operators (e.g. `__radd__`) won't be hidden by the automatic wrapper that `PyType_Ready()` produces if the forward method exists. Although they won't work as in Python, they will be possible to look up and call explicitly. This should make it easier to write code that's compatible with Cython 0.29.x and Cython 3 (where reverse operators will be full supported). Closes https://github.com/cython/cython/issues/4750
-rw-r--r--Cython/Compiler/Code.py4
-rw-r--r--Cython/Compiler/TypeSlots.py14
-rw-r--r--tests/run/special_methods_T561.pyx24
3 files changed, 40 insertions, 2 deletions
diff --git a/Cython/Compiler/Code.py b/Cython/Compiler/Code.py
index f43c4b2b8..45c5a325c 100644
--- a/Cython/Compiler/Code.py
+++ b/Cython/Compiler/Code.py
@@ -2233,8 +2233,8 @@ class CCodeWriter(object):
method_flags = entry.signature.method_flags()
if not method_flags:
return
- if entry.is_special:
- from . import TypeSlots
+ from . import TypeSlots
+ if entry.is_special or TypeSlots.is_reverse_number_slot(entry.name):
method_flags += [TypeSlots.method_coexist]
func_ptr = wrapper_code_writer.put_pymethoddef_wrapper(entry) if wrapper_code_writer else entry.func_cname
# Add required casts, but try not to shadow real warnings.
diff --git a/Cython/Compiler/TypeSlots.py b/Cython/Compiler/TypeSlots.py
index 0b4ff6704..c6867447d 100644
--- a/Cython/Compiler/TypeSlots.py
+++ b/Cython/Compiler/TypeSlots.py
@@ -625,6 +625,20 @@ def get_slot_code_by_name(scope, slot_name):
slot = get_slot_by_name(slot_name)
return slot.slot_code(scope)
+def is_reverse_number_slot(name):
+ """
+ Tries to identify __radd__ and friends (so the METH_COEXIST flag can be applied).
+
+ There's no great consequence if it inadvertently identifies a few other methods
+ so just use a simple rule rather than an exact list.
+ """
+ if name.startswith("__r") and name.endswith("__"):
+ forward_name = name.replace("r", "", 1)
+ for meth in PyNumberMethods:
+ if getattr(meth, "method_name", None) == forward_name:
+ return True
+ return False
+
#------------------------------------------------------------------------------------------
#
diff --git a/tests/run/special_methods_T561.pyx b/tests/run/special_methods_T561.pyx
index 350d452f4..e5d1ec5bc 100644
--- a/tests/run/special_methods_T561.pyx
+++ b/tests/run/special_methods_T561.pyx
@@ -923,3 +923,27 @@ cdef class VerySpecialSubType(VerySpecial):
def __get__(self, inst, own):
return VerySpecial.__get__(self, inst, own)
+
+
+cdef class ReverseMethodsExist:
+ """
+ reverse methods (such as __radd__) don't work in Cython <3. However, if they
+ are defined then it should be possible to look them up explicitly instead of
+ looking up autogenerated wrapper (which points to the forward method)
+
+ >>> o = ReverseMethodsExist()
+ >>> o + o
+ 'add'
+ >>> o.__add__(o)
+ 'add'
+ >>> o.__radd__(o)
+ 'radd'
+ >>> o.__rsub__(o)
+ 'rsub'
+ """
+ def __add__(self, other):
+ return "add"
+ def __radd__(self, other):
+ return "radd"
+ def __rsub__(self, other):
+ return "rsub"