# # Tables describing slots in the CPython type object # and associated know-how. # from __future__ import absolute_import from . import Naming from . import PyrexTypes from .Errors import error import copy invisible = ['__cinit__', '__dealloc__', '__richcmp__', '__nonzero__', '__bool__'] richcmp_special_methods = ['__eq__', '__ne__', '__lt__', '__gt__', '__le__', '__ge__'] class Signature(object): # Method slot signature descriptor. # # has_dummy_arg boolean # has_generic_args boolean # fixed_arg_format string # ret_format string # error_value string # use_fastcall boolean # # The formats are strings made up of the following # characters: # # 'O' Python object # 'T' Python object of the type of 'self' # 'v' void # 'p' void * # 'P' void ** # 'i' int # 'b' bint # 'I' int * # 'l' long # 'f' float # 'd' double # 'h' Py_hash_t # 'z' Py_ssize_t # 'Z' Py_ssize_t * # 's' char * # 'S' char ** # 'r' int used only to signal exception # 'B' Py_buffer * # '-' dummy 'self' argument (not used) # '*' rest of args passed as generic Python # arg tuple and kw dict (must be last # char in format string) # '?' optional object arg (currently for pow only) format_map = { 'O': PyrexTypes.py_object_type, 'v': PyrexTypes.c_void_type, 'p': PyrexTypes.c_void_ptr_type, 'P': PyrexTypes.c_void_ptr_ptr_type, 'i': PyrexTypes.c_int_type, 'b': PyrexTypes.c_bint_type, 'I': PyrexTypes.c_int_ptr_type, 'l': PyrexTypes.c_long_type, 'f': PyrexTypes.c_float_type, 'd': PyrexTypes.c_double_type, 'h': PyrexTypes.c_py_hash_t_type, 'z': PyrexTypes.c_py_ssize_t_type, 'Z': PyrexTypes.c_py_ssize_t_ptr_type, 's': PyrexTypes.c_char_ptr_type, 'S': PyrexTypes.c_char_ptr_ptr_type, 'r': PyrexTypes.c_returncode_type, 'B': PyrexTypes.c_py_buffer_ptr_type, '?': PyrexTypes.py_object_type # 'T', '-' and '*' are handled otherwise # and are not looked up in here } type_to_format_map = dict( (type_, format_) for format_, type_ in format_map.items()) error_value_map = { 'O': "NULL", 'T': "NULL", 'i': "-1", 'b': "-1", 'l': "-1", 'r': "-1", 'h': "-1", 'z': "-1", } # Use METH_FASTCALL instead of METH_VARARGS use_fastcall = False def __init__(self, arg_format, ret_format, nogil=False): self.has_dummy_arg = False self.has_generic_args = False self.optional_object_arg_count = 0 if arg_format[:1] == '-': self.has_dummy_arg = True arg_format = arg_format[1:] if arg_format[-1:] == '*': self.has_generic_args = True arg_format = arg_format[:-1] if arg_format[-1:] == '?': self.optional_object_arg_count += 1 self.fixed_arg_format = arg_format self.ret_format = ret_format self.error_value = self.error_value_map.get(ret_format, None) self.exception_check = ret_format != 'r' and self.error_value is not None self.is_staticmethod = False self.nogil = nogil def __repr__(self): return '' % ( self.ret_format, ', '.join(self.fixed_arg_format), '*' if self.has_generic_args else '') def min_num_fixed_args(self): return self.max_num_fixed_args() - self.optional_object_arg_count def max_num_fixed_args(self): return len(self.fixed_arg_format) def is_self_arg(self, i): # argument is 'self' for methods or 'class' for classmethods return self.fixed_arg_format[i] == 'T' def returns_self_type(self): # return type is same as 'self' argument type return self.ret_format == 'T' def fixed_arg_type(self, i): return self.format_map[self.fixed_arg_format[i]] def return_type(self): return self.format_map[self.ret_format] def format_from_type(self, arg_type): if arg_type.is_pyobject: arg_type = PyrexTypes.py_object_type return self.type_to_format_map[arg_type] def exception_value(self): return self.error_value_map.get(self.ret_format) def function_type(self, self_arg_override=None): # Construct a C function type descriptor for this signature args = [] for i in range(self.max_num_fixed_args()): if self_arg_override is not None and self.is_self_arg(i): assert isinstance(self_arg_override, PyrexTypes.CFuncTypeArg) args.append(self_arg_override) else: arg_type = self.fixed_arg_type(i) args.append(PyrexTypes.CFuncTypeArg("", arg_type, None)) if self_arg_override is not None and self.returns_self_type(): ret_type = self_arg_override.type else: ret_type = self.return_type() exc_value = self.exception_value() return PyrexTypes.CFuncType( ret_type, args, exception_value=exc_value, exception_check=self.exception_check, nogil=self.nogil) def method_flags(self): if self.ret_format == "O": full_args = self.fixed_arg_format if self.has_dummy_arg: full_args = "O" + full_args if full_args in ["O", "T"]: if not self.has_generic_args: return [method_noargs] elif self.use_fastcall: return [method_fastcall, method_keywords] else: return [method_varargs, method_keywords] elif full_args in ["OO", "TO"] and not self.has_generic_args: return [method_onearg] if self.is_staticmethod: if self.use_fastcall: return [method_fastcall, method_keywords] else: return [method_varargs, method_keywords] return None def method_function_type(self): # Return the C function type mflags = self.method_flags() kw = "WithKeywords" if (method_keywords in mflags) else "" for m in mflags: if m == method_noargs or m == method_onearg: return "PyCFunction" if m == method_varargs: return "PyCFunction" + kw if m == method_fastcall: return "__Pyx_PyCFunction_FastCall" + kw return None def with_fastcall(self): # Return a copy of this Signature with use_fastcall=True sig = copy.copy(self) sig.use_fastcall = True return sig @property def fastvar(self): # Used to select variants of functions, one dealing with METH_VARARGS # and one dealing with __Pyx_METH_FASTCALL if self.use_fastcall: return "FASTCALL" else: return "VARARGS" class SlotDescriptor(object): # Abstract base class for type slot descriptors. # # slot_name string Member name of the slot in the type object # is_initialised_dynamically Is initialised by code in the module init function # is_inherited Is inherited by subtypes (see PyType_Ready()) # py3 Indicates presence of slot in Python 3 # py2 Indicates presence of slot in Python 2 # ifdef Full #ifdef string that slot is wrapped in. Using this causes py3, py2 and flags to be ignored.) # used_ifdef Full #ifdef string that the slot value is wrapped in (otherwise it is assigned NULL) # Unlike "ifdef" the slot is defined and this just controls if it receives a value def __init__(self, slot_name, dynamic=False, inherited=False, py3=True, py2=True, ifdef=None, is_binop=False, used_ifdef=None): self.slot_name = slot_name self.is_initialised_dynamically = dynamic self.is_inherited = inherited self.ifdef = ifdef self.used_ifdef = used_ifdef self.py3 = py3 self.py2 = py2 self.is_binop = is_binop def slot_code(self, scope): raise NotImplementedError() def spec_value(self, scope): return self.slot_code(scope) def preprocessor_guard_code(self): ifdef = self.ifdef py2 = self.py2 py3 = self.py3 guard = None if ifdef: guard = "#if %s" % ifdef elif not py3 or py3 == '': guard = "#if PY_MAJOR_VERSION < 3" elif not py2: guard = "#if PY_MAJOR_VERSION >= 3" return guard def generate_spec(self, scope, code): if self.is_initialised_dynamically: return value = self.spec_value(scope) if value == "0": return preprocessor_guard = self.preprocessor_guard_code() if not preprocessor_guard: if self.py3 and self.slot_name.startswith('bf_'): # The buffer protocol requires Limited API 3.11, so check if the spec slots are available. preprocessor_guard = "#if defined(Py_%s)" % self.slot_name if preprocessor_guard: code.putln(preprocessor_guard) code.putln("{Py_%s, (void *)%s}," % (self.slot_name, value)) if preprocessor_guard: code.putln("#endif") def generate(self, scope, code): preprocessor_guard = self.preprocessor_guard_code() if preprocessor_guard: code.putln(preprocessor_guard) end_pypy_guard = False if self.is_initialised_dynamically: value = "0" else: value = self.slot_code(scope) if value == "0" and self.is_inherited: # PyPy currently has a broken PyType_Ready() that fails to # inherit some slots. To work around this, we explicitly # set inherited slots here, but only in PyPy since CPython # handles this better than we do (except for buffer slots in type specs). inherited_value = value current_scope = scope while (inherited_value == "0" and current_scope.parent_type and current_scope.parent_type.base_type and current_scope.parent_type.base_type.scope): current_scope = current_scope.parent_type.base_type.scope inherited_value = self.slot_code(current_scope) if inherited_value != "0": # we always need inherited buffer slots for the type spec is_buffer_slot = int(self.slot_name in ("bf_getbuffer", "bf_releasebuffer")) code.putln("#if CYTHON_COMPILING_IN_PYPY || %d" % is_buffer_slot) code.putln("%s, /*%s*/" % (inherited_value, self.slot_name)) code.putln("#else") end_pypy_guard = True if self.used_ifdef: code.putln("#if %s" % self.used_ifdef) code.putln("%s, /*%s*/" % (value, self.slot_name)) if self.used_ifdef: code.putln("#else") code.putln("NULL, /*%s*/" % self.slot_name) code.putln("#endif") if end_pypy_guard: code.putln("#endif") if self.py3 == '': code.putln("#else") code.putln("0, /*reserved*/") if preprocessor_guard: code.putln("#endif") # Some C implementations have trouble statically # initialising a global with a pointer to an extern # function, so we initialise some of the type slots # in the module init function instead. def generate_dynamic_init_code(self, scope, code): if self.is_initialised_dynamically: self.generate_set_slot_code( self.slot_code(scope), scope, code) def generate_set_slot_code(self, value, scope, code): if value == "0": return if scope.parent_type.typeptr_cname: target = "%s->%s" % (scope.parent_type.typeptr_cname, self.slot_name) else: assert scope.parent_type.typeobj_cname target = "%s.%s" % (scope.parent_type.typeobj_cname, self.slot_name) code.putln("%s = %s;" % (target, value)) class FixedSlot(SlotDescriptor): # Descriptor for a type slot with a fixed value. # # value string def __init__(self, slot_name, value, py3=True, py2=True, ifdef=None): SlotDescriptor.__init__(self, slot_name, py3=py3, py2=py2, ifdef=ifdef) self.value = value def slot_code(self, scope): return self.value class EmptySlot(FixedSlot): # Descriptor for a type slot whose value is always 0. def __init__(self, slot_name, py3=True, py2=True, ifdef=None): FixedSlot.__init__(self, slot_name, "0", py3=py3, py2=py2, ifdef=ifdef) class MethodSlot(SlotDescriptor): # Type slot descriptor for a user-definable method. # # signature Signature # 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, 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 self.slot_name = slot_name self.method_name = method_name self.alternatives = [] method_name_to_slot[method_name] = self # if fallback: self.alternatives.append(fallback) for alt in (self.py2, self.py3): if isinstance(alt, (tuple, list)): slot_name, method_name = alt self.alternatives.append(method_name) method_name_to_slot[method_name] = self def slot_code(self, scope): entry = scope.lookup_here(self.method_name) if entry and entry.is_special and entry.func_cname: return entry.func_cname for method_name in self.alternatives: entry = scope.lookup_here(method_name) if entry and entry.is_special and entry.func_cname: return entry.func_cname return "0" class InternalMethodSlot(SlotDescriptor): # Type slot descriptor for a method which is always # synthesized by Cython. # # slot_name string Member name of the slot in the type object def __init__(self, slot_name, **kargs): SlotDescriptor.__init__(self, slot_name, **kargs) def slot_code(self, scope): return scope.mangle_internal(self.slot_name) class GCDependentSlot(InternalMethodSlot): # Descriptor for a slot whose value depends on whether # the type participates in GC. def __init__(self, slot_name, **kargs): InternalMethodSlot.__init__(self, slot_name, **kargs) def slot_code(self, scope): if not scope.needs_gc(): return "0" if not scope.has_cyclic_pyobject_attrs: # if the type does not have GC relevant object attributes, it can # delegate GC methods to its parent - iff the parent functions # are defined in the same module parent_type_scope = scope.parent_type.base_type.scope if scope.parent_scope is parent_type_scope.parent_scope: entry = scope.parent_scope.lookup_here(scope.parent_type.base_type.name) if entry.visibility != 'extern': return self.slot_code(parent_type_scope) return InternalMethodSlot.slot_code(self, scope) class GCClearReferencesSlot(GCDependentSlot): def slot_code(self, scope): if scope.needs_tp_clear(): return GCDependentSlot.slot_code(self, scope) return "0" class ConstructorSlot(InternalMethodSlot): # Descriptor for tp_new and tp_dealloc. def __init__(self, slot_name, method=None, **kargs): InternalMethodSlot.__init__(self, slot_name, **kargs) self.method = method def _needs_own(self, scope): if (scope.parent_type.base_type and not scope.has_pyobject_attrs and not scope.has_memoryview_attrs and not scope.has_cpp_constructable_attrs and not (self.slot_name == 'tp_new' and scope.parent_type.vtabslot_cname)): entry = scope.lookup_here(self.method) if self.method else None if not (entry and entry.is_special): return False # Unless we can safely delegate to the parent, all types need a tp_new(). return True def _parent_slot_function(self, scope): parent_type_scope = scope.parent_type.base_type.scope if scope.parent_scope is parent_type_scope.parent_scope: entry = scope.parent_scope.lookup_here(scope.parent_type.base_type.name) if entry.visibility != 'extern': return self.slot_code(parent_type_scope) return None def slot_code(self, scope): if not self._needs_own(scope): # if the type does not have object attributes, it can # delegate GC methods to its parent - iff the parent # functions are defined in the same module slot_code = self._parent_slot_function(scope) return slot_code or '0' return InternalMethodSlot.slot_code(self, scope) def spec_value(self, scope): slot_function = self.slot_code(scope) if self.slot_name == "tp_dealloc" and slot_function != scope.mangle_internal("tp_dealloc"): # Not used => inherit from base type. return "0" return slot_function def generate_dynamic_init_code(self, scope, code): if self.slot_code(scope) != '0': return # If we don't have our own slot function and don't know the # parent function statically, copy it dynamically. base_type = scope.parent_type.base_type if base_type.typeptr_cname: src = '%s->%s' % (base_type.typeptr_cname, self.slot_name) elif base_type.is_extension_type and base_type.typeobj_cname: src = '%s.%s' % (base_type.typeobj_cname, self.slot_name) else: return self.generate_set_slot_code(src, scope, code) class SyntheticSlot(InternalMethodSlot): # Type slot descriptor for a synthesized method which # dispatches to one or more user-defined methods depending # on its arguments. If none of the relevant methods are # defined, the method will not be synthesized and an # alternative default value will be placed in the type # slot. def __init__(self, slot_name, user_methods, default_value, **kargs): InternalMethodSlot.__init__(self, slot_name, **kargs) self.user_methods = user_methods self.default_value = default_value def slot_code(self, scope): if scope.defines_any_special(self.user_methods): return InternalMethodSlot.slot_code(self, scope) else: return self.default_value def spec_value(self, scope): return self.slot_code(scope) class BinopSlot(SyntheticSlot): 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, method_name_to_slot, **kargs) self.right_slot = MethodSlot(signature, "", right_method, method_name_to_slot, **kargs) class RichcmpSlot(MethodSlot): def slot_code(self, scope): entry = scope.lookup_here(self.method_name) if entry and entry.is_special and entry.func_cname: return entry.func_cname elif scope.defines_any_special(richcmp_special_methods): return scope.mangle_internal(self.slot_name) else: return "0" class TypeFlagsSlot(SlotDescriptor): # Descriptor for the type flags slot. def slot_code(self, scope): value = "Py_TPFLAGS_DEFAULT" if scope.directives['type_version_tag']: # it's not in 'Py_TPFLAGS_DEFAULT' in Py2 value += "|Py_TPFLAGS_HAVE_VERSION_TAG" else: # it's enabled in 'Py_TPFLAGS_DEFAULT' in Py3 value = "(%s&~Py_TPFLAGS_HAVE_VERSION_TAG)" % value value += "|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_HAVE_NEWBUFFER" if not scope.parent_type.is_final_type: value += "|Py_TPFLAGS_BASETYPE" if scope.needs_gc(): value += "|Py_TPFLAGS_HAVE_GC" if scope.may_have_finalize(): value += "|Py_TPFLAGS_HAVE_FINALIZE" if scope.parent_type.has_sequence_flag: value += "|Py_TPFLAGS_SEQUENCE" return value def generate_spec(self, scope, code): # Flags are stored in the PyType_Spec, not in a PyType_Slot. return class DocStringSlot(SlotDescriptor): # Descriptor for the docstring slot. def slot_code(self, scope): doc = scope.doc if doc is None: return "0" if doc.is_unicode: doc = doc.as_utf8_string() return "PyDoc_STR(%s)" % doc.as_c_string_literal() class SuiteSlot(SlotDescriptor): # Descriptor for a substructure of the type object. # # sub_slots [SlotDescriptor] 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 substructures.append(self) def is_empty(self, scope): for slot in self.sub_slots: if slot.slot_code(scope) != "0": return False return True def substructure_cname(self, scope): return "%s%s_%s" % (Naming.pyrex_prefix, self.slot_name, scope.class_name) def slot_code(self, scope): if not self.is_empty(scope): return "&%s" % self.substructure_cname(scope) return "0" def generate_substructure(self, scope, code): if not self.is_empty(scope): code.putln("") if self.ifdef: code.putln("#if %s" % self.ifdef) code.putln( "static %s %s = {" % ( self.slot_type, self.substructure_cname(scope))) for slot in self.sub_slots: slot.generate(scope, code) code.putln("};") if self.ifdef: code.putln("#endif") def generate_spec(self, scope, code): for slot in self.sub_slots: slot.generate_spec(scope, code) class MethodTableSlot(SlotDescriptor): # Slot descriptor for the method table. def slot_code(self, scope): if scope.pyfunc_entries: return scope.method_table_cname else: return "0" class MemberTableSlot(SlotDescriptor): # Slot descriptor for the table of Python-accessible attributes. def slot_code(self, scope): # Only used in specs. return "0" def get_member_specs(self, scope): return [ get_slot_by_name("tp_dictoffset", scope.directives).members_slot_value(scope), #get_slot_by_name("tp_weaklistoffset").spec_value(scope), ] def is_empty(self, scope): for member_entry in self.get_member_specs(scope): if member_entry: return False return True def substructure_cname(self, scope): return "%s%s_%s" % (Naming.pyrex_prefix, self.slot_name, scope.class_name) def generate_substructure_spec(self, scope, code): if self.is_empty(scope): return from .Code import UtilityCode code.globalstate.use_utility_code(UtilityCode.load_cached("IncludeStructmemberH", "ModuleSetupCode.c")) code.putln("static struct PyMemberDef %s[] = {" % self.substructure_cname(scope)) for member_entry in self.get_member_specs(scope): if member_entry: code.putln(member_entry) code.putln("{NULL, 0, 0, 0, NULL}") code.putln("};") def spec_value(self, scope): if self.is_empty(scope): return "0" return self.substructure_cname(scope) class GetSetSlot(SlotDescriptor): # Slot descriptor for the table of attribute get & set methods. def slot_code(self, scope): if scope.property_entries: return scope.getset_table_cname else: return "0" class BaseClassSlot(SlotDescriptor): # Slot descriptor for the base class slot. def __init__(self, name): SlotDescriptor.__init__(self, name, dynamic=True) def generate_dynamic_init_code(self, scope, code): base_type = scope.parent_type.base_type if base_type: code.putln("%s->%s = %s;" % ( scope.parent_type.typeptr_cname, self.slot_name, base_type.typeptr_cname)) class DictOffsetSlot(SlotDescriptor): # Slot descriptor for a class' dict offset, for dynamic attributes. def slot_code(self, scope): dict_entry = scope.lookup_here("__dict__") if not scope.is_closure_class_scope else None if dict_entry and dict_entry.is_variable: if getattr(dict_entry.type, 'cname', None) != 'PyDict_Type': error(dict_entry.pos, "__dict__ slot must be of type 'dict'") return "0" type = scope.parent_type if type.typedef_flag: objstruct = type.objstruct_cname else: objstruct = "struct %s" % type.objstruct_cname return ("offsetof(%s, %s)" % ( objstruct, dict_entry.cname)) else: return "0" def members_slot_value(self, scope): dict_offset = self.slot_code(scope) if dict_offset == "0": return None return '{"__dictoffset__", T_PYSSIZET, %s, READONLY, NULL},' % dict_offset ## The following slots are (or could be) initialised with an ## extern function pointer. # #slots_initialised_from_extern = ( # "tp_free", #) #------------------------------------------------------------------------------------------ # # Utility functions for accessing slot table data structures # #------------------------------------------------------------------------------------------ def get_property_accessor_signature(name): # Return signature of accessor for an extension type # property, else None. return property_accessor_signatures.get(name) def get_base_slot_function(scope, slot): # Returns the function implementing this slot in the baseclass. # This is useful for enabling the compiler to optimize calls # that recursively climb the class hierarchy. base_type = scope.parent_type.base_type if base_type and scope.parent_scope is base_type.scope.parent_scope: parent_slot = slot.slot_code(base_type.scope) if parent_slot != '0': entry = scope.parent_scope.lookup_here(scope.parent_type.base_type.name) if entry.visibility != 'extern': return parent_slot return None def get_slot_function(scope, slot): # Returns the function implementing this slot in the baseclass. # This is useful for enabling the compiler to optimize calls # that recursively climb the class hierarchy. slot_code = slot.slot_code(scope) if slot_code != '0': entry = scope.parent_scope.lookup_here(scope.parent_type.name) if entry.visibility != 'extern': return slot_code return None def get_slot_by_name(slot_name, compiler_directives): # For now, only search the type struct, no referenced sub-structs. 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_code_by_name(scope, slot_name): slot = get_slot_by_name(slot_name, scope.directives) 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 get_slot_table(None).PyNumberMethods: if hasattr(meth, "right_slot"): return True return False #------------------------------------------------------------------------------------------ # # Signatures for generic Python functions and methods. # #------------------------------------------------------------------------------------------ pyfunction_signature = Signature("-*", "O") pymethod_signature = Signature("T*", "O") #------------------------------------------------------------------------------------------ # # Signatures for simple Python functions. # #------------------------------------------------------------------------------------------ pyfunction_noargs = Signature("-", "O") pyfunction_onearg = Signature("-O", "O") #------------------------------------------------------------------------------------------ # # Signatures for the various kinds of function that # can appear in the type object and its substructures. # #------------------------------------------------------------------------------------------ unaryfunc = Signature("T", "O") # typedef PyObject * (*unaryfunc)(PyObject *); binaryfunc = Signature("OO", "O") # typedef PyObject * (*binaryfunc)(PyObject *, PyObject *); ibinaryfunc = Signature("TO", "O") # typedef PyObject * (*binaryfunc)(PyObject *, PyObject *); powternaryfunc = Signature("OO?", "O") # typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *); ipowternaryfunc = Signature("TO?", "O") # typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *); callfunc = Signature("T*", "O") # typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *); inquiry = Signature("T", "i") # typedef int (*inquiry)(PyObject *); lenfunc = Signature("T", "z") # typedef Py_ssize_t (*lenfunc)(PyObject *); # typedef int (*coercion)(PyObject **, PyObject **); intargfunc = Signature("Ti", "O") # typedef PyObject *(*intargfunc)(PyObject *, int); ssizeargfunc = Signature("Tz", "O") # typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t); intintargfunc = Signature("Tii", "O") # typedef PyObject *(*intintargfunc)(PyObject *, int, int); ssizessizeargfunc = Signature("Tzz", "O") # typedef PyObject *(*ssizessizeargfunc)(PyObject *, Py_ssize_t, Py_ssize_t); intobjargproc = Signature("TiO", 'r') # typedef int(*intobjargproc)(PyObject *, int, PyObject *); ssizeobjargproc = Signature("TzO", 'r') # typedef int(*ssizeobjargproc)(PyObject *, Py_ssize_t, PyObject *); intintobjargproc = Signature("TiiO", 'r') # typedef int(*intintobjargproc)(PyObject *, int, int, PyObject *); ssizessizeobjargproc = Signature("TzzO", 'r') # typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); intintargproc = Signature("Tii", 'r') ssizessizeargproc = Signature("Tzz", 'r') objargfunc = Signature("TO", "O") objobjargproc = Signature("TOO", 'r') # typedef int (*objobjargproc)(PyObject *, PyObject *, PyObject *); readbufferproc = Signature("TzP", "z") # typedef Py_ssize_t (*readbufferproc)(PyObject *, Py_ssize_t, void **); writebufferproc = Signature("TzP", "z") # typedef Py_ssize_t (*writebufferproc)(PyObject *, Py_ssize_t, void **); segcountproc = Signature("TZ", "z") # typedef Py_ssize_t (*segcountproc)(PyObject *, Py_ssize_t *); charbufferproc = Signature("TzS", "z") # typedef Py_ssize_t (*charbufferproc)(PyObject *, Py_ssize_t, char **); objargproc = Signature("TO", 'r') # typedef int (*objobjproc)(PyObject *, PyObject *); # typedef int (*visitproc)(PyObject *, void *); # typedef int (*traverseproc)(PyObject *, visitproc, void *); destructor = Signature("T", "v") # typedef void (*destructor)(PyObject *); # printfunc = Signature("TFi", 'r') # typedef int (*printfunc)(PyObject *, FILE *, int); # typedef PyObject *(*getattrfunc)(PyObject *, char *); getattrofunc = Signature("TO", "O") # typedef PyObject *(*getattrofunc)(PyObject *, PyObject *); # typedef int (*setattrfunc)(PyObject *, char *, PyObject *); setattrofunc = Signature("TOO", 'r') # typedef int (*setattrofunc)(PyObject *, PyObject *, PyObject *); delattrofunc = Signature("TO", 'r') cmpfunc = Signature("TO", "i") # typedef int (*cmpfunc)(PyObject *, PyObject *); reprfunc = Signature("T", "O") # typedef PyObject *(*reprfunc)(PyObject *); hashfunc = Signature("T", "h") # typedef Py_hash_t (*hashfunc)(PyObject *); richcmpfunc = Signature("TOi", "O") # typedef PyObject *(*richcmpfunc) (PyObject *, PyObject *, int); getiterfunc = Signature("T", "O") # typedef PyObject *(*getiterfunc) (PyObject *); iternextfunc = Signature("T", "O") # typedef PyObject *(*iternextfunc) (PyObject *); descrgetfunc = Signature("TOO", "O") # typedef PyObject *(*descrgetfunc) (PyObject *, PyObject *, PyObject *); descrsetfunc = Signature("TOO", 'r') # typedef int (*descrsetfunc) (PyObject *, PyObject *, PyObject *); descrdelfunc = Signature("TO", 'r') initproc = Signature("T*", 'r') # typedef int (*initproc)(PyObject *, PyObject *, PyObject *); # typedef PyObject *(*newfunc)(struct _typeobject *, PyObject *, PyObject *); # typedef PyObject *(*allocfunc)(struct _typeobject *, int); getbufferproc = Signature("TBi", "r") # typedef int (*getbufferproc)(PyObject *, Py_buffer *, int); releasebufferproc = Signature("TB", "v") # typedef void (*releasebufferproc)(PyObject *, Py_buffer *); #------------------------------------------------------------------------------------------ # # Signatures for accessor methods of properties. # #------------------------------------------------------------------------------------------ property_accessor_signatures = { '__get__': Signature("T", "O"), '__set__': Signature("TO", 'r'), '__del__': Signature("T", 'r') } PyNumberMethods_Py2only_GUARD = "PY_MAJOR_VERSION < 3 || (CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX < 0x03050000)" #------------------------------------------------------------------------------------------ # # 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 # #------------------------------------------------------------------------------------------ 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 ptf = powternaryfunc if old_binops else ipowternaryfunc # 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(ptf, "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 = ""), 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(ptf, "nb_inplace_power", "__ipow__", method_name_to_slot), 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(bf, "nb_floor_divide", "__floordiv__", method_name_to_slot), BinopSlot(bf, "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(bf, "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), EmptySlot("am_send", ifdef="PY_VERSION_HEX >= 0x030A00A3"), ) 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"), SyntheticSlot("tp_finalize", ["__del__"], "0", ifdef="PY_VERSION_HEX >= 0x030400a1", used_ifdef="CYTHON_USE_TP_FINALIZE"), EmptySlot("tp_vectorcall", ifdef="PY_VERSION_HEX >= 0x030800b1 && (!CYTHON_COMPILING_IN_PYPY || PYPY_VERSION_NUM >= 0x07030800)"), EmptySlot("tp_print", ifdef="PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000"), EmptySlot("tp_watched", ifdef="PY_VERSION_HEX >= 0x030C0000"), # PyPy specific extension - only here to avoid C compiler warnings. EmptySlot("tp_pypy_flags", ifdef="CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX >= 0x03090000 && PY_VERSION_HEX < 0x030a0000"), ) #------------------------------------------------------------------------------------------ # # 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(destructor, "", "__del__", 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 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) def __iter__(self): # make it easier to iterate over all the slots return iter(self.slot_table) _slot_table_dict = {} 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() 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] # 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. method_noargs = "METH_NOARGS" method_onearg = "METH_O" method_varargs = "METH_VARARGS" method_fastcall = "__Pyx_METH_FASTCALL" # Actually VARARGS on versions < 3.7 method_keywords = "METH_KEYWORDS" method_coexist = "METH_COEXIST"