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

Changed the way how parameters are being built #314

Merged
merged 1 commit into from
Feb 13, 2016
Merged

Conversation

PCManticore
Copy link
Contributor

The old way consisted in having the parameter names, their defaults and their annotations separated in different components of the Arguments node. We introduced a new Param node, which holds
the name of a parameter, its default value and its annotation. If any of the last two values are missing, then that slot will be filled with a new node kind, Empty, which is used for specifying the
lack of something (None could have been used instead, but that means having non-AST nodes in the Arguments node). We're also having support for positional only arguments, for the moment only in raw_building, but we can add later on for extension modules and other builtins. I decided to add a flag to the Param nodes which describes it as being positional only or not, because introducing another object to the Arguments node would mean to iterate over both lists (.args and .positional_only_args) when wanting to process all the parameters of a function, so this seems way easier to me to manage.

Close #215

@ceridwen Please do a review when you'll get some time.

@ceridwen
Copy link
Contributor

I would use "Parameter" as the name for the new node rather than "Param." I don't like shortening names into non-words unless it's necessary, and I don't think it is here. For instance, inspect/funcsigs calls their parameter object "Parameter."

For the implementation of Empty, I was planning to have __bool__ return False, like None is treated as False. This will save a lot of refactoring work changing boolean checks to type checks and preserve backwards compatibility for the plugins. Empty should be a singleton object: there's no reason to consume additional memory or time by creating a new object every time we need an Empty node, since all of them are identical anyways. I'd call the underlying type _Empty or _EmptyType or EmptyType (by analogy with NoneType) and then instantiate the object once in node_classes.py.

@PCManticore
Copy link
Contributor Author

Thanks, these make sense! I'll update the PR.

defaults = []
annotations = []
def _extract_args(parameters, parent):
"""Generate an iterator of params from Python parameters."""
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we're now going to have two objects named Parameter (the astroid node and the inspect/funcsigs.Parameter), I'd rewrite this to say something like, "Generates an iterator of Parameter nodes from an iterator of inspect/funcsigs.Parameter objects."

@ceridwen
Copy link
Contributor

I think that our handling of positional-only and keyword-only arguments ought to be consistent: there should be another field in Arguments nodes to hold positional-only arguments like kwonlyargs, or keyword-only arguments should be flagged in the Parameter nodes like the positional-only arguments are. Doing one with a flag in Parameters and the other with a field in Arguments is going to lead to confusion later, even though Python handles these parameters differently (positional-only parameters are only possible in C, keyword-only arguments are available in pure Python).

@@ -113,6 +114,53 @@ def _get_context(node):
return CONTEXTS.get(type(node.ctx), astroid.Load)


class ParameterVisitor(object):
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a quibble but this could probably be a subclass of ast.NodeVisitor. In the future, I'd like to reorganize the rebuilder to make it so that the main visitor contains only methods corresponding to the standard library AST's nodes, and move all other nodes to independent functions or something else here. We might also consider if using the ast.NodeVisitor is appropriate for the main visitor: it might offer better forward compatibility when the CPython developers change something in the standard library AST. On the other hand, it wouldn't work with turning the visitor methods into generators, which I proposed to prevent it from overflowing the call stack and which might also speed it up. If we're going to end up moving to another parser like 2to3 or Dropbox's altered version of the CPython parser, all this is moot, though.

The old way consisted in having the parameter names, their
defaults and their annotations separated in different components
of the Arguments node. We introduced a new Param node, which holds
the name of a parameter, its default value and its annotation.
If any of the last two values are missing, then that slot will be
filled with a new node kind, Empty, which is used for specifying the
lack of something (None could have been used instead, but that means having
non-AST nodes in the Arguments node).
We're also having support for positional only arguments, for the moment
only in raw_building.

Close #215
@PCManticore
Copy link
Contributor Author

Thanks for the review. Here's the updated version. I dropped the positional_only attribute from the Parameter node and went with separate lists of arguments, but I also included a new attribute in the Arguments node, positional_and_keyword, which holds both positional only and positional_or_keyword arguments. That's because when processing the arguments we're interested in both, while trying to separate their processing from kwonlyargs, so whenever .args.args was used, it got replaced with positional_and_keyword list. Decided not to subclass ParameterVisitor from ast.NodeVisitor, it's not a big deal for now and we're planning to change it anyway.

@@ -46,8 +47,18 @@ def _extract_expressions(node):
child = getattr(node.parent, name)
if isinstance(child, (list, tuple)):
for idx, compound_child in enumerate(child):
if compound_child is node:

# Can't find a cleaner way to do this.
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment could be expanded to clarify what this code is doing. (I'm not clear on its purpose.)

@ceridwen
Copy link
Contributor

I added a few final comments, otherwise looks good to me.

PCManticore added a commit that referenced this pull request Feb 13, 2016
Changed the way how parameters are being built
@PCManticore PCManticore merged commit f470225 into 2.0 Feb 13, 2016
@PCManticore PCManticore deleted the arguments-215 branch December 30, 2016 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants