Repository: cython/cython
Pull request: Make self argument for binops typed
The type for the self argument for binops depends on the compile directives. Therefore it needs a set of type slots that depends on the compiler directives.

I've therefore got rid of the big static list of typeslots in TypeSlots.py in favour of a class that defines them all (and
can be initialized with suitable compiler directives as needed). This involves moving a static dictionary and list out of the
global scope too, so that they too can be part of the class. The passing of the dictionary and list to all the constructors
is a bit awkward

Fixes #4434

Problem statement:
[BUG] __add__ method gets Python object not cython object as self.
**Describe the bug**
This may be working as intended if so I will have to find a workaround for it but I just wanted to confirm first.
The `__eq__` method of a class in cython is given the cython version of the class even if called from python.
I would expect this behaviour to apply to all dunder methods but it does not apply to the `__add__` and `__radd__` methods.
These two methods are given the python object from which the internal attributes cannot be accessed.
This may apply to more methods but the add methods are the only ones I have currently tested.

**To Reproduce**
Code to reproduce the behaviour:
```cython
from cython import typeof

cdef class Test:
    def __eq__(self, other):
        print("eq", typeof(self), repr(self))
        return [] == other

    def __add__(self, other):
        print("add", typeof(self), repr(self))
        return [] + other

    def __radd__(self, other):
        print("radd", typeof(self), repr(self))
        return other + []

cdef Test test = Test()
a = test == []  # eq Test <test.Test object at 0x000001267FF376C0>
b = test + []  # add Python object <test.Test object at 0x000001F97E77C6C0>
c = [] + test  # radd Python object <test.Test object at 0x000001C24B3966C0>
```

**Expected behavior**
I would expect the add methods to be given the cython version of Test so that the internal attributes can be accessed.

**Environment (please complete the following information):**
 - OS: Windows
 - Python version 3.9.1
 - Cython version 3.0.0a9

Hints from issue discussion:
Thanks - I think this is something we know about but it probably does need fixing. It applies to all of the binary operators.

In the past this was deliberate and [documented](https://cython.readthedocs.io/en/latest/src/userguide/special_methods.html#arithmetic-methods) because add could be called with either `self` or `other` being `Test`.

Now that we implement `__radd__` correctly it should be possible to type `self` (see https://github.com/cython/cython/issues/2056). It's slightly complicated though because we do still try to support the old behaviour by default. We've [discussed this before](https://github.com/cython/cython/pull/4204#discussion_r648043848) and I think we decided it was best to add the typing even if it might cause issues.

It's easily worked around though - you just assign `self` to something typed:

```
def __add__(self, other):
        cdef Test selft = self
        print("add", typeof(selft), repr(self))
        return [] + other
```

I'll make this a blocker for Cython 3 I think because it's a potentially incompatible change so we want to do it there. I don't think it'll be large change.

Commit message: Added documentation in "migrating" guide

Patch to review:
diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py
index 99b2f0028ff..713adb3bb2d 100644
--- a/Cython/Compiler/ModuleNode.py
+++ b/Cython/Compiler/ModuleNode.py
@@ -1437,7 +1437,7 @@ def generate_typeobj_definitions(self, env, code):
                         warning(scope.parent_type.pos,
                                 "total_ordering directive used, but no comparison and equality methods defined")
 
-                    for slot in TypeSlots.PyNumberMethods:
+                    for slot in TypeSlots.get_slot_table(code.globalstate.directives).PyNumberMethods:
                         if slot.is_binop and scope.defines_any_special(slot.user_methods):
                             self.generate_binop_function(scope, slot, code, entry.pos)
 
@@ -1845,7 +1845,7 @@ def generate_traverse_function(self, scope, code, cclass_entry):
         code.putln("}")
 
     def generate_clear_function(self, scope, code, cclass_entry):
-        tp_slot = TypeSlots.get_slot_by_name("tp_clear")
+        tp_slot = TypeSlots.get_slot_by_name("tp_clear", scope.directives)
         slot_func = scope.mangle_internal("tp_clear")
         base_type = scope.parent_type.base_type
         if tp_slot.slot_code(scope) != slot_func:
@@ -2225,10 +2225,10 @@ def generate_binop_function(self, scope, slot, code, pos):
         if preprocessor_guard:
             code.putln(preprocessor_guard)
 
-        if slot.left_slot.signature == TypeSlots.binaryfunc:
+        if slot.left_slot.signature in (TypeSlots.binaryfunc, TypeSlots.ibinaryfunc):
             slot_type = 'binaryfunc'
             extra_arg = extra_arg_decl = ''
-        elif slot.left_slot.signature == TypeSlots.ternaryfunc:
+        elif slot.left_slot.signature in (TypeSlots.ternaryfunc, TypeSlots.iternaryfunc):
             slot_type = 'ternaryfunc'
             extra_arg = ', extra_arg'
             extra_arg_decl = ', PyObject* extra_arg'
@@ -2519,17 +2519,17 @@ def generate_typeobj_spec(self, entry, code):
         ext_type = entry.type
         scope = ext_type.scope
 
-        members_slot = TypeSlots.get_slot_by_name("tp_members")
+        members_slot = TypeSlots.get_slot_by_name("tp_members", code.globalstate.directives)
         members_slot.generate_substructure_spec(scope, code)
 
-        buffer_slot = TypeSlots.get_slot_by_name("tp_as_buffer")
+        buffer_slot = TypeSlots.get_slot_by_name("tp_as_buffer", code.globalstate.directives)
         if not buffer_slot.is_empty(scope):
             code.putln("#if !CYTHON_COMPILING_IN_LIMITED_API")
             buffer_slot.generate_substructure(scope, code)
             code.putln("#endif")
 
         code.putln("static PyType_Slot %s_slots[] = {" % ext_type.typeobj_cname)
-        for slot in TypeSlots.slot_table:
+        for slot in TypeSlots.get_slot_table(code.globalstate.directives):
             slot.generate_spec(scope, code)
         code.putln("{0, 0},")
         code.putln("};")
@@ -2543,14 +2543,14 @@ def generate_typeobj_spec(self, entry, code):
         code.putln('"%s.%s",' % (self.full_module_name, classname.replace('"', '')))
         code.putln("sizeof(%s)," % objstruct)
         code.putln("0,")
-        code.putln("%s," % TypeSlots.get_slot_by_name("tp_flags").slot_code(scope))
+        code.putln("%s," % TypeSlots.get_slot_by_name("tp_flags", scope.directives).slot_code(scope))
         code.putln("%s_slots," % ext_type.typeobj_cname)
         code.putln("};")
 
     def generate_typeobj_definition(self, modname, entry, code):
         type = entry.type
         scope = type.scope
-        for suite in TypeSlots.substructures:
+        for suite in TypeSlots.get_slot_table(code.globalstate.directives).substructures:
             suite.generate_substructure(scope, code)
         code.putln("")
         if entry.visibility == 'public':
@@ -2574,7 +2574,7 @@ def generate_typeobj_definition(self, modname, entry, code):
             "sizeof(%s), /*tp_basicsize*/" % objstruct)
         code.putln(
             "0, /*tp_itemsize*/")
-        for slot in TypeSlots.slot_table:
+        for slot in TypeSlots.get_slot_table(code.globalstate.directives):
             slot.generate(scope, code)
         code.putln(
             "};")
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
index aa05a26b884..83a040457e3 100644
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -2417,7 +2417,7 @@ def get_preprocessor_guard(self):
         if not self.entry.is_special:
             return None
         name = self.entry.name
-        slot = TypeSlots.method_name_to_slot.get(name)
+        slot = TypeSlots.get_slot_table(self.local_scope.directives).get_slot_by_method_name(name)
         if not slot:
             return None
         if name == '__long__' and not self.entry.scope.lookup_here('__int__'):
@@ -5335,7 +5335,7 @@ def generate_type_ready_code(entry, code, bases_tuple_cname=None, check_heap_typ
                         UtilityCode.load_cached('ValidateBasesTuple', 'ExtensionTypes.c'))
                     code.put_error_if_neg(entry.pos, "__Pyx_validate_bases_tuple(%s.name, %s, %s)" % (
                         typespec_cname,
-                        TypeSlots.get_slot_by_name("tp_dictoffset").slot_code(scope),
+                        TypeSlots.get_slot_by_name("tp_dictoffset", scope.directives).slot_code(scope),
                         bases_tuple_cname or tuple_temp,
                     ))
 
@@ -5359,7 +5359,7 @@ def generate_type_ready_code(entry, code, bases_tuple_cname=None, check_heap_typ
                     ))
 
             # The buffer interface is not currently supported by PyType_FromSpec().
-            buffer_slot = TypeSlots.get_slot_by_name("tp_as_buffer")
+            buffer_slot = TypeSlots.get_slot_by_name("tp_as_buffer", code.globalstate.directives)
             if not buffer_slot.is_empty(scope):
                 code.putln("#if !CYTHON_COMPILING_IN_LIMITED_API")
                 code.putln("%s->%s = %s;" % (
@@ -5369,7 +5369,8 @@ def generate_type_ready_code(entry, code, bases_tuple_cname=None, check_heap_typ
                 ))
                 # Still need to inherit buffer methods since PyType_Ready() didn't do it for us.
                 for buffer_method_name in ("__getbuffer__", "__releasebuffer__"):
-                    buffer_slot = TypeSlots.get_slot_by_method_name(buffer_method_name)
+                    buffer_slot = TypeSlots.get_slot_table(
+                        code.globalstate.directives). get_slot_by_method_name(buffer_method_name)
                     if buffer_slot.slot_code(scope) == "0" and not TypeSlots.get_base_slot_function(scope, buffer_slot):
                         code.putln("if (!%s->tp_as_buffer->%s &&"
                                    " %s->tp_base->tp_as_buffer &&"
@@ -5405,7 +5406,7 @@ def generate_type_ready_code(entry, code, bases_tuple_cname=None, check_heap_typ
 
             code.putln("#if !CYTHON_COMPILING_IN_LIMITED_API")
             # FIXME: these still need to get initialised even with the limited-API
-            for slot in TypeSlots.slot_table:
+            for slot in TypeSlots.get_slot_table(code.globalstate.directives):
                 slot.generate_dynamic_init_code(scope, code)
             code.putln("#endif")
 
@@ -5450,7 +5451,8 @@ def generate_type_ready_code(entry, code, bases_tuple_cname=None, check_heap_typ
                 is_buffer = func.name in ('__getbuffer__', '__releasebuffer__')
                 if (func.is_special and Options.docstrings and
                         func.wrapperbase_cname and not is_buffer):
-                    slot = TypeSlots.method_name_to_slot.get(func.name)
+                    slot = TypeSlots.get_slot_table(
+                        entry.type.scope.directives).get_slot_by_method_name(func.name)
                     preprocessor_guard = slot.preprocessor_guard_code() if slot else None
                     if preprocessor_guard:
                         code.putln(preprocessor_guard)
diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py
index 712a06a7af6..e18849df9ab 100644
--- a/Cython/Compiler/Symtab.py
+++ b/Cython/Compiler/Symtab.py
@@ -20,7 +20,7 @@
 from .PyrexTypes import py_object_type, unspecified_type
 from .TypeSlots import (
     pyfunction_signature, pymethod_signature, richcmp_special_methods,
-    get_special_method_signature, get_property_accessor_signature)
+    get_slot_table, get_property_accessor_signature)
 from . import Future
 
 from . import Code
@@ -2240,7 +2240,8 @@ def declare_var(self, name, type, pos,
                 error(pos,
                     "C attributes cannot be added in implementation part of"
                     " extension type defined in a pxd")
-            if not self.is_closure_class_scope and get_special_method_signature(name):
+            if (not self.is_closure_class_scope and
+                    get_slot_table(self.directives).get_special_method_signature(name)):
                 error(pos,
                     "The name '%s' is reserved for a special method."
                         % name)
@@ -2312,7 +2313,7 @@ def declare_pyfunction(self, name, pos, allow_redefine=False):
                 "in a future version of Pyrex and Cython. Use __cinit__ instead.")
         entry = self.declare_var(name, py_object_type, pos,
                                  visibility='extern')
-        special_sig = get_special_method_signature(name)
+        special_sig = get_slot_table(self.directives).get_special_method_signature(name)
         if special_sig:
             # Special methods get put in the method table with a particular
             # signature declared in advance.
@@ -2344,7 +2345,8 @@ def declare_cfunction(self, name, type, pos,
                           cname=None, visibility='private', api=0, in_pxd=0,
                           defining=0, modifiers=(), utility_code=None, overridable=False):
         name = self.mangle_class_private_name(name)
-        if get_special_method_signature(name) and not self.parent_type.is_builtin_type:
+        if (get_slot_table(self.directives).get_special_method_signature(name)
+                and not self.parent_type.is_builtin_type):
             error(pos, "Special methods must be declared with 'def', not 'cdef'")
         args = type.args
         if not type.is_static_method:
diff --git a/Cython/Compiler/TypeSlots.py b/Cython/Compiler/TypeSlots.py
index d7b68bbd37d..f99ab0a2177 100644
--- a/Cython/Compiler/TypeSlots.py
+++ b/Cython/Compiler/TypeSlots.py
@@ -354,8 +354,8 @@ class MethodSlot(SlotDescriptor):
     #  method_name  string           The __xxx__ name of the method
     #  alternatives [string]         Alternative list of __xxx__ names for the method
 
-    def __init__(self, signature, slot_name, method_name, fallback=None,
-                 py3=True, py2=True, ifdef=None, inherited=True):
+    def __init__(self, signature, slot_name, method_name, method_name_to_slot,
+                 fallback=None, py3=True, py2=True, ifdef=None, inherited=True):
         SlotDescriptor.__init__(self, slot_name, py3=py3, py2=py2,
                                 ifdef=ifdef, inherited=inherited)
         self.signature = signature
@@ -509,14 +509,14 @@ def spec_value(self, scope):
 
 
 class BinopSlot(SyntheticSlot):
-    def __init__(self, signature, slot_name, left_method, **kargs):
+    def __init__(self, signature, slot_name, left_method, method_name_to_slot, **kargs):
         assert left_method.startswith('__')
         right_method = '__r' + left_method[2:]
         SyntheticSlot.__init__(
                 self, slot_name, [left_method, right_method], "0", is_binop=True, **kargs)
         # MethodSlot causes special method registration.
-        self.left_slot = MethodSlot(signature, "", left_method, **kargs)
-        self.right_slot = MethodSlot(signature, "", right_method, **kargs)
+        self.left_slot = MethodSlot(signature, "", left_method, method_name_to_slot, **kargs)
+        self.right_slot = MethodSlot(signature, "", right_method, method_name_to_slot, **kargs)
 
 
 class RichcmpSlot(MethodSlot):
@@ -570,7 +570,7 @@ class SuiteSlot(SlotDescriptor):
     #
     #  sub_slots   [SlotDescriptor]
 
-    def __init__(self, sub_slots, slot_type, slot_name, ifdef=None):
+    def __init__(self, sub_slots, slot_type, slot_name, substructures, ifdef=None):
         SlotDescriptor.__init__(self, slot_name, ifdef=ifdef)
         self.sub_slots = sub_slots
         self.slot_type = slot_type
@@ -612,8 +612,6 @@ def generate_spec(self, scope, code):
         for slot in self.sub_slots:
             slot.generate_spec(scope, code)
 
-substructures = []   # List of all SuiteSlot instances
-
 class MethodTableSlot(SlotDescriptor):
     #  Slot descriptor for the method table.
 
@@ -633,7 +631,7 @@ def slot_code(self, scope):
 
     def get_member_specs(self, scope):
         return [
-            get_slot_by_name("tp_dictoffset").members_slot_value(scope),
+            get_slot_by_name("tp_dictoffset", scope.directives).members_slot_value(scope),
             #get_slot_by_name("tp_weaklistoffset").spec_value(scope),
         ]
 
@@ -717,12 +715,6 @@ def members_slot_value(self, scope):
             return None
         return '{"__dictoffset__", T_PYSSIZET, %s, READONLY, NULL},' % dict_offset
 
-
-
-# The following dictionary maps __xxx__ method names to slot descriptors.
-
-method_name_to_slot = {}
-
 ## The following slots are (or could be) initialised with an
 ## extern function pointer.
 #
@@ -736,17 +728,6 @@ def members_slot_value(self, scope):
 #
 #------------------------------------------------------------------------------------------
 
-def get_special_method_signature(name):
-    #  Given a method name, if it is a special method,
-    #  return its signature, else return None.
-    slot = method_name_to_slot.get(name)
-    if slot:
-        return slot.signature
-    elif name in richcmp_special_methods:
-        return ibinaryfunc
-    else:
-        return None
-
 
 def get_property_accessor_signature(name):
     #  Return signature of accessor for an extension type
@@ -780,21 +761,16 @@ def get_slot_function(scope, slot):
     return None
 
 
-def get_slot_by_name(slot_name):
+def get_slot_by_name(slot_name, compiler_directives):
     # For now, only search the type struct, no referenced sub-structs.
-    for slot in slot_table:
+    for slot in get_slot_table(compiler_directives).slot_table:
         if slot.slot_name == slot_name:
             return slot
     assert False, "Slot not found: %s" % slot_name
 
 
-def get_slot_by_method_name(method_name):
-    # For now, only search the type struct, no referenced sub-structs.
-    return method_name_to_slot[method_name]
-
-
 def get_slot_code_by_name(scope, slot_name):
-    slot = get_slot_by_name(slot_name)
+    slot = get_slot_by_name(slot_name, scope.directives)
     return slot.slot_code(scope)
 
 
@@ -890,203 +866,268 @@ def get_slot_code_by_name(scope, slot_name):
     '__del__': Signature("T", 'r')
 }
 
-#------------------------------------------------------------------------------------------
-#
-#  Descriptor tables for the slots of the various type object
-#  substructures, in the order they appear in the structure.
-#
-#------------------------------------------------------------------------------------------
 
 PyNumberMethods_Py2only_GUARD = "PY_MAJOR_VERSION < 3 || (CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX < 0x03050000)"
 
-PyNumberMethods = (
-    BinopSlot(binaryfunc, "nb_add", "__add__"),
-    BinopSlot(binaryfunc, "nb_subtract", "__sub__"),
-    BinopSlot(binaryfunc, "nb_multiply", "__mul__"),
-    BinopSlot(binaryfunc, "nb_divide", "__div__", ifdef = PyNumberMethods_Py2only_GUARD),
-    BinopSlot(binaryfunc, "nb_remainder", "__mod__"),
-    BinopSlot(binaryfunc, "nb_divmod", "__divmod__"),
-    BinopSlot(ternaryfunc, "nb_power", "__pow__"),
-    MethodSlot(unaryfunc, "nb_negative", "__neg__"),
-    MethodSlot(unaryfunc, "nb_positive", "__pos__"),
-    MethodSlot(unaryfunc, "nb_absolute", "__abs__"),
-    MethodSlot(inquiry, "nb_bool", "__bool__", py2 = ("nb_nonzero", "__nonzero__")),
-    MethodSlot(unaryfunc, "nb_invert", "__invert__"),
-    BinopSlot(binaryfunc, "nb_lshift", "__lshift__"),
-    BinopSlot(binaryfunc, "nb_rshift", "__rshift__"),
-    BinopSlot(binaryfunc, "nb_and", "__and__"),
-    BinopSlot(binaryfunc, "nb_xor", "__xor__"),
-    BinopSlot(binaryfunc, "nb_or", "__or__"),
-    EmptySlot("nb_coerce", ifdef = PyNumberMethods_Py2only_GUARD),
-    MethodSlot(unaryfunc, "nb_int", "__int__", fallback="__long__"),
-    MethodSlot(unaryfunc, "nb_long", "__long__", fallback="__int__", py3 = "<RESERVED>"),
-    MethodSlot(unaryfunc, "nb_float", "__float__"),
-    MethodSlot(unaryfunc, "nb_oct", "__oct__", ifdef = PyNumberMethods_Py2only_GUARD),
-    MethodSlot(unaryfunc, "nb_hex", "__hex__", ifdef = PyNumberMethods_Py2only_GUARD),
-
-    # Added in release 2.0
-    MethodSlot(ibinaryfunc, "nb_inplace_add", "__iadd__"),
-    MethodSlot(ibinaryfunc, "nb_inplace_subtract", "__isub__"),
-    MethodSlot(ibinaryfunc, "nb_inplace_multiply", "__imul__"),
-    MethodSlot(ibinaryfunc, "nb_inplace_divide", "__idiv__", ifdef = PyNumberMethods_Py2only_GUARD),
-    MethodSlot(ibinaryfunc, "nb_inplace_remainder", "__imod__"),
-    MethodSlot(ibinaryfunc, "nb_inplace_power", "__ipow__"),  # actually ternaryfunc!!!
-    MethodSlot(ibinaryfunc, "nb_inplace_lshift", "__ilshift__"),
-    MethodSlot(ibinaryfunc, "nb_inplace_rshift", "__irshift__"),
-    MethodSlot(ibinaryfunc, "nb_inplace_and", "__iand__"),
-    MethodSlot(ibinaryfunc, "nb_inplace_xor", "__ixor__"),
-    MethodSlot(ibinaryfunc, "nb_inplace_or", "__ior__"),
-
-    # Added in release 2.2
-    # The following require the Py_TPFLAGS_HAVE_CLASS flag
-    BinopSlot(binaryfunc, "nb_floor_divide", "__floordiv__"),
-    BinopSlot(binaryfunc, "nb_true_divide", "__truediv__"),
-    MethodSlot(ibinaryfunc, "nb_inplace_floor_divide", "__ifloordiv__"),
-    MethodSlot(ibinaryfunc, "nb_inplace_true_divide", "__itruediv__"),
-
-    # Added in release 2.5
-    MethodSlot(unaryfunc, "nb_index", "__index__"),
-
-    # Added in release 3.5
-    BinopSlot(binaryfunc, "nb_matrix_multiply", "__matmul__", ifdef="PY_VERSION_HEX >= 0x03050000"),
-    MethodSlot(ibinaryfunc, "nb_inplace_matrix_multiply", "__imatmul__", ifdef="PY_VERSION_HEX >= 0x03050000"),
-)
-
-PySequenceMethods = (
-    MethodSlot(lenfunc, "sq_length", "__len__"),
-    EmptySlot("sq_concat"),  # nb_add used instead
-    EmptySlot("sq_repeat"),  # nb_multiply used instead
-    SyntheticSlot("sq_item", ["__getitem__"], "0"),    #EmptySlot("sq_item"),   # mp_subscript used instead
-    MethodSlot(ssizessizeargfunc, "sq_slice", "__getslice__"),
-    EmptySlot("sq_ass_item"),  # mp_ass_subscript used instead
-    SyntheticSlot("sq_ass_slice", ["__setslice__", "__delslice__"], "0"),
-    MethodSlot(cmpfunc, "sq_contains", "__contains__"),
-    EmptySlot("sq_inplace_concat"),  # nb_inplace_add used instead
-    EmptySlot("sq_inplace_repeat"),  # nb_inplace_multiply used instead
-)
-
-PyMappingMethods = (
-    MethodSlot(lenfunc, "mp_length", "__len__"),
-    MethodSlot(objargfunc, "mp_subscript", "__getitem__"),
-    SyntheticSlot("mp_ass_subscript", ["__setitem__", "__delitem__"], "0"),
-)
-
-PyBufferProcs = (
-    MethodSlot(readbufferproc, "bf_getreadbuffer", "__getreadbuffer__", py3 = False),
-    MethodSlot(writebufferproc, "bf_getwritebuffer", "__getwritebuffer__", py3 = False),
-    MethodSlot(segcountproc, "bf_getsegcount", "__getsegcount__", py3 = False),
-    MethodSlot(charbufferproc, "bf_getcharbuffer", "__getcharbuffer__", py3 = False),
-
-    MethodSlot(getbufferproc, "bf_getbuffer", "__getbuffer__"),
-    MethodSlot(releasebufferproc, "bf_releasebuffer", "__releasebuffer__")
-)
-
-PyAsyncMethods = (
-    MethodSlot(unaryfunc, "am_await", "__await__"),
-    MethodSlot(unaryfunc, "am_aiter", "__aiter__"),
-    MethodSlot(unaryfunc, "am_anext", "__anext__"),
-)
-
 #------------------------------------------------------------------------------------------
 #
 #  The main slot table. This table contains descriptors for all the
 #  top-level type slots, beginning with tp_dealloc, in the order they
 #  appear in the type object.
 #
+# It depends on some compiler directives (currently c_api_binop_methods), so the
+# slot tables for each set of compiler directives are generated lazily and put in
+# the _slot_table_dict
+#
 #------------------------------------------------------------------------------------------
 
-slot_table = (
-    ConstructorSlot("tp_dealloc", '__dealloc__'),
-    EmptySlot("tp_print", ifdef="PY_VERSION_HEX < 0x030800b4"),
-    EmptySlot("tp_vectorcall_offset", ifdef="PY_VERSION_HEX >= 0x030800b4"),
-    EmptySlot("tp_getattr"),
-    EmptySlot("tp_setattr"),
-
-    # tp_compare (Py2) / tp_reserved (Py3<3.5) / tp_as_async (Py3.5+) is always used as tp_as_async in Py3
-    MethodSlot(cmpfunc, "tp_compare", "__cmp__", ifdef="PY_MAJOR_VERSION < 3"),
-    SuiteSlot(PyAsyncMethods, "__Pyx_PyAsyncMethodsStruct", "tp_as_async", ifdef="PY_MAJOR_VERSION >= 3"),
-
-    MethodSlot(reprfunc, "tp_repr", "__repr__"),
-
-    SuiteSlot(PyNumberMethods, "PyNumberMethods", "tp_as_number"),
-    SuiteSlot(PySequenceMethods, "PySequenceMethods", "tp_as_sequence"),
-    SuiteSlot(PyMappingMethods, "PyMappingMethods", "tp_as_mapping"),
-
-    MethodSlot(hashfunc, "tp_hash", "__hash__", inherited=False),    # Py3 checks for __richcmp__
-    MethodSlot(callfunc, "tp_call", "__call__"),
-    MethodSlot(reprfunc, "tp_str", "__str__"),
-
-    SyntheticSlot("tp_getattro", ["__getattr__","__getattribute__"], "0"),  #"PyObject_GenericGetAttr"),
-    SyntheticSlot("tp_setattro", ["__setattr__", "__delattr__"], "0"),  #"PyObject_GenericSetAttr"),
-
-    SuiteSlot(PyBufferProcs, "PyBufferProcs", "tp_as_buffer"),
-
-    TypeFlagsSlot("tp_flags"),
-    DocStringSlot("tp_doc"),
-
-    GCDependentSlot("tp_traverse"),
-    GCClearReferencesSlot("tp_clear"),
-
-    RichcmpSlot(richcmpfunc, "tp_richcompare", "__richcmp__", inherited=False),  # Py3 checks for __hash__
-
-    EmptySlot("tp_weaklistoffset"),
-
-    MethodSlot(getiterfunc, "tp_iter", "__iter__"),
-    MethodSlot(iternextfunc, "tp_iternext", "__next__"),
+class SlotTable(object):
+    def __init__(self, old_binops):
+        # The following dictionary maps __xxx__ method names to slot descriptors.
+        method_name_to_slot = {}
+        self._get_slot_by_method_name = method_name_to_slot.get
+        self.substructures = []   # List of all SuiteSlot instances
+
+        bf = binaryfunc if old_binops else ibinaryfunc
+        tf = ternaryfunc if old_binops else iternaryfunc
+
+        #  Descriptor tables for the slots of the various type object
+        #  substructures, in the order they appear in the structure.
+        self.PyNumberMethods = (
+            BinopSlot(bf, "nb_add", "__add__", method_name_to_slot),
+            BinopSlot(bf, "nb_subtract", "__sub__", method_name_to_slot),
+            BinopSlot(bf, "nb_multiply", "__mul__", method_name_to_slot),
+            BinopSlot(bf, "nb_divide", "__div__", method_name_to_slot,
+                      ifdef = PyNumberMethods_Py2only_GUARD),
+            BinopSlot(bf, "nb_remainder", "__mod__", method_name_to_slot),
+            BinopSlot(bf, "nb_divmod", "__divmod__", method_name_to_slot),
+            BinopSlot(tf, "nb_power", "__pow__", method_name_to_slot),
+            MethodSlot(unaryfunc, "nb_negative", "__neg__", method_name_to_slot),
+            MethodSlot(unaryfunc, "nb_positive", "__pos__", method_name_to_slot),
+            MethodSlot(unaryfunc, "nb_absolute", "__abs__", method_name_to_slot),
+            MethodSlot(inquiry, "nb_bool", "__bool__", method_name_to_slot,
+                       py2 = ("nb_nonzero", "__nonzero__")),
+            MethodSlot(unaryfunc, "nb_invert", "__invert__", method_name_to_slot),
+            BinopSlot(bf, "nb_lshift", "__lshift__", method_name_to_slot),
+            BinopSlot(bf, "nb_rshift", "__rshift__", method_name_to_slot),
+            BinopSlot(bf, "nb_and", "__and__", method_name_to_slot),
+            BinopSlot(bf, "nb_xor", "__xor__", method_name_to_slot),
+            BinopSlot(bf, "nb_or", "__or__", method_name_to_slot),
+            EmptySlot("nb_coerce", ifdef = PyNumberMethods_Py2only_GUARD),
+            MethodSlot(unaryfunc, "nb_int", "__int__", method_name_to_slot, fallback="__long__"),
+            MethodSlot(unaryfunc, "nb_long", "__long__", method_name_to_slot,
+                       fallback="__int__", py3 = "<RESERVED>"),
+            MethodSlot(unaryfunc, "nb_float", "__float__", method_name_to_slot),
+            MethodSlot(unaryfunc, "nb_oct", "__oct__", method_name_to_slot,
+                       ifdef = PyNumberMethods_Py2only_GUARD),
+            MethodSlot(unaryfunc, "nb_hex", "__hex__", method_name_to_slot,
+                       ifdef = PyNumberMethods_Py2only_GUARD),
+
+            # Added in release 2.0
+            MethodSlot(ibinaryfunc, "nb_inplace_add", "__iadd__", method_name_to_slot),
+            MethodSlot(ibinaryfunc, "nb_inplace_subtract", "__isub__", method_name_to_slot),
+            MethodSlot(ibinaryfunc, "nb_inplace_multiply", "__imul__", method_name_to_slot),
+            MethodSlot(ibinaryfunc, "nb_inplace_divide", "__idiv__", method_name_to_slot,
+                       ifdef = PyNumberMethods_Py2only_GUARD),
+            MethodSlot(ibinaryfunc, "nb_inplace_remainder", "__imod__", method_name_to_slot),
+            MethodSlot(ibinaryfunc, "nb_inplace_power", "__ipow__",
+                       method_name_to_slot),  # actually ternaryfunc!!!
+            MethodSlot(ibinaryfunc, "nb_inplace_lshift", "__ilshift__", method_name_to_slot),
+            MethodSlot(ibinaryfunc, "nb_inplace_rshift", "__irshift__", method_name_to_slot),
+            MethodSlot(ibinaryfunc, "nb_inplace_and", "__iand__", method_name_to_slot),
+            MethodSlot(ibinaryfunc, "nb_inplace_xor", "__ixor__", method_name_to_slot),
+            MethodSlot(ibinaryfunc, "nb_inplace_or", "__ior__", method_name_to_slot),
+
+            # Added in release 2.2
+            # The following require the Py_TPFLAGS_HAVE_CLASS flag
+            BinopSlot(binaryfunc, "nb_floor_divide", "__floordiv__", method_name_to_slot),
+            BinopSlot(binaryfunc, "nb_true_divide", "__truediv__", method_name_to_slot),
+            MethodSlot(ibinaryfunc, "nb_inplace_floor_divide", "__ifloordiv__", method_name_to_slot),
+            MethodSlot(ibinaryfunc, "nb_inplace_true_divide", "__itruediv__", method_name_to_slot),
+
+            # Added in release 2.5
+            MethodSlot(unaryfunc, "nb_index", "__index__", method_name_to_slot),
+
+            # Added in release 3.5
+            BinopSlot(binaryfunc, "nb_matrix_multiply", "__matmul__", method_name_to_slot,
+                      ifdef="PY_VERSION_HEX >= 0x03050000"),
+            MethodSlot(ibinaryfunc, "nb_inplace_matrix_multiply", "__imatmul__", method_name_to_slot,
+                       ifdef="PY_VERSION_HEX >= 0x03050000"),
+        )
+
+        self.PySequenceMethods = (
+            MethodSlot(lenfunc, "sq_length", "__len__", method_name_to_slot),
+            EmptySlot("sq_concat"),  # nb_add used instead
+            EmptySlot("sq_repeat"),  # nb_multiply used instead
+            SyntheticSlot("sq_item", ["__getitem__"], "0"),    #EmptySlot("sq_item"),   # mp_subscript used instead
+            MethodSlot(ssizessizeargfunc, "sq_slice", "__getslice__", method_name_to_slot),
+            EmptySlot("sq_ass_item"),  # mp_ass_subscript used instead
+            SyntheticSlot("sq_ass_slice", ["__setslice__", "__delslice__"], "0"),
+            MethodSlot(cmpfunc, "sq_contains", "__contains__", method_name_to_slot),
+            EmptySlot("sq_inplace_concat"),  # nb_inplace_add used instead
+            EmptySlot("sq_inplace_repeat"),  # nb_inplace_multiply used instead
+        )
+
+        self.PyMappingMethods = (
+            MethodSlot(lenfunc, "mp_length", "__len__", method_name_to_slot),
+            MethodSlot(objargfunc, "mp_subscript", "__getitem__", method_name_to_slot),
+            SyntheticSlot("mp_ass_subscript", ["__setitem__", "__delitem__"], "0"),
+        )
+
+        self.PyBufferProcs = (
+            MethodSlot(readbufferproc, "bf_getreadbuffer", "__getreadbuffer__", method_name_to_slot,
+                       py3 = False),
+            MethodSlot(writebufferproc, "bf_getwritebuffer", "__getwritebuffer__", method_name_to_slot,
+                       py3 = False),
+            MethodSlot(segcountproc, "bf_getsegcount", "__getsegcount__", method_name_to_slot,
+                       py3 = False),
+            MethodSlot(charbufferproc, "bf_getcharbuffer", "__getcharbuffer__", method_name_to_slot,
+                       py3 = False),
+
+            MethodSlot(getbufferproc, "bf_getbuffer", "__getbuffer__", method_name_to_slot),
+            MethodSlot(releasebufferproc, "bf_releasebuffer", "__releasebuffer__", method_name_to_slot)
+        )
+
+        self.PyAsyncMethods = (
+            MethodSlot(unaryfunc, "am_await", "__await__", method_name_to_slot),
+            MethodSlot(unaryfunc, "am_aiter", "__aiter__", method_name_to_slot),
+            MethodSlot(unaryfunc, "am_anext", "__anext__", method_name_to_slot),
+        )
+
+        self.slot_table = (
+            ConstructorSlot("tp_dealloc", '__dealloc__'),
+            EmptySlot("tp_print", ifdef="PY_VERSION_HEX < 0x030800b4"),
+            EmptySlot("tp_vectorcall_offset", ifdef="PY_VERSION_HEX >= 0x030800b4"),
+            EmptySlot("tp_getattr"),
+            EmptySlot("tp_setattr"),
+
+            # tp_compare (Py2) / tp_reserved (Py3<3.5) / tp_as_async (Py3.5+) is always used as tp_as_async in Py3
+            MethodSlot(cmpfunc, "tp_compare", "__cmp__", method_name_to_slot, ifdef="PY_MAJOR_VERSION < 3"),
+            SuiteSlot(self. PyAsyncMethods, "__Pyx_PyAsyncMethodsStruct", "tp_as_async",
+                      self.substructures, ifdef="PY_MAJOR_VERSION >= 3"),
+
+            MethodSlot(reprfunc, "tp_repr", "__repr__", method_name_to_slot),
+
+            SuiteSlot(self.PyNumberMethods, "PyNumberMethods", "tp_as_number", self.substructures),
+            SuiteSlot(self.PySequenceMethods, "PySequenceMethods", "tp_as_sequence", self.substructures),
+            SuiteSlot(self.PyMappingMethods, "PyMappingMethods", "tp_as_mapping", self.substructures),
+
+            MethodSlot(hashfunc, "tp_hash", "__hash__", method_name_to_slot,
+                       inherited=False),    # Py3 checks for __richcmp__
+            MethodSlot(callfunc, "tp_call", "__call__", method_name_to_slot),
+            MethodSlot(reprfunc, "tp_str", "__str__", method_name_to_slot),
+
+            SyntheticSlot("tp_getattro", ["__getattr__","__getattribute__"], "0"),  #"PyObject_GenericGetAttr"),
+            SyntheticSlot("tp_setattro", ["__setattr__", "__delattr__"], "0"),  #"PyObject_GenericSetAttr"),
+
+            SuiteSlot(self.PyBufferProcs, "PyBufferProcs", "tp_as_buffer", self.substructures),
+
+            TypeFlagsSlot("tp_flags"),
+            DocStringSlot("tp_doc"),
+
+            GCDependentSlot("tp_traverse"),
+            GCClearReferencesSlot("tp_clear"),
+
+            RichcmpSlot(richcmpfunc, "tp_richcompare", "__richcmp__", method_name_to_slot,
+                        inherited=False),  # Py3 checks for __hash__
+
+            EmptySlot("tp_weaklistoffset"),
+
+            MethodSlot(getiterfunc, "tp_iter", "__iter__", method_name_to_slot),
+            MethodSlot(iternextfunc, "tp_iternext", "__next__", method_name_to_slot),
+
+            MethodTableSlot("tp_methods"),
+            MemberTableSlot("tp_members"),
+            GetSetSlot("tp_getset"),
+
+            BaseClassSlot("tp_base"),  #EmptySlot("tp_base"),
+            EmptySlot("tp_dict"),
+
+            SyntheticSlot("tp_descr_get", ["__get__"], "0"),
+            SyntheticSlot("tp_descr_set", ["__set__", "__delete__"], "0"),
+
+            DictOffsetSlot("tp_dictoffset", ifdef="!CYTHON_USE_TYPE_SPECS"),  # otherwise set via "__dictoffset__" member
+
+            MethodSlot(initproc, "tp_init", "__init__", method_name_to_slot),
+            EmptySlot("tp_alloc"),  #FixedSlot("tp_alloc", "PyType_GenericAlloc"),
+            ConstructorSlot("tp_new", "__cinit__"),
+            EmptySlot("tp_free"),
+
+            EmptySlot("tp_is_gc"),
+            EmptySlot("tp_bases"),
+            EmptySlot("tp_mro"),
+            EmptySlot("tp_cache"),
+            EmptySlot("tp_subclasses"),
+            EmptySlot("tp_weaklist"),
+            EmptySlot("tp_del"),
+            EmptySlot("tp_version_tag"),
+            EmptySlot("tp_finalize", ifdef="PY_VERSION_HEX >= 0x030400a1"),
+            EmptySlot("tp_vectorcall", ifdef="PY_VERSION_HEX >= 0x030800b1"),
+            EmptySlot("tp_print", ifdef="PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000"),
+            # PyPy specific extension - only here to avoid C compiler warnings.
+            EmptySlot("tp_pypy_flags", ifdef="CYTHON_COMPILING_IN_PYPY && PYPY_VERSION_NUM+0 >= 0x06000000"),
+        )
+
+        #------------------------------------------------------------------------------------------
+        #
+        #  Descriptors for special methods which don't appear directly
+        #  in the type object or its substructures. These methods are
+        #  called from slot functions synthesized by Cython.
+        #
+        #------------------------------------------------------------------------------------------
+
+        MethodSlot(initproc, "", "__cinit__", method_name_to_slot)
+        MethodSlot(destructor, "", "__dealloc__", method_name_to_slot)
+        MethodSlot(objobjargproc, "", "__setitem__", method_name_to_slot)
+        MethodSlot(objargproc, "", "__delitem__", method_name_to_slot)
+        MethodSlot(ssizessizeobjargproc, "", "__setslice__", method_name_to_slot)
+        MethodSlot(ssizessizeargproc, "", "__delslice__", method_name_to_slot)
+        MethodSlot(getattrofunc, "", "__getattr__", method_name_to_slot)
+        MethodSlot(getattrofunc, "", "__getattribute__", method_name_to_slot)
+        MethodSlot(setattrofunc, "", "__setattr__", method_name_to_slot)
+        MethodSlot(delattrofunc, "", "__delattr__", method_name_to_slot)
+        MethodSlot(descrgetfunc, "", "__get__", method_name_to_slot)
+        MethodSlot(descrsetfunc, "", "__set__", method_name_to_slot)
+        MethodSlot(descrdelfunc, "", "__delete__", method_name_to_slot)
+
+    def get_special_method_signature(self, name):
+        #  Given a method name, if it is a special method,
+        #  return its signature, else return None.
+        slot = self._get_slot_by_method_name(name)
+        if slot:
+            return slot.signature
+        elif name in richcmp_special_methods:
+            return ibinaryfunc
+        else:
+            return None
 
-    MethodTableSlot("tp_methods"),
-    MemberTableSlot("tp_members"),
-    GetSetSlot("tp_getset"),
+    def get_slot_by_method_name(self, method_name):
+        # For now, only search the type struct, no referenced sub-structs.
+        return self._get_slot_by_method_name(method_name)
 
-    BaseClassSlot("tp_base"),  #EmptySlot("tp_base"),
-    EmptySlot("tp_dict"),
+    def __iter__(self):
+        # make it easier to iterate over all the slots
+        return iter(self.slot_table)
 
-    SyntheticSlot("tp_descr_get", ["__get__"], "0"),
-    SyntheticSlot("tp_descr_set", ["__set__", "__delete__"], "0"),
 
-    DictOffsetSlot("tp_dictoffset", ifdef="!CYTHON_USE_TYPE_SPECS"),  # otherwise set via "__dictoffset__" member
+_slot_table_dict = {}
 
-    MethodSlot(initproc, "tp_init", "__init__"),
-    EmptySlot("tp_alloc"),  #FixedSlot("tp_alloc", "PyType_GenericAlloc"),
-    ConstructorSlot("tp_new", "__cinit__"),
-    EmptySlot("tp_free"),
+def get_slot_table(compiler_directives):
+    if not compiler_directives:
+        # fetch default directives here since the builtin type classes don't have
+        # directives set
+        from .Options import get_directive_defaults
+        compiler_directives = get_directive_defaults()
 
-    EmptySlot("tp_is_gc"),
-    EmptySlot("tp_bases"),
-    EmptySlot("tp_mro"),
-    EmptySlot("tp_cache"),
-    EmptySlot("tp_subclasses"),
-    EmptySlot("tp_weaklist"),
-    EmptySlot("tp_del"),
-    EmptySlot("tp_version_tag"),
-    EmptySlot("tp_finalize", ifdef="PY_VERSION_HEX >= 0x030400a1"),
-    EmptySlot("tp_vectorcall", ifdef="PY_VERSION_HEX >= 0x030800b1"),
-    EmptySlot("tp_print", ifdef="PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000"),
-    # PyPy specific extension - only here to avoid C compiler warnings.
-    EmptySlot("tp_pypy_flags", ifdef="CYTHON_COMPILING_IN_PYPY && PYPY_VERSION_NUM+0 >= 0x06000000"),
-)
+    old_binops = compiler_directives['c_api_binop_methods']
+    key = (old_binops,)
+    if key not in _slot_table_dict:
+        _slot_table_dict[key] = SlotTable(old_binops=old_binops)
+    return _slot_table_dict[key]
 
-#------------------------------------------------------------------------------------------
-#
-#  Descriptors for special methods which don't appear directly
-#  in the type object or its substructures. These methods are
-#  called from slot functions synthesized by Cython.
-#
-#------------------------------------------------------------------------------------------
 
-MethodSlot(initproc, "", "__cinit__")
-MethodSlot(destructor, "", "__dealloc__")
-MethodSlot(objobjargproc, "", "__setitem__")
-MethodSlot(objargproc, "", "__delitem__")
-MethodSlot(ssizessizeobjargproc, "", "__setslice__")
-MethodSlot(ssizessizeargproc, "", "__delslice__")
-MethodSlot(getattrofunc, "", "__getattr__")
-MethodSlot(getattrofunc, "", "__getattribute__")
-MethodSlot(setattrofunc, "", "__setattr__")
-MethodSlot(delattrofunc, "", "__delattr__")
-MethodSlot(descrgetfunc, "", "__get__")
-MethodSlot(descrsetfunc, "", "__set__")
-MethodSlot(descrdelfunc, "", "__delete__")
+# Populate "special_method_names" based on the default directives (so it can always be accessed quickly).
+special_method_names = set(get_slot_table(compiler_directives=None))
 
 
 # Method flags for python-exposed methods.
diff --git a/Cython/Compiler/Visitor.py b/Cython/Compiler/Visitor.py
index 3181ed448e8..6b13287a74a 100644
--- a/Cython/Compiler/Visitor.py
+++ b/Cython/Compiler/Visitor.py
@@ -675,7 +675,7 @@ def _dispatch_to_method_handler(self, attr_name, self_arg,
         method_handler = self._find_handler(
             "method_%s_%s" % (type_name, attr_name), kwargs)
         if method_handler is None:
-            if (attr_name in TypeSlots.method_name_to_slot
+            if (attr_name in TypeSlots.special_method_names
                     or attr_name in ['__new__', '__class__']):
                 method_handler = self._find_handler(
                     "slot%s" % attr_name, kwargs)
diff --git a/docs/src/userguide/migrating_to_cy30.rst b/docs/src/userguide/migrating_to_cy30.rst
index 1dd29a9a2a3..357132887d7 100644
--- a/docs/src/userguide/migrating_to_cy30.rst
+++ b/docs/src/userguide/migrating_to_cy30.rst
@@ -157,3 +157,18 @@ Here ``Base.__bar`` is mangled to ``_Base__bar`` and ``Derived.__bar``
 to ``_Derived__bar``. Therefore ``call_bar`` will always call 
 ``_Base__bar``. This matches established Python behaviour and applies
 for ``def``, ``cdef`` and ``cpdef`` methods and attributes.
+
+Arithmetic special methods
+==========================
+
+The behaviour of arithmetic special methods (for example ``__add__``
+and ``__pow__``) of cdef classes has changed in Cython 3.0. They now 
+support separate "reversed" versions of these methods (e.g. 
+``__radd__``, ``__rpow__``) that behave like in pure Python.
+The main incompatible change is that the type of the first operand
+(usually ``__self__``) is now assumed to be that of the defining class,
+rather than relying on the user to test and cast the type of each operand.
+
+The old behaviour can be restored with the 
+:ref:`directive <compiler-directives>` ``c_api_binop_methods=True``.
+More details are given in :ref:`arithmetic_methods`.
diff --git a/tests/run/binop_reverse_methods_GH2056.pyx b/tests/run/binop_reverse_methods_GH2056.pyx
index bce2f1df127..4938f0d154e 100644
--- a/tests/run/binop_reverse_methods_GH2056.pyx
+++ b/tests/run/binop_reverse_methods_GH2056.pyx
@@ -37,25 +37,29 @@ class Base(object):
         self.implemented = implemented
 
     def __add__(self, other):
-        if (<Base>self).implemented:
+        assert cython.typeof(self) == "Base"
+        if self.implemented:
             return "Base.__add__(%s, %s)" % (self, other)
         else:
             return NotImplemented
 
     def __radd__(self, other):
-        if (<Base>self).implemented:
+        assert cython.typeof(self) == "Base"
+        if self.implemented:
             return "Base.__radd__(%s, %s)" % (self, other)
         else:
             return NotImplemented
 
     def __pow__(self, other, mod):
-        if (<Base>self).implemented:
+        assert cython.typeof(self) == "Base"
+        if self.implemented:
             return "Base.__pow__(%s, %s, %s)" % (self, other, mod)
         else:
             return NotImplemented
 
     def __rpow__(self, other, mod):
-        if (<Base>self).implemented:
+        assert cython.typeof(self) == "Base"
+        if self.implemented:
             return "Base.__rpow__(%s, %s, %s)" % (self, other, mod)
         else:
             return NotImplemented
@@ -94,7 +98,8 @@ class OverloadLeft(Base):
         self.derived_implemented = implemented
 
     def __add__(self, other):
-        if (<OverloadLeft>self).derived_implemented:
+        assert cython.typeof(self) == "OverloadLeft"
+        if self.derived_implemented:
             return "OverloadLeft.__add__(%s, %s)" % (self, other)
         else:
             return NotImplemented
@@ -130,7 +135,8 @@ class OverloadRight(Base):
         self.derived_implemented = implemented
 
     def __radd__(self, other):
-        if (<OverloadRight>self).derived_implemented:
+        assert cython.typeof(self) == "OverloadRight"
+        if self.derived_implemented:
             return "OverloadRight.__radd__(%s, %s)" % (self, other)
         else:
             return NotImplemented
@@ -166,6 +172,7 @@ class OverloadCApi(Base):
         self.derived_implemented = derived_implemented
 
     def __add__(self, other):
+        assert cython.typeof(self) != "OverloadCApi"  # should be untyped
         if isinstance(self, OverloadCApi):
             derived_implemented = (<OverloadCApi>self).derived_implemented
         else:


Code review comments:
diff_hunk: @@ -5369,7 +5369,8 @@ def generate_type_ready_code(entry, code, bases_tuple_cname=None, check_heap_typ
                 ))
                 # Still need to inherit buffer methods since PyType_Ready() didn't do it for us.
                 for buffer_method_name in ("__getbuffer__", "__releasebuffer__"):
-                    buffer_slot = TypeSlots.get_slot_by_method_name(buffer_method_name)
+                    buffer_slot = TypeSlots.get_slot_table(
+                        code.globalstate.directives). get_slot_by_method_name(buffer_method_name)
path: Cython/Compiler/Nodes.py
text: @user1:
```suggestion
                        code.globalstate.directives).get_slot_by_method_name(buffer_method_name)
```