Commit 9b045609 authored by Emmanuel Gil Peyrot's avatar Emmanuel Gil Peyrot

Add a pass transforming Python-style properties in cdef class into Cython-style properties.

This makes properties work properly in cdef classes, and gives them the
exact same AST as the “property something:” blocks, whose syntax should
probably be deprecated now.

Fixes T264.
parent 558613a5
......@@ -1270,6 +1270,75 @@ class WithTransform(CythonTransform, SkipDeclarations):
return node
class PropertyTransform(ScopeTrackingTransform):
"""
This pass transforms Python-style decorator properties into a
PropertyNode with up to the three getter, setter and deleter
DefNode.
The functional style isn't supported yet.
"""
def visit_CClassDefNode(self, node):
node._properties = {}
self.visit_scope(node, 'cclass')
del node._properties
return node
def visit_DefNode(self, node):
self.visitchildren(node)
if self.scope_type != 'cclass' or not node.decorators:
return node
properties = self.scope_node._properties
for decorator_node in node.decorators[::-1]:
decorator = decorator_node.decorator
if (isinstance(decorator, ExprNodes.NameNode) and
decorator.name == 'property'):
name = node.name
node.name = '__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 self._test_property_attribute(decorator, 'getter', properties):
assert decorator.obj.name == node.name
return self._add_to_property(properties, node, '__get__', decorator_node)
elif self._test_property_attribute(decorator, 'setter', properties):
assert decorator.obj.name == node.name
return self._add_to_property(properties, node, '__set__', decorator_node)
elif self._test_property_attribute(decorator, 'deleter', properties):
assert decorator.obj.name == node.name
return self._add_to_property(properties, node, '__del__', decorator_node)
return node
def _test_property_attribute(self, decorator, name, properties):
return (isinstance(decorator, ExprNodes.AttributeNode) and
decorator.attribute == name and
decorator.obj.name in properties)
def _add_to_property(self, properties, node, name, decorator):
prop = properties[node.name]
node.name = name
node.decorators.remove(decorator)
stats = prop.body.stats
for i, stat in enumerate(stats):
if stat.name == name:
stats[i] = node
break
else:
stats.append(node)
return []
class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
"""Originally, this was the only place where decorators were
transformed into the corresponding calling code. Now, this is
......
......@@ -171,7 +171,7 @@ def create_pipeline(context, mode, exclude_classes=()):
from .ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
from .ParseTreeTransforms import InterpretCompilerDirectives, TransformBuiltinMethods
from .ParseTreeTransforms import ExpandInplaceOperators, ParallelRangeTransform
from .ParseTreeTransforms import CalculateQualifiedNamesTransform
from .ParseTreeTransforms import CalculateQualifiedNamesTransform, PropertyTransform
from .TypeInference import MarkParallelAssignments, MarkOverflowingArithmetic
from .ParseTreeTransforms import AdjustDefByDirectives, AlignFunctionDefinitions
from .ParseTreeTransforms import RemoveUnreachableCode, GilCheck
......@@ -216,6 +216,7 @@ def create_pipeline(context, mode, exclude_classes=()):
RemoveUnreachableCode(context),
ConstantFolding(),
FlattenInListTransform(),
PropertyTransform(context),
DecoratorTransform(context),
ForwardDeclareTypes(context),
AnalyseDeclarationsTransform(context),
......
# mode: run
# ticket: 264
# tag: property, decorator
cdef class Prop:
"""
>>> p = Prop()
>>> p.prop
GETTING 'None'
>>> p.prop = 1
SETTING '1' (previously: 'None')
>>> p.prop
GETTING '1'
1
>>> p.prop = 2
SETTING '2' (previously: '1')
>>> p.prop
GETTING '2'
2
>>> del p.prop
DELETING '2'
>>> p.prop
GETTING 'None'
"""
cdef _value
def __init__(self):
self._value = None
@property
def prop(self):
print("FAIL")
return 0
@prop.getter
def prop(self):
print("FAIL")
@property
def prop(self):
print("GETTING '%s'" % self._value)
return self._value
@prop.setter
def prop(self, value):
print("SETTING '%s' (previously: '%s')" % (value, self._value))
self._value = value
@prop.deleter
def prop(self):
print("DELETING '%s'" % self._value)
self._value = None
# mode: run
# ticket: 264
# tag: property, decorator
cdef class Prop:
cdef _value
# mode: run
# ticket: 264
# tag: property, decorator
class Prop(object):
"""
>>> p = Prop()
>>> p.prop
GETTING 'None'
>>> p.prop = 1
SETTING '1' (previously: 'None')
>>> p.prop
GETTING '1'
1
>>> p.prop = 2
SETTING '2' (previously: '1')
>>> p.prop
GETTING '2'
2
>>> del p.prop
DELETING '2'
>>> p.prop
GETTING 'None'
"""
def __init__(self):
self._value = None
@property
def prop(self):
print("FAIL")
return 0
@prop.getter
def prop(self):
print("FAIL")
@property
def prop(self):
print("GETTING '%s'" % self._value)
return self._value
@prop.setter
def prop(self, value):
print("SETTING '%s' (previously: '%s')" % (value, self._value))
self._value = value
@prop.deleter
def prop(self):
print("DELETING '%s'" % self._value)
self._value = None
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