Commit 315fd426 authored by da-woods's avatar da-woods Committed by GitHub

Make reference counting more type specific by moving it into PyrexTypes (GH-3377)

The idea being that struct-types like memoryviews
can generate their own reference counting code
using a common interface with Python objects.
parent ffbecc75
...@@ -2095,123 +2095,89 @@ class CCodeWriter(object): ...@@ -2095,123 +2095,89 @@ class CCodeWriter(object):
from .PyrexTypes import py_object_type, typecast from .PyrexTypes import py_object_type, typecast
return typecast(py_object_type, type, cname) return typecast(py_object_type, type, cname)
def put_gotref(self, cname): def put_gotref(self, cname, type):
self.putln("__Pyx_GOTREF(%s);" % cname) type.generate_gotref(self, cname)
def put_giveref(self, cname): def put_giveref(self, cname, type):
self.putln("__Pyx_GIVEREF(%s);" % cname) type.generate_giveref(self, cname)
def put_xgiveref(self, cname): def put_xgiveref(self, cname, type):
self.putln("__Pyx_XGIVEREF(%s);" % cname) type.generate_xgiveref(self, cname)
def put_xgotref(self, cname): def put_xgotref(self, cname, type):
self.putln("__Pyx_XGOTREF(%s);" % cname) type.generate_xgotref(self, cname)
def put_incref(self, cname, type, nanny=True): def put_incref(self, cname, type, nanny=True):
if nanny: # Note: original put_Memslice_Incref/Decref also added in some utility code
self.putln("__Pyx_INCREF(%s);" % self.as_pyobject(cname, type)) # this is unnecessary since the relevant utility code is loaded anyway if a memoryview is used
else: # and so has been removed. However, it's potentially a feature that might be useful here
self.putln("Py_INCREF(%s);" % self.as_pyobject(cname, type)) type.generate_incref(self, cname, nanny=nanny)
def put_decref(self, cname, type, nanny=True): def put_xincref(self, cname, type, nanny=True):
self._put_decref(cname, type, nanny, null_check=False, clear=False) type.generate_xincref(self, cname, nanny=nanny)
def put_var_gotref(self, entry): def put_decref(self, cname, type, nanny=True, have_gil=True):
if entry.type.is_pyobject: type.generate_decref(self, cname, nanny=nanny, have_gil=have_gil)
self.putln("__Pyx_GOTREF(%s);" % self.entry_as_pyobject(entry))
def put_var_giveref(self, entry): def put_xdecref(self, cname, type, nanny=True, have_gil=True):
if entry.type.is_pyobject: type.generate_xdecref(self, cname, nanny=nanny, have_gil=have_gil)
self.putln("__Pyx_GIVEREF(%s);" % self.entry_as_pyobject(entry))
def put_var_xgotref(self, entry): def put_decref_clear(self, cname, type, clear_before_decref=False, nanny=True, have_gil=True):
if entry.type.is_pyobject: type.generate_decref_clear(self, cname, clear_before_decref=clear_before_decref,
self.putln("__Pyx_XGOTREF(%s);" % self.entry_as_pyobject(entry)) nanny=nanny, have_gil=have_gil)
def put_var_xgiveref(self, entry): def put_xdecref_clear(self, cname, type, clear_before_decref=False, nanny=True, have_gil=True):
if entry.type.is_pyobject: type.generate_xdecref_clear(self, cname, clear_before_decref=clear_before_decref,
self.putln("__Pyx_XGIVEREF(%s);" % self.entry_as_pyobject(entry)) nanny=nanny, have_gil=have_gil)
def put_var_incref(self, entry, nanny=True): def put_decref_set(self, cname, type, rhs_cname):
if entry.type.is_pyobject: type.generate_decref_set(self, cname, rhs_cname)
if nanny:
self.putln("__Pyx_INCREF(%s);" % self.entry_as_pyobject(entry))
else:
self.putln("Py_INCREF(%s);" % self.entry_as_pyobject(entry))
def put_var_xincref(self, entry): def put_xdecref_set(self, cname, type, rhs_cname):
if entry.type.is_pyobject: type.generate_xdecref_set(self, cname, rhs_cname)
self.putln("__Pyx_XINCREF(%s);" % self.entry_as_pyobject(entry))
def put_decref_clear(self, cname, type, nanny=True, clear_before_decref=False): def put_incref_memoryviewslice(self, slice_cname, type, have_gil):
self._put_decref(cname, type, nanny, null_check=False, # TODO ideally this would just be merged into "put_incref"
clear=True, clear_before_decref=clear_before_decref) type.generate_incref_memoryviewslice(self, slice_cname, have_gil=have_gil)
def put_xdecref(self, cname, type, nanny=True, have_gil=True): def put_var_incref_memoryviewslice(self, entry, have_gil):
self._put_decref(cname, type, nanny, null_check=True, self.put_incref_memoryviewslice(entry.cname, entry.type, have_gil=have_gil)
have_gil=have_gil, clear=False)
def put_xdecref_clear(self, cname, type, nanny=True, clear_before_decref=False): def put_var_gotref(self, entry):
self._put_decref(cname, type, nanny, null_check=True, self.put_gotref(entry.cname, entry.type)
clear=True, clear_before_decref=clear_before_decref)
def _put_decref(self, cname, type, nanny=True, null_check=False, def put_var_giveref(self, entry):
have_gil=True, clear=False, clear_before_decref=False): self.put_giveref(entry.cname, entry.type)
if type.is_memoryviewslice:
self.put_xdecref_memoryviewslice(cname, have_gil=have_gil)
return
prefix = '__Pyx' if nanny else 'Py' def put_var_xgotref(self, entry):
X = 'X' if null_check else '' self.put_xgotref(entry.cname, entry.type)
if clear: def put_var_xgiveref(self, entry):
if clear_before_decref: self.put_xgiveref(entry.cname, entry.type)
if not nanny:
X = '' # CPython doesn't have a Py_XCLEAR()
self.putln("%s_%sCLEAR(%s);" % (prefix, X, cname))
else:
self.putln("%s_%sDECREF(%s); %s = 0;" % (
prefix, X, self.as_pyobject(cname, type), cname))
else:
self.putln("%s_%sDECREF(%s);" % (
prefix, X, self.as_pyobject(cname, type)))
def put_decref_set(self, cname, rhs_cname): def put_var_incref(self, entry, **kwds):
self.putln("__Pyx_DECREF_SET(%s, %s);" % (cname, rhs_cname)) self.put_incref(entry.cname, entry.type, **kwds)
def put_xdecref_set(self, cname, rhs_cname): def put_var_xincref(self, entry, **kwds):
self.putln("__Pyx_XDECREF_SET(%s, %s);" % (cname, rhs_cname)) self.put_xincref(entry.cname, entry.type, **kwds)
def put_var_decref(self, entry): def put_var_decref(self, entry, **kwds):
if entry.type.is_pyobject: self.put_decref(entry.cname, entry.type, **kwds)
self.putln("__Pyx_XDECREF(%s);" % self.entry_as_pyobject(entry))
def put_var_xdecref(self, entry, nanny=True): def put_var_xdecref(self, entry, **kwds):
if entry.type.is_pyobject: self.put_xdecref(entry.cname, entry.type, **kwds)
if nanny:
self.putln("__Pyx_XDECREF(%s);" % self.entry_as_pyobject(entry))
else:
self.putln("Py_XDECREF(%s);" % self.entry_as_pyobject(entry))
def put_var_decref_clear(self, entry): def put_var_decref_clear(self, entry, **kwds):
self._put_var_decref_clear(entry, null_check=False) self.put_decref_clear(entry.cname, entry.type, clear_before_decref=entry.in_closure, **kwds)
def put_var_xdecref_clear(self, entry): def put_var_decref_set(self, entry, rhs_cname, **kwds):
self._put_var_decref_clear(entry, null_check=True) self.put_decref_set(entry.cname, entry.type, rhs_cname, **kwds)
def _put_var_decref_clear(self, entry, null_check): def put_var_xdecref_set(self, entry, rhs_cname, **kwds):
if entry.type.is_pyobject: self.put_xdecref_set(entry.cname, entry.type, rhs_cname, **kwds)
if entry.in_closure:
# reset before DECREF to make sure closure state is def put_var_xdecref_clear(self, entry, **kwds):
# consistent during call to DECREF() self.put_xdecref_clear(entry.cname, entry.type, clear_before_decref=entry.in_closure, **kwds)
self.putln("__Pyx_%sCLEAR(%s);" % (
null_check and 'X' or '',
entry.cname))
else:
self.putln("__Pyx_%sDECREF(%s); %s = 0;" % (
null_check and 'X' or '',
self.entry_as_pyobject(entry),
entry.cname))
def put_var_decrefs(self, entries, used_only = 0): def put_var_decrefs(self, entries, used_only = 0):
for entry in entries: for entry in entries:
...@@ -2229,19 +2195,6 @@ class CCodeWriter(object): ...@@ -2229,19 +2195,6 @@ class CCodeWriter(object):
for entry in entries: for entry in entries:
self.put_var_xdecref_clear(entry) self.put_var_xdecref_clear(entry)
def put_incref_memoryviewslice(self, slice_cname, have_gil=False):
from . import MemoryView
self.globalstate.use_utility_code(MemoryView.memviewslice_init_code)
self.putln("__PYX_INC_MEMVIEW(&%s, %d);" % (slice_cname, int(have_gil)))
def put_xdecref_memoryviewslice(self, slice_cname, have_gil=False):
from . import MemoryView
self.globalstate.use_utility_code(MemoryView.memviewslice_init_code)
self.putln("__PYX_XDEC_MEMVIEW(&%s, %d);" % (slice_cname, int(have_gil)))
def put_xgiveref_memoryviewslice(self, slice_cname):
self.put_xgiveref("%s.memview" % slice_cname)
def put_init_to_py_none(self, cname, type, nanny=True): def put_init_to_py_none(self, cname, type, nanny=True):
from .PyrexTypes import py_object_type, typecast from .PyrexTypes import py_object_type, typecast
py_none = typecast(type, py_object_type, "Py_None") py_none = typecast(type, py_object_type, "Py_None")
......
This diff is collapsed.
...@@ -881,7 +881,7 @@ class FusedCFuncDefNode(StatListNode): ...@@ -881,7 +881,7 @@ class FusedCFuncDefNode(StatListNode):
"((__pyx_FusedFunctionObject *) %s)->__signatures__ = %s;" % "((__pyx_FusedFunctionObject *) %s)->__signatures__ = %s;" %
(self.resulting_fused_function.result(), (self.resulting_fused_function.result(),
self.__signatures__.result())) self.__signatures__.result()))
code.put_giveref(self.__signatures__.result()) self.__signatures__.generate_giveref(code)
self.fused_func_assignment.generate_execution_code(code) self.fused_func_assignment.generate_execution_code(code)
......
...@@ -101,7 +101,8 @@ def put_acquire_memoryviewslice(lhs_cname, lhs_type, lhs_pos, rhs, code, ...@@ -101,7 +101,8 @@ def put_acquire_memoryviewslice(lhs_cname, lhs_type, lhs_pos, rhs, code,
def put_assign_to_memviewslice(lhs_cname, rhs, rhs_cname, memviewslicetype, code, def put_assign_to_memviewslice(lhs_cname, rhs, rhs_cname, memviewslicetype, code,
have_gil=False, first_assignment=False): have_gil=False, first_assignment=False):
if not first_assignment: if not first_assignment:
code.put_xdecref_memoryviewslice(lhs_cname, have_gil=have_gil) code.put_xdecref(lhs_cname, memviewslicetype,
have_gil=have_gil)
if not rhs.result_in_temp(): if not rhs.result_in_temp():
rhs.make_owned_memoryviewslice(code) rhs.make_owned_memoryviewslice(code)
...@@ -248,7 +249,7 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry): ...@@ -248,7 +249,7 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry):
return bufp return bufp
def generate_buffer_slice_code(self, code, indices, dst, have_gil, def generate_buffer_slice_code(self, code, indices, dst, dst_type, have_gil,
have_slices, directives): have_slices, directives):
""" """
Slice a memoryviewslice. Slice a memoryviewslice.
...@@ -265,7 +266,7 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry): ...@@ -265,7 +266,7 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry):
code.putln("%(dst)s.data = %(src)s.data;" % locals()) code.putln("%(dst)s.data = %(src)s.data;" % locals())
code.putln("%(dst)s.memview = %(src)s.memview;" % locals()) code.putln("%(dst)s.memview = %(src)s.memview;" % locals())
code.put_incref_memoryviewslice(dst) code.put_incref_memoryviewslice(dst, dst_type, have_gil=have_gil)
all_dimensions_direct = all(access == 'direct' for access, packing in self.type.axes) all_dimensions_direct = all(access == 'direct' for access, packing in self.type.axes)
suboffset_dim_temp = [] suboffset_dim_temp = []
......
...@@ -1582,13 +1582,9 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -1582,13 +1582,9 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
for entry in cpp_class_attrs: for entry in cpp_class_attrs:
code.putln("__Pyx_call_destructor(p->%s);" % entry.cname) code.putln("__Pyx_call_destructor(p->%s);" % entry.cname)
for entry in py_attrs: for entry in (py_attrs + memoryview_slices):
code.put_xdecref_clear("p->%s" % entry.cname, entry.type, nanny=False, code.put_xdecref_clear("p->%s" % entry.cname, entry.type, nanny=False,
clear_before_decref=True) clear_before_decref=True, have_gil=True)
for entry in memoryview_slices:
code.put_xdecref_memoryviewslice("p->%s" % entry.cname,
have_gil=True)
if base_type: if base_type:
if needs_gc: if needs_gc:
...@@ -2945,7 +2941,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -2945,7 +2941,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
EncodedString(decode_filename( EncodedString(decode_filename(
os.path.dirname(module_path)))).cname, os.path.dirname(module_path)))).cname,
code.error_goto_if_null(temp, self.pos))) code.error_goto_if_null(temp, self.pos)))
code.put_gotref(temp) code.put_gotref(temp, py_object_type)
code.putln( code.putln(
'if (PyObject_SetAttrString(%s, "__path__", %s) < 0) %s;' % ( 'if (PyObject_SetAttrString(%s, "__path__", %s) < 0) %s;' % (
env.module_cname, temp, code.error_goto(self.pos))) env.module_cname, temp, code.error_goto(self.pos)))
...@@ -3182,7 +3178,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -3182,7 +3178,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
module_temp, module_temp,
Naming.pymoduledef_cname, Naming.pymoduledef_cname,
code.error_goto_if_null(module_temp, self.pos))) code.error_goto_if_null(module_temp, self.pos)))
code.put_gotref(module_temp) code.put_gotref(module_temp, py_object_type)
code.putln(code.error_goto_if_neg("PyState_AddModule(%s, &%s)" % ( code.putln(code.error_goto_if_neg("PyState_AddModule(%s, &%s)" % (
module_temp, Naming.pymoduledef_cname), self.pos)) module_temp, Naming.pymoduledef_cname), self.pos))
code.put_decref_clear(module_temp, type=py_object_type) code.put_decref_clear(module_temp, type=py_object_type)
...@@ -3536,7 +3532,7 @@ class ModuleImportGenerator(object): ...@@ -3536,7 +3532,7 @@ class ModuleImportGenerator(object):
self.temps.append(temp) self.temps.append(temp)
code.putln('%s = PyImport_ImportModule(%s); if (unlikely(!%s)) %s' % ( code.putln('%s = PyImport_ImportModule(%s); if (unlikely(!%s)) %s' % (
temp, module_name_string, temp, error_code)) temp, module_name_string, temp, error_code))
code.put_gotref(temp) code.put_gotref(temp, py_object_type)
self.imported[module_name_string] = temp self.imported[module_name_string] = temp
return temp return temp
......
This diff is collapsed.
...@@ -193,6 +193,8 @@ class PyrexType(BaseType): ...@@ -193,6 +193,8 @@ class PyrexType(BaseType):
# is_pythran_expr boolean Is Pythran expr # is_pythran_expr boolean Is Pythran expr
# is_numpy_buffer boolean Is Numpy array buffer # is_numpy_buffer boolean Is Numpy array buffer
# has_attributes boolean Has C dot-selectable attributes # has_attributes boolean Has C dot-selectable attributes
# needs_refcounting boolean Needs code to be generated similar to incref/gotref/decref.
# Largely used internally.
# default_value string Initial value that can be assigned before first user assignment. # default_value string Initial value that can be assigned before first user assignment.
# declaration_value string The value statically assigned on declaration (if any). # declaration_value string The value statically assigned on declaration (if any).
# entry Entry The Entry for this type # entry Entry The Entry for this type
...@@ -257,6 +259,7 @@ class PyrexType(BaseType): ...@@ -257,6 +259,7 @@ class PyrexType(BaseType):
is_pythran_expr = 0 is_pythran_expr = 0
is_numpy_buffer = 0 is_numpy_buffer = 0
has_attributes = 0 has_attributes = 0
needs_refcounting = 0
default_value = "" default_value = ""
declaration_value = "" declaration_value = ""
...@@ -334,6 +337,31 @@ class PyrexType(BaseType): ...@@ -334,6 +337,31 @@ class PyrexType(BaseType):
convert_call, convert_call,
code.error_goto_if(error_condition or self.error_condition(result_code), error_pos)) code.error_goto_if(error_condition or self.error_condition(result_code), error_pos))
def _generate_dummy_refcounting(self, code, *ignored_args, **ignored_kwds):
if self.needs_refcounting:
raise NotImplementedError("Ref-counting operation not yet implemented for type %s" %
self)
def _generate_dummy_refcounting_assignment(self, code, cname, rhs_cname, *ignored_args, **ignored_kwds):
if self.needs_refcounting:
raise NotImplementedError("Ref-counting operation not yet implemented for type %s" %
self)
code.putln("%s = %s" % (cname, rhs_cname))
generate_incref = generate_xincref = generate_decref = generate_xdecref \
= generate_decref_clear = generate_xdecref_clear \
= generate_gotref = generate_xgotref = generate_giveref = generate_xgiveref \
= _generate_dummy_refcounting
generate_decref_set = generate_xdecref_set = _generate_dummy_refcounting_assignment
def nullcheck_string(self, code, cname):
if self.needs_refcounting:
raise NotImplementedError("Ref-counting operation not yet implemented for type %s" %
self)
code.putln("1")
def public_decl(base_code, dll_linkage): def public_decl(base_code, dll_linkage):
if dll_linkage: if dll_linkage:
...@@ -566,6 +594,10 @@ class MemoryViewSliceType(PyrexType): ...@@ -566,6 +594,10 @@ class MemoryViewSliceType(PyrexType):
is_memoryviewslice = 1 is_memoryviewslice = 1
has_attributes = 1 has_attributes = 1
needs_refcounting = 1 # Ideally this would be true and reference counting for
# memoryview and pyobject code could be generated in the same way.
# However, memoryviews are sufficiently specialized that this doesn't
# seem practical. Implement a limited version of it for now
scope = None scope = None
# These are special cased in Defnode # These are special cased in Defnode
...@@ -1039,6 +1071,36 @@ class MemoryViewSliceType(PyrexType): ...@@ -1039,6 +1071,36 @@ class MemoryViewSliceType(PyrexType):
def cast_code(self, expr_code): def cast_code(self, expr_code):
return expr_code return expr_code
# When memoryviews are increfed currently seems heavily special-cased.
# Therefore, use our own function for now
def generate_incref(self, code, name, **kwds):
pass
def generate_incref_memoryviewslice(self, code, slice_cname, have_gil):
# TODO ideally would be done separately
code.putln("__PYX_INC_MEMVIEW(&%s, %d);" % (slice_cname, int(have_gil)))
# decref however did look to always apply for memoryview slices
# with "have_gil" set to True by default
def generate_xdecref(self, code, cname, nanny, have_gil):
code.putln("__PYX_XDEC_MEMVIEW(&%s, %d);" % (cname, int(have_gil)))
def generate_decref(self, code, cname, nanny, have_gil):
# Fall back to xdecref since we don't care to have a separate decref version for this.
self.generate_xdecref(code, cname, nanny, have_gil)
def generate_xdecref_clear(self, code, cname, clear_before_decref, **kwds):
self.generate_xdecref(code, cname, **kwds)
code.putln("%s.memview = NULL; %s.data = NULL;" % (cname, cname))
def generate_decref_clear(self, code, cname, **kwds):
# memoryviews don't currently distinguish between xdecref and decref
self.generate_xdecref_clear(code, cname, **kwds)
# memoryviews don't participate in giveref/gotref
generate_gotref = generate_xgotref = generate_xgiveref = generate_giveref = lambda *args: None
class BufferType(BaseType): class BufferType(BaseType):
# #
...@@ -1137,6 +1199,7 @@ class PyObjectType(PyrexType): ...@@ -1137,6 +1199,7 @@ class PyObjectType(PyrexType):
is_subclassed = False is_subclassed = False
is_gc_simple = False is_gc_simple = False
builtin_trashcan = False # builtin type using trashcan builtin_trashcan = False # builtin type using trashcan
needs_refcounting = True
def __str__(self): def __str__(self):
return "Python object" return "Python object"
...@@ -1189,6 +1252,76 @@ class PyObjectType(PyrexType): ...@@ -1189,6 +1252,76 @@ class PyObjectType(PyrexType):
def check_for_null_code(self, cname): def check_for_null_code(self, cname):
return cname return cname
def generate_incref(self, code, cname, nanny):
if nanny:
code.putln("__Pyx_INCREF(%s);" % self.as_pyobject(cname))
else:
code.putln("Py_INCREF(%s);" % self.as_pyobject(cname))
def generate_xincref(self, code, cname, nanny):
if nanny:
code.putln("__Pyx_XINCREF(%s);" % self.as_pyobject(cname))
else:
code.putln("Py_XINCREF(%s);" % self.as_pyobject(cname))
def generate_decref(self, code, cname, nanny, have_gil):
# have_gil is for the benefit of memoryviewslice - it's ignored here
assert have_gil
self._generate_decref(code, cname, nanny, null_check=False, clear=False)
def generate_xdecref(self, code, cname, nanny, have_gil):
# in this (and other) PyObjectType functions, have_gil is being
# passed to provide a common interface with MemoryviewSlice.
# It's ignored here
self._generate_decref(code, cname, nanny, null_check=True,
clear=False)
def generate_decref_clear(self, code, cname, clear_before_decref, nanny, have_gil):
self._generate_decref(code, cname, nanny, null_check=False,
clear=True, clear_before_decref=clear_before_decref)
def generate_xdecref_clear(self, code, cname, clear_before_decref=False, nanny=True, have_gil=None):
self._generate_decref(code, cname, nanny, null_check=True,
clear=True, clear_before_decref=clear_before_decref)
def generate_gotref(self, code, cname):
code.putln("__Pyx_GOTREF(%s);" % self.as_pyobject(cname))
def generate_xgotref(self, code, cname):
code.putln("__Pyx_XGOTREF(%s);" % self.as_pyobject(cname))
def generate_giveref(self, code, cname):
code.putln("__Pyx_GIVEREF(%s);" % self.as_pyobject(cname))
def generate_xgiveref(self, code, cname):
code.putln("__Pyx_XGIVEREF(%s);" % self.as_pyobject(cname))
def generate_decref_set(self, code, cname, rhs_cname):
code.putln("__Pyx_DECREF_SET(%s, %s);" % (cname, rhs_cname))
def generate_xdecref_set(self, code, cname, rhs_cname):
code.putln("__Pyx_XDECREF_SET(%s, %s);" % (cname, rhs_cname))
def _generate_decref(self, code, cname, nanny, null_check=False,
clear=False, clear_before_decref=False):
prefix = '__Pyx' if nanny else 'Py'
X = 'X' if null_check else ''
if clear:
if clear_before_decref:
if not nanny:
X = '' # CPython doesn't have a Py_XCLEAR()
code.putln("%s_%sCLEAR(%s);" % (prefix, X, cname))
else:
code.putln("%s_%sDECREF(%s); %s = 0;" % (
prefix, X, self.as_pyobject(cname), cname))
else:
code.putln("%s_%sDECREF(%s);" % (
prefix, X, self.as_pyobject(cname)))
def nullcheck_string(self, cname):
return cname
builtin_types_that_cannot_create_refcycles = set([ builtin_types_that_cannot_create_refcycles = set([
'object', 'bool', 'int', 'long', 'float', 'complex', 'object', 'bool', 'int', 'long', 'float', 'complex',
......
...@@ -262,6 +262,10 @@ class Entry(object): ...@@ -262,6 +262,10 @@ class Entry(object):
else: else:
return NotImplemented return NotImplemented
@property
def cf_is_reassigned(self):
return len(self.cf_assignments) > 1
class InnerEntry(Entry): class InnerEntry(Entry):
""" """
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment