summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorscoder <stefan_ml@behnel.de>2020-05-04 22:54:53 +0200
committerGitHub <noreply@github.com>2020-05-04 22:54:53 +0200
commit22a49f403b6f00f1674664e01ee0f8212c06ef13 (patch)
tree9acc9b0eda3ff58845ce770ded7a33a91ef9e4af
parent3bd6ccb011e227090560286abb1990a12a51d4d5 (diff)
downloadcython-22a49f403b6f00f1674664e01ee0f8212c06ef13.tar.gz
Rewrite the C property feature (GH-3571)
* Rewrite C property support (GH-2640) based on inline C methods. Supersedes GH-2640 and GH-3095. Closes GH-3521. * Test fix for `numpy_parallel.pyx`: avoid depending on whether "nd.shape" requires the GIL or not. * Turn NumPy's "ndarray.data" into a property to avoid direct struct access. * Make "ndarray.size" accessible without the GIL.
-rw-r--r--Cython/Compiler/ExprNodes.py53
-rw-r--r--Cython/Compiler/Nodes.py97
-rw-r--r--Cython/Compiler/ParseTreeTransforms.py131
-rw-r--r--Cython/Compiler/Pipeline.py3
-rw-r--r--Cython/Compiler/PyrexTypes.py20
-rw-r--r--Cython/Compiler/Symtab.py42
-rw-r--r--Cython/Includes/numpy/__init__.pxd31
-rw-r--r--tests/run/ext_attr_getter.srctree145
-rw-r--r--tests/run/numpy_parallel.pyx8
9 files changed, 370 insertions, 160 deletions
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
index c8c33843e..895ff3fe2 100644
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -4024,12 +4024,9 @@ class IndexNode(_IndexingBaseNode):
else:
assert False, "unexpected base type in indexing: %s" % self.base.type
elif self.base.type.is_cfunction:
- if self.base.entry.is_cgetter:
- index_code = "(%s[%s])"
- else:
- return "%s<%s>" % (
- self.base.result(),
- ",".join([param.empty_declaration_code() for param in self.type_indices]))
+ return "%s<%s>" % (
+ self.base.result(),
+ ",".join([param.empty_declaration_code() for param in self.type_indices]))
elif self.base.type.is_ctuple:
index = self.index.constant_result
if index < 0:
@@ -5594,6 +5591,16 @@ class SimpleCallNode(CallNode):
except Exception as e:
self.compile_time_value_error(e)
+ @classmethod
+ def for_cproperty(cls, pos, obj, entry):
+ # Create a call node for C property access.
+ property_scope = entry.scope
+ getter_entry = property_scope.lookup_here(entry.name)
+ assert getter_entry, "Getter not found in scope %s: %s" % (property_scope, property_scope.entries)
+ function = NameNode(pos, name=entry.name, entry=getter_entry, type=getter_entry.type)
+ node = cls(pos, function=function, args=[obj])
+ return node
+
def analyse_as_type(self, env):
attr = self.function.as_cython_attribute()
if attr == 'pointer':
@@ -6789,7 +6796,7 @@ class AttributeNode(ExprNode):
is_attribute = 1
subexprs = ['obj']
- _type = PyrexTypes.error_type
+ type = PyrexTypes.error_type
entry = None
is_called = 0
needs_none_check = True
@@ -6797,20 +6804,6 @@ class AttributeNode(ExprNode):
is_special_lookup = False
is_py_attr = 0
- @property
- def type(self):
- if self._type.is_cfunction and hasattr(self._type, 'entry') and self._type.entry.is_cgetter:
- return self._type.return_type
- return self._type
-
- @type.setter
- def type(self, value):
- # XXX review where the attribute is set
- # make sure it is not already a cgetter
- if self._type.is_cfunction and hasattr(self._type, 'entry') and self._type.entry.is_cgetter:
- error(self.pos, "%s.type already set" % self.__name__)
- self._type = value
-
def as_cython_attribute(self):
if (isinstance(self.obj, NameNode) and
self.obj.is_cython_module and not
@@ -6896,7 +6889,7 @@ class AttributeNode(ExprNode):
if node is None:
node = self.analyse_as_ordinary_attribute_node(env, target)
assert node is not None
- if node.entry:
+ if (node.is_attribute or node.is_name) and node.entry:
node.entry.used = True
if node.is_attribute:
node.wrap_obj_in_nonecheck(env)
@@ -7029,6 +7022,11 @@ class AttributeNode(ExprNode):
self.result_ctype = py_object_type
elif target and self.obj.type.is_builtin_type:
error(self.pos, "Assignment to an immutable object field")
+ elif self.entry and self.entry.is_cproperty:
+ if not target:
+ return SimpleCallNode.for_cproperty(self.pos, self.obj, self.entry).analyse_types(env)
+ # TODO: implement writable C-properties?
+ error(self.pos, "Assignment to a read-only property")
#elif self.type.is_memoryviewslice and not target:
# self.is_temp = True
return self
@@ -7085,8 +7083,11 @@ class AttributeNode(ExprNode):
# fused function go through assignment synthesis
# (foo = pycfunction(foo_func_obj)) and need to go through
# regular Python lookup as well
- if (entry.is_variable and not entry.fused_cfunction) or entry.is_cmethod:
- self._type = entry.type
+ if entry.is_cproperty:
+ self.type = entry.type
+ return
+ elif (entry.is_variable and not entry.fused_cfunction) or entry.is_cmethod:
+ self.type = entry.type
self.member = entry.cname
return
else:
@@ -7106,7 +7107,7 @@ class AttributeNode(ExprNode):
# mangle private '__*' Python attributes used inside of a class
self.attribute = env.mangle_class_private_name(self.attribute)
self.member = self.attribute
- self._type = py_object_type
+ self.type = py_object_type
self.is_py_attr = 1
if not obj_type.is_pyobject and not obj_type.is_error:
@@ -7188,8 +7189,6 @@ class AttributeNode(ExprNode):
obj_code = obj.result_as(obj.type)
#print "...obj_code =", obj_code ###
if self.entry and self.entry.is_cmethod:
- if self.entry.is_cgetter:
- return "%s(%s)" % (self.entry.func_cname, obj_code)
if obj.type.is_extension_type and not self.entry.is_builtin_cmethod:
if self.entry.final_func_cname:
return self.entry.final_func_cname
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
index aa6741bff..5af5323c3 100644
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -21,7 +21,7 @@ from . import Naming
from . import PyrexTypes
from . import TypeSlots
from .PyrexTypes import py_object_type, error_type
-from .Symtab import (ModuleScope, LocalScope, ClosureScope,
+from .Symtab import (ModuleScope, LocalScope, ClosureScope, PropertyScope,
StructOrUnionScope, PyClassScope, CppClassScope, TemplateScope,
punycodify_name)
from .Code import UtilityCode
@@ -154,6 +154,7 @@ class Node(object):
is_literal = 0
is_terminator = 0
is_wrapper = False # is a DefNode wrapper for a C function
+ is_cproperty = False
temps = None
# All descendants should set child_attrs to a list of the attributes
@@ -453,8 +454,13 @@ class CDefExternNode(StatNode):
env.add_include_file(self.include_file, self.verbatim_include, late)
def analyse_expressions(self, env):
+ # Allow C properties, inline methods, etc. also in external types.
+ self.body = self.body.analyse_expressions(env)
return self
+ def generate_function_definitions(self, env, code):
+ self.body.generate_function_definitions(env, code)
+
def generate_execution_code(self, code):
pass
@@ -479,6 +485,9 @@ class CDeclaratorNode(Node):
calling_convention = ""
+ def declared_name(self):
+ return None
+
def analyse_templates(self):
# Only C++ functions have templates.
return None
@@ -493,6 +502,9 @@ class CNameDeclaratorNode(CDeclaratorNode):
default = None
+ def declared_name(self):
+ return self.name
+
def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False):
if nonempty and self.name == '':
# May have mistaken the name for the type.
@@ -516,6 +528,9 @@ class CPtrDeclaratorNode(CDeclaratorNode):
child_attrs = ["base"]
+ def declared_name(self):
+ return self.base.declared_name()
+
def analyse_templates(self):
return self.base.analyse_templates()
@@ -531,6 +546,9 @@ class CReferenceDeclaratorNode(CDeclaratorNode):
child_attrs = ["base"]
+ def declared_name(self):
+ return self.base.declared_name()
+
def analyse_templates(self):
return self.base.analyse_templates()
@@ -603,6 +621,9 @@ class CFuncDeclaratorNode(CDeclaratorNode):
is_const_method = 0
templates = None
+ def declared_name(self):
+ return self.base.declared_name()
+
def analyse_templates(self):
if isinstance(self.base, CArrayDeclaratorNode):
from .ExprNodes import TupleNode, NameNode
@@ -836,6 +857,9 @@ class CArgDeclNode(Node):
annotation = None
is_dynamic = 0
+ def declared_name(self):
+ return self.declarator.declared_name()
+
@property
def name_cstring(self):
return self.name.as_c_string_literal()
@@ -1728,10 +1752,6 @@ class FuncDefNode(StatNode, BlockNode):
def generate_function_definitions(self, env, code):
from . import Buffer
- if self.entry.is_cgetter:
- # no code to generate
- return
-
lenv = self.local_scope
if lenv.is_closure_scope and not lenv.is_passthrough:
outer_scope_cname = "%s->%s" % (Naming.cur_scope_cname,
@@ -2344,7 +2364,7 @@ class CFuncDefNode(FuncDefNode):
# is_static_method whether this is a static method
# is_c_class_method whether this is a cclass method
- child_attrs = ["base_type", "declarator", "body", "py_func_stat", "decorators"]
+ child_attrs = ["base_type", "declarator", "body", "decorators", "py_func_stat"]
outer_attrs = ["decorators", "py_func_stat"]
inline_in_pxd = False
@@ -2359,27 +2379,15 @@ class CFuncDefNode(FuncDefNode):
def unqualified_name(self):
return self.entry.name
+ def declared_name(self):
+ return self.declarator.declared_name()
+
@property
def code_object(self):
# share the CodeObject with the cpdef wrapper (if available)
return self.py_func.code_object if self.py_func else None
def analyse_declarations(self, env):
- is_property = 0
- if self.decorators:
- for decorator in self.decorators:
- func = decorator.decorator
- if func.is_name:
- if func.name == 'property':
- is_property = 1
- elif func.name == 'staticmethod':
- pass
- else:
- error(self.pos, "Cannot handle %s decorators yet" % func.name)
- else:
- error(self.pos,
- "Cannot handle %s decorators yet" % type(func).__name__)
-
self.is_c_class_method = env.is_c_class_scope
if self.directive_locals is None:
self.directive_locals = {}
@@ -2458,10 +2466,6 @@ class CFuncDefNode(FuncDefNode):
cname=cname, visibility=self.visibility, api=self.api,
defining=self.body is not None, modifiers=self.modifiers,
overridable=self.overridable)
- if is_property:
- self.entry.is_property = 1
- env.property_entries.append(self.entry)
- env.cfunc_entries.remove(self.entry)
self.entry.inline_func_in_pxd = self.inline_in_pxd
self.return_type = typ.return_type
if self.return_type.is_array and self.visibility != 'extern':
@@ -2628,7 +2632,7 @@ class CFuncDefNode(FuncDefNode):
header = self.return_type.declaration_code(entity, dll_linkage=dll_linkage)
#print (storage_class, modifiers, header)
- needs_proto = self.is_c_class_method
+ needs_proto = self.is_c_class_method or self.entry.is_cproperty
if self.template_declaration:
if needs_proto:
code.globalstate.parts['module_declarations'].putln(self.template_declaration)
@@ -5332,14 +5336,13 @@ class PropertyNode(StatNode):
#
# name string
# doc EncodedString or None Doc string
- # entry Symtab.Entry
+ # entry Symtab.Entry The Entry of the property attribute
# body StatListNode
child_attrs = ["body"]
def analyse_declarations(self, env):
self.entry = env.declare_property(self.name, self.doc, self.pos)
- self.entry.scope.directives = env.directives
self.body.analyse_declarations(self.entry.scope)
def analyse_expressions(self, env):
@@ -5356,6 +5359,44 @@ class PropertyNode(StatNode):
self.body.annotate(code)
+class CPropertyNode(StatNode):
+ """Definition of a C property, backed by a CFuncDefNode getter.
+ """
+ # name string
+ # doc EncodedString or None Doc string of the property
+ # entry Symtab.Entry The Entry of the property attribute
+ # body StatListNode[CFuncDefNode] (for compatibility with PropertyNode)
+
+ child_attrs = ["body"]
+ is_cproperty = True
+
+ @property
+ def cfunc(self):
+ stats = self.body.stats
+ assert stats and isinstance(stats[0], CFuncDefNode), stats
+ return stats[0]
+
+ def analyse_declarations(self, env):
+ scope = PropertyScope(self.name, class_scope=env)
+ self.body.analyse_declarations(scope)
+ entry = self.entry = env.declare_property(
+ self.name, self.doc, self.pos, ctype=self.cfunc.return_type, property_scope=scope)
+ entry.getter_cname = self.cfunc.entry.cname
+
+ def analyse_expressions(self, env):
+ self.body = self.body.analyse_expressions(env)
+ return self
+
+ def generate_function_definitions(self, env, code):
+ self.body.generate_function_definitions(env, code)
+
+ def generate_execution_code(self, code):
+ pass
+
+ def annotate(self, code):
+ self.body.annotate(code)
+
+
class GlobalNode(StatNode):
# Global variable declaration.
#
diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py
index d722529b8..15feda18f 100644
--- a/Cython/Compiler/ParseTreeTransforms.py
+++ b/Cython/Compiler/ParseTreeTransforms.py
@@ -593,14 +593,12 @@ class PxdPostParse(CythonTransform, SkipDeclarations):
err = self.ERR_INLINE_ONLY
if (isinstance(node, Nodes.DefNode) and self.scope_type == 'cclass'
- and node.name in ('__getbuffer__', '__releasebuffer__')):
+ and node.name in ('__getbuffer__', '__releasebuffer__')):
err = None # allow these slots
if isinstance(node, Nodes.CFuncDefNode):
- if node.decorators and self.scope_type == 'cclass':
- err = None
- elif (u'inline' in node.modifiers and
- self.scope_type in ('pxd', 'cclass')):
+ if (u'inline' in node.modifiers and
+ self.scope_type in ('pxd', 'cclass')):
node.inline_in_pxd = True
if node.visibility != 'private':
err = self.ERR_NOGO_WITH_INLINE % node.visibility
@@ -1365,7 +1363,7 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
if self._properties is None:
self._properties = []
self._properties.append({})
- super(DecoratorTransform, self).visit_CClassDefNode(node)
+ node = super(DecoratorTransform, self).visit_CClassDefNode(node)
self._properties.pop()
return node
@@ -1375,6 +1373,25 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
warning(node.pos, "'property %s:' syntax is deprecated, use '@property'" % node.name, level)
return node
+ def visit_CFuncDefNode(self, node):
+ node = self.visit_FuncDefNode(node)
+ if self.scope_type != 'cclass' or self.scope_node.visibility != "extern" or not node.decorators:
+ return node
+
+ ret_node = node
+ decorator_node = self._find_property_decorator(node)
+ if decorator_node:
+ if decorator_node.decorator.is_name:
+ name = node.declared_name()
+ if name:
+ ret_node = self._add_property(node, name, decorator_node)
+ else:
+ error(decorator_node.pos, "C property decorator can only be @property")
+
+ if node.decorators:
+ return self._reject_decorated_property(node, node.decorators[0])
+ return ret_node
+
def visit_DefNode(self, node):
scope_type = self.scope_type
node = self.visit_FuncDefNode(node)
@@ -1382,28 +1399,12 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
return node
# transform @property decorators
- properties = self._properties[-1]
- for decorator_node in node.decorators[::-1]:
+ decorator_node = self._find_property_decorator(node)
+ if decorator_node is not None:
decorator = decorator_node.decorator
- if decorator.is_name and decorator.name == 'property':
- if len(node.decorators) > 1:
- return self._reject_decorated_property(node, decorator_node)
- name = node.name
- node.name = EncodedString('__get__')
- node.decorators.remove(decorator_node)
- stat_list = [node]
- if name in properties:
- prop = properties[name]
- prop.pos = node.pos
- prop.doc = node.doc
- prop.body.stats = stat_list
- return []
- prop = Nodes.PropertyNode(node.pos, name=name)
- prop.doc = node.doc
- prop.body = Nodes.StatListNode(node.pos, stats=stat_list)
- properties[name] = prop
- return [prop]
- elif decorator.is_attribute and decorator.obj.name in properties:
+ if decorator.is_name:
+ return self._add_property(node, node.name, decorator_node)
+ else:
handler_name = self._map_property_attribute(decorator.attribute)
if handler_name:
if decorator.obj.name != node.name:
@@ -1414,7 +1415,7 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
elif len(node.decorators) > 1:
return self._reject_decorated_property(node, decorator_node)
else:
- return self._add_to_property(properties, node, handler_name, decorator_node)
+ return self._add_to_property(node, handler_name, decorator_node)
# we clear node.decorators, so we need to set the
# is_staticmethod/is_classmethod attributes now
@@ -1429,6 +1430,18 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
node.decorators = None
return self.chain_decorators(node, decs, node.name)
+ def _find_property_decorator(self, node):
+ properties = self._properties[-1]
+ for decorator_node in node.decorators[::-1]:
+ decorator = decorator_node.decorator
+ if decorator.is_name and decorator.name == 'property':
+ # @property
+ return decorator_node
+ elif decorator.is_attribute and decorator.obj.name in properties:
+ # @prop.setter etc.
+ return decorator_node
+ return None
+
@staticmethod
def _reject_decorated_property(node, decorator_node):
# restrict transformation to outermost decorator as wrapped properties will probably not work
@@ -1437,9 +1450,42 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
error(deco.pos, "Property methods with additional decorators are not supported")
return node
- @staticmethod
- def _add_to_property(properties, node, name, decorator):
+ def _add_property(self, node, name, decorator_node):
+ if len(node.decorators) > 1:
+ return self._reject_decorated_property(node, decorator_node)
+ node.decorators.remove(decorator_node)
+ properties = self._properties[-1]
+ is_cproperty = isinstance(node, Nodes.CFuncDefNode)
+ body = Nodes.StatListNode(node.pos, stats=[node])
+ if is_cproperty:
+ if name in properties:
+ error(node.pos, "C property redeclared")
+ if 'inline' not in node.modifiers:
+ error(node.pos, "C property method must be declared 'inline'")
+ prop = Nodes.CPropertyNode(node.pos, doc=node.doc, name=name, body=body)
+ elif name in properties:
+ prop = properties[name]
+ if prop.is_cproperty:
+ error(node.pos, "C property redeclared")
+ else:
+ node.name = EncodedString("__get__")
+ prop.pos = node.pos
+ prop.doc = node.doc
+ prop.body.stats = [node]
+ return None
+ else:
+ node.name = EncodedString("__get__")
+ prop = Nodes.PropertyNode(
+ node.pos, name=name, doc=node.doc, body=body)
+ properties[name] = prop
+ return prop
+
+ def _add_to_property(self, node, name, decorator):
+ properties = self._properties[-1]
prop = properties[node.name]
+ if prop.is_cproperty:
+ error(node.pos, "C property redeclared")
+ return None
node.name = name
node.decorators.remove(decorator)
stats = prop.body.stats
@@ -1449,7 +1495,7 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
break
else:
stats.append(node)
- return []
+ return None
@staticmethod
def chain_decorators(node, decorators, name):
@@ -2274,29 +2320,6 @@ class AnalyseExpressionsTransform(CythonTransform):
node = node.base
return node
-class ReplacePropertyNode(CythonTransform):
- def visit_CFuncDefNode(self, node):
- if not node.decorators:
- return node
- decorator = self.find_first_decorator(node, 'property')
- if decorator:
- # transform class functions into c-getters
- if len(node.decorators) > 1:
- # raises
- self._reject_decorated_property(node, decorator_node)
- node.entry.is_cgetter = True
- # Add a func_cname to be output instead of the attribute
- node.entry.func_cname = node.body.stats[0].value.function.name
- node.decorators.remove(decorator)
- return node
-
- def find_first_decorator(self, node, name):
- for decorator_node in node.decorators[::-1]:
- decorator = decorator_node.decorator
- if decorator.is_name and decorator.name == name:
- return decorator_node
- return None
-
class FindInvalidUseOfFusedTypes(CythonTransform):
diff --git a/Cython/Compiler/Pipeline.py b/Cython/Compiler/Pipeline.py
index f4fbb6e11..5194c3e49 100644
--- a/Cython/Compiler/Pipeline.py
+++ b/Cython/Compiler/Pipeline.py
@@ -146,7 +146,7 @@ def create_pipeline(context, mode, exclude_classes=()):
from .ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
from .ParseTreeTransforms import TrackNumpyAttributes, InterpretCompilerDirectives, TransformBuiltinMethods
from .ParseTreeTransforms import ExpandInplaceOperators, ParallelRangeTransform
- from .ParseTreeTransforms import CalculateQualifiedNamesTransform, ReplacePropertyNode
+ from .ParseTreeTransforms import CalculateQualifiedNamesTransform
from .TypeInference import MarkParallelAssignments, MarkOverflowingArithmetic
from .ParseTreeTransforms import AdjustDefByDirectives, AlignFunctionDefinitions
from .ParseTreeTransforms import RemoveUnreachableCode, GilCheck
@@ -198,7 +198,6 @@ def create_pipeline(context, mode, exclude_classes=()):
AnalyseDeclarationsTransform(context),
AutoTestDictTransform(context),
EmbedSignature(context),
- ReplacePropertyNode(context),
EarlyReplaceBuiltinCalls(context), ## Necessary?
TransformBuiltinMethods(context),
MarkParallelAssignments(context),
diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py
index 98c57fc5f..f3618ffd2 100644
--- a/Cython/Compiler/PyrexTypes.py
+++ b/Cython/Compiler/PyrexTypes.py
@@ -1691,15 +1691,21 @@ class PythranExpr(CType):
self.scope = scope = Symtab.CClassScope('', None, visibility="extern")
scope.parent_type = self
scope.directives = {}
- scope.declare_cgetter(
- "shape",
- CPtrType(c_long_type),
- pos=None,
- cname="__Pyx_PythranShapeAccessor",
- visibility="extern",
- nogil=True)
+
scope.declare_var("ndim", c_long_type, pos=None, cname="value", is_cdef=True)
+ shape_type = c_ptr_type(c_long_type)
+ shape_entry = scope.declare_property(
+ "shape", doc="Pythran array shape", ctype=shape_type, pos=None)
+ shape_entry.scope.declare_cfunction(
+ name="shape",
+ type=CFuncType(shape_type, [CFuncTypeArg("self", self, pos=None)], nogil=True),
+ cname="__Pyx_PythranShapeAccessor",
+ visibility='extern',
+ pos=None,
+ )
+
+
return True
def __eq__(self, other):
diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py
index dcf5a9fac..17b99be11 100644
--- a/Cython/Compiler/Symtab.py
+++ b/Cython/Compiler/Symtab.py
@@ -109,6 +109,7 @@ class Entry(object):
# doc_cname string or None C const holding the docstring
# getter_cname string C func for getting property
# setter_cname string C func for setting or deleting property
+ # is_cproperty boolean Is an inline property of an external type
# is_self_arg boolean Is the "self" arg of an exttype method
# is_arg boolean Is the arg of a method
# is_local boolean Is a local variable
@@ -183,6 +184,7 @@ class Entry(object):
is_cpp_class = 0
is_const = 0
is_property = 0
+ is_cproperty = 0
doc_cname = None
getter_cname = None
setter_cname = None
@@ -2395,15 +2397,18 @@ class CClassScope(ClassScope):
entry.as_variable = var_entry
return entry
- def declare_property(self, name, doc, pos):
+ def declare_property(self, name, doc, pos, ctype=None, property_scope=None):
entry = self.lookup_here(name)
if entry is None:
- entry = self.declare(name, name, py_object_type, pos, 'private')
- entry.is_property = 1
+ entry = self.declare(name, name, py_object_type if ctype is None else ctype, pos, 'private')
+ entry.is_property = True
+ if ctype is not None:
+ entry.is_cproperty = True
entry.doc = doc
- entry.scope = PropertyScope(name,
- outer_scope = self.global_scope(), parent_scope = self)
- entry.scope.parent_type = self.parent_type
+ if property_scope is None:
+ entry.scope = PropertyScope(name, class_scope=self)
+ else:
+ entry.scope = property_scope
self.property_entries.append(entry)
return entry
@@ -2607,6 +2612,31 @@ class PropertyScope(Scope):
is_property_scope = 1
+ def __init__(self, name, class_scope):
+ # outer scope is None for some internal properties
+ outer_scope = class_scope.global_scope() if class_scope.outer_scope else None
+ Scope.__init__(self, name, outer_scope, parent_scope=class_scope)
+ self.parent_type = class_scope.parent_type
+ self.directives = class_scope.directives
+
+ def declare_cfunction(self, name, type, pos, *args, **kwargs):
+ """Declare a C property function.
+ """
+ if type.return_type.is_void:
+ error(pos, "C property method cannot return 'void'")
+
+ if type.args and type.args[0].type is py_object_type:
+ # Set 'self' argument type to extension type.
+ type.args[0].type = self.parent_scope.parent_type
+ elif len(type.args) != 1:
+ error(pos, "C property method must have a single (self) argument")
+ elif not (type.args[0].type.is_pyobject or type.args[0].type is self.parent_scope.parent_type):
+ error(pos, "C property method must have a single (object) argument")
+
+ entry = Scope.declare_cfunction(self, name, type, pos, *args, **kwargs)
+ entry.is_cproperty = True
+ return entry
+
def declare_pyfunction(self, name, pos, allow_redefine=False):
# Add an entry for a method.
signature = get_property_accessor_signature(name)
diff --git a/Cython/Includes/numpy/__init__.pxd b/Cython/Includes/numpy/__init__.pxd
index 3e765498e..1bcd588f5 100644
--- a/Cython/Includes/numpy/__init__.pxd
+++ b/Cython/Includes/numpy/__init__.pxd
@@ -244,25 +244,33 @@ cdef extern from "numpy/arrayobject.h":
cdef:
# Only taking a few of the most commonly used and stable fields.
- char *data
- dtype descr # deprecated since NumPy 1.7 !
- PyObject* base
+ dtype descr # deprecated since NumPy 1.7 !
+ PyObject* base # NOT PUBLIC, DO NOT USE !
@property
- cdef int ndim(self):
+ cdef inline int ndim(self):
return PyArray_NDIM(self)
@property
- cdef npy_intp *shape(self):
+ cdef inline npy_intp *shape(self):
return PyArray_DIMS(self)
@property
- cdef npy_intp *strides(self):
+ cdef inline npy_intp *strides(self):
return PyArray_STRIDES(self)
@property
- cdef npy_intp size(self):
- return PyArray_SIZE(ndarray)
+ cdef inline npy_intp size(self) nogil:
+ return PyArray_SIZE(self)
+
+ @property
+ cdef inline char* data(self) nogil:
+ """The pointer to the data buffer as a char*.
+ This is provided for legacy reasons to avoid direct struct field access.
+ For new code that needs this access, you probably want to cast the result
+ of `PyArray_DATA()` instead.
+ """
+ return PyArray_BYTES(self)
# Note: This syntax (function definition in pxd files) is an
@@ -449,8 +457,9 @@ cdef extern from "numpy/arrayobject.h":
bint PyArray_ISFORTRAN(ndarray)
int PyArray_FORTRANIF(ndarray)
- void* PyArray_DATA(ndarray)
- char* PyArray_BYTES(ndarray)
+ void* PyArray_DATA(ndarray) nogil
+ char* PyArray_BYTES(ndarray) nogil
+
npy_intp* PyArray_DIMS(ndarray)
npy_intp* PyArray_STRIDES(ndarray)
npy_intp PyArray_DIM(ndarray, size_t)
@@ -551,7 +560,7 @@ cdef extern from "numpy/arrayobject.h":
bint PyArray_CheckAnyScalar(object)
ndarray PyArray_GETCONTIGUOUS(ndarray)
bint PyArray_SAMESHAPE(ndarray, ndarray)
- npy_intp PyArray_SIZE(ndarray)
+ npy_intp PyArray_SIZE(ndarray) nogil
npy_intp PyArray_NBYTES(ndarray)
object PyArray_FROM_O(object)
diff --git a/tests/run/ext_attr_getter.srctree b/tests/run/ext_attr_getter.srctree
index 682b47eaf..76f059ed7 100644
--- a/tests/run/ext_attr_getter.srctree
+++ b/tests/run/ext_attr_getter.srctree
@@ -1,22 +1,58 @@
+# mode: run
+# tag: cgetter, property
+
+"""
PYTHON setup.py build_ext --inplace
-PYTHON -c "import runner"
+PYTHON run_failure_tests.py
+PYTHON runner.py
+"""
######## setup.py ########
from Cython.Build.Dependencies import cythonize
-from Cython.Compiler.Errors import CompileError
from distutils.core import setup
-# force the build order
+# Enforce the right build order
setup(ext_modules = cythonize("foo_extension.pyx", language_level=3))
setup(ext_modules = cythonize("getter[0-9].pyx", language_level=3))
-for name in ("getter_fail0.pyx", "getter_fail1.pyx"):
+
+######## run_failure_tests.py ########
+
+import glob
+import sys
+
+from Cython.Build.Dependencies import cythonize
+from Cython.Compiler.Errors import CompileError
+
+# Run the failure tests
+failed_tests = []
+passed_tests = []
+
+def run_test(name):
+ title = name
+ with open(name, 'r') as f:
+ for line in f:
+ if 'TEST' in line:
+ title = line.partition('TEST:')[2].strip()
+ break
+ sys.stderr.write("\n### TESTING: %s\n" % title)
+
try:
cythonize(name, language_level=3)
- assert False
except CompileError as e:
- print("\nGot expected exception, continuing\n")
+ sys.stderr.write("\nOK: got expected exception\n")
+ passed_tests.append(name)
+ else:
+ sys.stderr.write("\nFAIL: compilation did not detect the error\n")
+ failed_tests.append(name)
+
+for name in sorted(glob.glob("getter_fail*.pyx")):
+ run_test(name)
+
+assert not failed_tests, "Failed tests: %s" % failed_tests
+assert passed_tests # check that tests were found at all
+
######## foo.h ########
@@ -39,9 +75,9 @@ typedef struct {
} FooStructOpaque;
-#define PyFoo_GET0M(a) ((FooStructNominal*)a)->f0
-#define PyFoo_GET1M(a) ((FooStructNominal*)a)->f1
-#define PyFoo_GET2M(a) ((FooStructNominal*)a)->f2
+#define PyFoo_GET0M(a) (((FooStructNominal*)a)->f0)
+#define PyFoo_GET1M(a) (((FooStructNominal*)a)->f1)
+#define PyFoo_GET2M(a) (((FooStructNominal*)a)->f2)
int PyFoo_Get0F(FooStructOpaque *f)
{
@@ -67,6 +103,7 @@ int *PyFoo_GetV(FooStructOpaque *f)
}
#endif
+
######## foo_extension.pyx ########
cdef class Foo:
@@ -117,6 +154,7 @@ class OpaqueFoo(Foo):
def field2(self):
raise AttributeError('no direct access to field2')
+
######## getter0.pyx ########
# Access base Foo fields from C via aliased field names
@@ -138,6 +176,7 @@ def check_pyobj(Foo f):
# compare the c code to the check_pyobj in getter2.pyx
return bool(f.field1)
+
######## getter.pxd ########
# Access base Foo fields from C via getter functions
@@ -146,26 +185,33 @@ def check_pyobj(Foo f):
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque, check_size ignore]:
@property
- cdef int fieldM0(self):
+ cdef inline int fieldM0(self):
return PyFoo_GET0M(self)
@property
- cdef int fieldF1(self):
+ cdef inline int fieldF1(self) except -123:
return PyFoo_Get1F(self)
@property
- cdef int fieldM2(self):
+ cdef inline int fieldM2(self):
return PyFoo_GET2M(self)
@property
- cdef int *vector(self):
+ cdef inline int *vector(self):
return PyFoo_GetV(self)
+ @property
+ cdef inline int meaning_of_life(self) except -99:
+ cdef int ret = 21
+ ret *= 2
+ return ret
+
int PyFoo_GET0M(Foo); # this is actually a macro !
int PyFoo_Get1F(Foo);
int PyFoo_GET2M(Foo); # this is actually a macro !
int *PyFoo_GetV(Foo);
+
######## getter1.pyx ########
cimport getter
@@ -184,6 +230,7 @@ def vec0(getter.Foo f):
def check_binop(getter.Foo f):
return f.fieldF1 / 10
+
######## getter2.pyx ########
cimport getter
@@ -194,27 +241,81 @@ def check_pyobj(getter.Foo f):
def check_unary(getter.Foo f):
return -f.fieldF1
-######## getter_fail0.pyx ########
+def check_meaning_of_life(getter.Foo f):
+ return f.meaning_of_life
+
-# Make sure not all decorators are accepted
+######## getter_fail_classmethod.pyx ########
+
+# TEST: Make sure not all decorators are accepted.
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque]:
+ @property
@classmethod
- cdef void field0():
- print('in staticmethod of Foo')
+ cdef inline int field0(cls):
+ print('in classmethod of Foo')
-######## getter_fail1.pyx ########
-# Make sure not all decorators are accepted
-cimport cython
+######## getter_fail_dot_getter.pyx ########
+
+# TEST: Make sure not all decorators are accepted.
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque]:
- @prop.getter
+ @property
+ cdef inline int field0(self):
+ pass
+
+ @field0.getter
+ cdef inline void field1(self):
+ pass
+
+
+######## getter_fail_no_inline.pyx ########
+
+# TEST: Properties must be declared "inline".
+
+cdef extern from "foo.h":
+ ctypedef class foo_extension.Foo [object FooStructOpaque]:
+ @property
+ cdef int field0(self):
+ pass
+
+
+######## getter_fail_void.pyx ########
+
+# TEST: Properties must have a non-void return type.
+
+cdef extern from "foo.h":
+ ctypedef class foo_extension.Foo [object FooStructOpaque]:
+ @property
cdef void field0(self):
pass
+
+######## getter_fail_no_args.pyx ########
+
+# TEST: Properties must have the right signature.
+
+cdef extern from "foo.h":
+ ctypedef class foo_extension.Foo [object FooStructOpaque]:
+ @property
+ cdef int field0():
+ pass
+
+
+######## getter_fail_too_many_args.pyx ########
+
+# TEST: Properties must have the right signature.
+
+cdef extern from "foo.h":
+ ctypedef class foo_extension.Foo [object FooStructOpaque]:
+ @property
+ cdef int field0(x, y):
+ pass
+
+
######## runner.py ########
import warnings
@@ -247,6 +348,8 @@ val = getter2.check_pyobj(opaque_foo)
assert val is True
val = getter2.check_unary(opaque_foo)
assert val == -123
+val = getter2.check_meaning_of_life(opaque_foo)
+assert val == 42
try:
f0 = opaque_ret.field0
diff --git a/tests/run/numpy_parallel.pyx b/tests/run/numpy_parallel.pyx
index da1444c38..96a60be14 100644
--- a/tests/run/numpy_parallel.pyx
+++ b/tests/run/numpy_parallel.pyx
@@ -21,7 +21,7 @@ def test_parallel_numpy_arrays():
3
4
"""
- cdef Py_ssize_t i
+ cdef Py_ssize_t i, length
cdef np.ndarray[np.int_t] x
try:
@@ -32,10 +32,10 @@ def test_parallel_numpy_arrays():
return
x = numpy.zeros(10, dtype=numpy.int)
+ length = x.shape[0]
- for i in prange(x.shape[0], nogil=True):
+ for i in prange(length, nogil=True):
x[i] = i - 5
for i in x:
- print i
-
+ print(i)