Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: allow @property decorator on external ctypedef classes #2640

Merged
merged 16 commits into from Jan 16, 2019

Conversation

mattip
Copy link
Contributor

@mattip mattip commented Oct 2, 2018

Following up the discussion on the mailing list. I got to the point where I can use the new syntax:

  • Write a srctree test that creates various types of external ctypedef classes, add one that overrides a python-level @Property with a cython one. This is a continuation of the test added in DOC, TST: test and document external extension type attribute aliasing #2629

  • Add decorator child and outer attributes to CFuncDefNode. I simply copied this from DefNode, is it required? - yes, seems to be required

  • Add a DecoratorTransform.visit_CFuncDefNode function based on DecoratorTransform.visit_DefNode and verify it is reached while parsing the ctypedef class.

  • Found the point at which the attribute lookup should become a property function call (I think) is in AttributeNode.analyse_attribute. But it seems the entry is wrong, I need to either

  • fix the entry in PropertyNode.analyse_declarations and somehow make sure it is used instead of the old entry

  • change the code in CFuncDefNode.analyse_declarations and remove the DecoratorTransform.visit_CFuncDefNode function

Any hints would be welcome.

Edit: added a new pass, still not working

Cython/Compiler/Nodes.py Outdated Show resolved Hide resolved
Cython/Compiler/Nodes.py Show resolved Hide resolved
Cython/Compiler/Nodes.py Show resolved Hide resolved
@mattip
Copy link
Contributor Author

mattip commented Oct 29, 2018

xref #2498 and numpy/numpy#11803

Cython/Compiler/Nodes.py Show resolved Hide resolved
Cython/Compiler/Nodes.py Show resolved Hide resolved
Cython/Compiler/Nodes.py Show resolved Hide resolved
Cython/Compiler/ParseTreeTransforms.py Outdated Show resolved Hide resolved
tests/run/ext_attr_getter.srctree Outdated Show resolved Hide resolved
@mattip mattip force-pushed the ctypedef-class-getter2 branch 2 times, most recently from 2d60950 to 83d5bdd Compare November 9, 2018 02:33
@mattip
Copy link
Contributor Author

mattip commented Nov 9, 2018

I added a ReplacePropertyNode pass and verified that the CFuncDefNode was changed to a SimpleCallNode. However it seems the pass is too late or I need to propogate the changes further.

The AddNode from "f.field0 + f.field1" has operands that are AttributeNodes, and those contain the old CFuncDefNodes. In which pass are the AttributeNodes updated or how can I trigger re-evaluating them?

@mattip
Copy link
Contributor Author

mattip commented Nov 9, 2018

It seems the "magic" is in getting from the CDefExternNode.stats to the CClassScope.entries, there the field0 is an Entry with an almost-correct but slightly incorrect type. So when the AddNode looks up its operands (in AttributeNode.analyse_attribute), the AttributeNode uses the field0 entry, and fails.

@mattip
Copy link
Contributor Author

mattip commented Nov 9, 2018

The CClassScope.cfunc_entries are first set in the AnalyseDeclarationsTransform, which was where I modified the entries in commit 726ebd8 (the first attempt in this PR). After I modify the entries in a later pass, how do I retrigger analyse_declarations for this node?

@mattip
Copy link
Contributor Author

mattip commented Nov 14, 2018

Rebased off master to get the check_size option.

I needed to add a property kwarg to add_cfunction so that the entry ended up on the property_entries not the cfunc_entries, which is determined during the AnalyseDeclarations pass. Entries in the cfunc_entries end up on the vfunctab, and in the test I create a class with no class methods so there is no vfunctab.

Other than that, I added a pass to mark the function entry that it is a cgetter function. The AttributeNode handles this and outputs the appropriate code.

@mattip
Copy link
Contributor Author

mattip commented Nov 14, 2018

btw, the no-capture option PR #2701 was essential in allowing me to debug the various passes in a strctree test

@mattip
Copy link
Contributor Author

mattip commented Nov 16, 2018

tests pass

@mattip mattip changed the title WIP ENH: allow @property decorator on external ctypedef classes ENH: allow @property decorator on external ctypedef classes Nov 16, 2018
@scoder scoder added this to the 3.0 milestone Nov 18, 2018
@mattip
Copy link
Contributor Author

mattip commented Nov 26, 2018

Is there something more I should do to move this forward?

Copy link
Contributor

@scoder scoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the (end of year) silence. There seem to be some problems with the implementation that I commented on.

property = False
if self.decorators:
for _node in self.decorators:
if _node.decorator.is_name and _node.decorator.name == 'property':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repetition of this looping code seems to scream for a helper function, e.g. one that searches for a specific decorator (and maybe applies some safety checks internally).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged with decorator analysis at the beginning of the function. Did you mean that I should refactor that into a helper function?

Cython/Compiler/ExprNodes.py Outdated Show resolved Hide resolved
Cython/Compiler/ExprNodes.py Show resolved Hide resolved
Cython/Compiler/Symtab.py Outdated Show resolved Hide resolved
tests/run/ext_attr_getter.srctree Outdated Show resolved Hide resolved
tests/run/ext_attr_getter.srctree Outdated Show resolved Hide resolved
Cython/Compiler/ParseTreeTransforms.py Outdated Show resolved Hide resolved
Cython/Compiler/Symtab.py Outdated Show resolved Hide resolved
Cython/Compiler/Symtab.py Outdated Show resolved Hide resolved
@@ -7146,6 +7146,8 @@ def calculate_result_code(self):
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whitespace missing after "%".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

elif func.name == 'staticmethod':
pass
else:
error(self.pos, "Cannot handle %s decorators yet" % func.name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we must also reject decorators that are not plain names, e.g. attributes or function calls. Anything that's not @property or @staticmethod.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, extra test added. Since I want to print the type of the unhandled instance, I needed to rename type -> type in that function.

if is_property:
self.entry.is_property = 1
env.property_entries.append(self.entry)
env.cfunc_entries.remove(self.entry)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me wonder why we shouldn't be calling declare_property() here in the first place, instead of declare_cfunction(). What do you think? It seems to do the right thing, pretty much.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CClassScope.declare_property does not dive into the wrapped function the way CClassScope.declare_cfuntion does. I played with this for a while but could not get it to work cleanly, without in the end calling declare_cfunction anyway.

Copy link
Contributor Author

@mattip mattip Jan 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, extra test added. Since I want to print the type of the unhandled instance, I needed to rename type -> type in that function.

Edit: wrong comment

@@ -752,7 +754,8 @@ def register_pyfunction(self, entry):

def declare_cfunction(self, name, type, pos,
cname=None, visibility='private', api=0, in_pxd=0,
defining=0, modifiers=(), utility_code=None, overridable=False):
defining=0, modifiers=(), utility_code=None,
overridable=False):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are now unfortunate, unnecessary changes. Could you undo the signature reformatting?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@mattip
Copy link
Contributor Author

mattip commented Jan 16, 2019

How do you easily tell which test is failing on travis? I ended up rerunning the failing build locally

Edit: ... and it passed :(

@scoder
Copy link
Contributor

scoder commented Jan 16, 2019

Looks ok now, thanks!

@mattip
Copy link
Contributor Author

mattip commented Jan 17, 2019

darn, this isn't quite good enough. It doesn't handle @property in a pxd file (like numpy.__init__.pxd, it doesn't handle cdef *int vec(self) nodes, and it doesn't handle obj.field1 == obj.field2 type nodes. I am working on fixes. but just wanted to give a heads up that the feature is only partially complete. There may be a design flaw that a decorated CFuncDefNode is different from a Property node, maybe they should be the same.

Or maybe this is beyond the scope of where you want cython to go, and the whole thing should be reverted?

@scoder
Copy link
Contributor

scoder commented Jan 18, 2019

a decorated CFuncDefNode is different from a Property node

Yeah, that sounds wrong. We should reuse the property infrastructure that we have. IIUC, the only difference is that these properties are resolved at compile time and the underlying method gets called. They are essentially "inline" properties.

@mattip
Copy link
Contributor Author

mattip commented Jan 18, 2019

Is there any assumption Property nodes are on python classes and not c classes? Or maybe that's what you mean about "inline".

@scoder
Copy link
Contributor

scoder commented Jan 18, 2019

They are always on cdef classes. Properties on Python classes are just arbitrary objects and not special syntax tree constructs.
The missing feature is that property nodes were not previously useful on external classes, since there was no code that Cython needed to generate for them. The fact that the usage of properties could be inlined into direct method calls (not only for external classes) was not really considered. But it could apply to properties marked as @cython.inline as well as any property on final classes, whether implemented in the module or externally cimported.

@scoder
Copy link
Contributor

scoder commented Jan 18, 2019

(BTW, thanks for asking the right questions and digging into the details of this feature. I think we are slowly getting an idea of where this is really going.)

@mattip mattip deleted the ctypedef-class-getter2 branch May 26, 2019 13:38
@mattip mattip restored the ctypedef-class-getter2 branch May 26, 2019 13:39
@mattip
Copy link
Contributor Author

mattip commented May 26, 2019

I want to get back to this and finish it up.

But it could apply to properties marked as @cython.inline as well as any property on final classes, whether implemented in the module or externally cimported

I am not sure what you mean by this. Looking at the implementation of @cython.inline, it seems more like a construct used in a .py file to create a "local" c-extension module with a single function than a general-purpose decorator on a cdef class or function

scoder added a commit that referenced this pull request May 4, 2020
scoder added a commit that referenced this pull request May 4, 2020
* 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants