summaryrefslogtreecommitdiff
path: root/astroid/tree/rebuilder.py
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2016-02-01 11:53:36 +0000
committerClaudiu Popa <pcmanticore@gmail.com>2016-02-13 14:08:58 +0000
commit6d2529632bae545ff7564501cac14316d5ea9204 (patch)
treecfcb11c01f1ddd4806a24469687a1ebc18bd41f4 /astroid/tree/rebuilder.py
parent18fa724c04c2393b134d57d4fe4cebe38472bad8 (diff)
downloadastroid-git-6d2529632bae545ff7564501cac14316d5ea9204.tar.gz
Changed the way how parameters are being built
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
Diffstat (limited to 'astroid/tree/rebuilder.py')
-rw-r--r--astroid/tree/rebuilder.py162
1 files changed, 106 insertions, 56 deletions
diff --git a/astroid/tree/rebuilder.py b/astroid/tree/rebuilder.py
index 90a02f73..074aea1e 100644
--- a/astroid/tree/rebuilder.py
+++ b/astroid/tree/rebuilder.py
@@ -20,6 +20,7 @@ order to get a single Astroid representation
"""
import ast
+import collections
import sys
import astroid
@@ -113,6 +114,53 @@ def _get_context(node):
return CONTEXTS.get(type(node.ctx), astroid.Load)
+class ParameterVisitor(object):
+ """A visitor which is used for building the components of Arguments node."""
+
+ def __init__(self, visitor):
+ self._visitor = visitor
+
+ def visit(self, param_node, *args):
+ cls_name = param_node.__class__.__name__
+ visit_name = 'visit_' + REDIRECT.get(cls_name, cls_name).lower()
+ visit_method = getattr(self, visit_name)
+ return visit_method(param_node, *args)
+
+ def visit_arg(self, param_node, *args):
+ name = param_node.arg
+ return self._build_parameter(param_node, name, *args)
+
+ def visit_name(self, param_node, *args):
+ name = param_node.id
+ return self._build_parameter(param_node, name, *args)
+
+ def visit_tuple(self, param_node, parent, default):
+ # We're not supporting nested arguments anymore, but in order to
+ # simply not crash when running on Python 2, we're unpacking the elements
+ # before hand. We simply don't want to support this feature anymore,
+ # so it's possible to be broken.
+ converted_node = self._visitor.visit(param_node, parent)
+ for element in converted_node.elts:
+ param = nodes.Parameter(name=element.name, lineno=param_node.lineno,
+ col_offset=param_node.col_offset,
+ parent=parent)
+ param.postinit(default=default, annotation=nodes.Empty)
+ yield param
+
+ def _build_parameter(self, param_node, name, parent, default):
+ param = nodes.Parameter(name=name, lineno=getattr(param_node, 'lineno', None),
+ col_offset=getattr(param_node, 'col_offset', None),
+ parent=parent)
+ annotation = nodes.Empty
+ param_annotation = getattr(param_node, 'annotation', None)
+ if param_annotation:
+ annotation = self._visitor.visit(param_annotation, param)
+
+ param.postinit(default=default, annotation=annotation)
+ yield param
+
+
+
class TreeRebuilder(object):
"""Rebuilds the ast tree to become an Astroid tree"""
@@ -141,57 +189,64 @@ class TreeRebuilder(object):
def visit_arguments(self, node, parent):
"""visit a Arguments node by returning a fresh instance of it"""
- vararg, kwarg = node.vararg, node.kwarg
- if PY34:
- newnode = nodes.Arguments(vararg.arg if vararg else None,
- kwarg.arg if kwarg else None,
- parent)
- else:
- newnode = nodes.Arguments(vararg, kwarg, parent)
- args = [self.visit(child, newnode) for child in node.args]
- defaults = [self.visit(child, newnode)
- for child in node.defaults]
- varargannotation = None
- kwargannotation = None
- # change added in 82732 (7c5c678e4164), vararg and kwarg
- # are instances of `ast.arg`, not strings
- if vararg:
- if PY34:
- if node.vararg.annotation:
- varargannotation = self.visit(node.vararg.annotation,
- newnode)
- vararg = vararg.arg
- elif PY3 and node.varargannotation:
- varargannotation = self.visit(node.varargannotation,
- newnode)
- if kwarg:
- if PY34:
- if node.kwarg.annotation:
- kwargannotation = self.visit(node.kwarg.annotation,
- newnode)
- kwarg = kwarg.arg
- elif PY3:
- if node.kwargannotation:
- kwargannotation = self.visit(node.kwargannotation,
- newnode)
- if PY3:
- kwonlyargs = [self.visit(child, newnode) for child
- in node.kwonlyargs]
- kw_defaults = [self.visit(child, newnode) if child else
- None for child in node.kw_defaults]
- annotations = [self.visit(arg.annotation, newnode) if
- arg.annotation else None for arg in node.args]
- kwonly_annotations = [self.visit(arg.annotation, newnode)
- if arg.annotation else None
- for arg in node.kwonlyargs]
- else:
- kwonlyargs = []
- kw_defaults = []
- annotations = []
- kwonly_annotations = []
- newnode.postinit(args, defaults, kwonlyargs, kw_defaults,
- annotations, kwonly_annotations,
- varargannotation, kwargannotation)
+ def _build_variadic(field_name):
+ param = nodes.Empty
+ variadic = getattr(node, field_name)
+
+ if variadic:
+ # Various places to get the name from.
+ try:
+ param_name = variadic.id
+ except AttributeError:
+ try:
+ param_name = variadic.arg
+ except AttributeError:
+ param_name = variadic
+
+ param = nodes.Parameter(name=param_name,
+ lineno=newnode.lineno,
+ col_offset=newnode.col_offset,
+ parent=newnode)
+ # Get the annotation of the variadic node.
+ annotation = nodes.Empty
+ default = nodes.Empty
+ variadic_annotation = getattr(variadic, 'annotation', None)
+ if variadic_annotation is None:
+ # Support for Python 3.3.
+ variadic_annotation = getattr(node, field_name + 'annotation', None)
+ if variadic_annotation:
+ annotation = self.visit(variadic_annotation, param)
+
+ param.postinit(default=default, annotation=annotation)
+ return param
+
+ def _build_args(params, defaults):
+ # Pad the list of defaults so that each arguments gets a default.
+ defaults = collections.deque(defaults)
+ while len(defaults) != len(params):
+ defaults.appendleft(nodes.Empty)
+
+ param_visitor = ParameterVisitor(self)
+ for parameter in params:
+ default = defaults.popleft()
+ if default:
+ default = self.visit(default, newnode)
+
+ for param in param_visitor.visit(parameter, newnode, default):
+ yield param
+
+ newnode = nodes.Arguments(parent=parent)
+ # Build the arguments list.
+ positional_args = list(_build_args(node.args, node.defaults))
+ kwonlyargs = list(_build_args(getattr(node, 'kwonlyargs', ()),
+ getattr(node, 'kw_defaults', ())))
+ # Build vararg and kwarg.
+ vararg = _build_variadic('vararg')
+ kwarg = _build_variadic('kwarg')
+ # Prepare the arguments new node.
+ newnode.postinit(args=positional_args, vararg=vararg, kwarg=kwarg,
+ keyword_only=kwonlyargs,
+ positional_only=[])
return newnode
def visit_assert(self, node, parent):
@@ -729,11 +784,6 @@ class TreeRebuilder(object):
class TreeRebuilder3(TreeRebuilder):
"""extend and overwrite TreeRebuilder for python3k"""
- def visit_arg(self, node, parent):
- """visit a arg node by returning a fresh AssName instance"""
- # TODO(cpopa): introduce an Arg node instead of using AssignName.
- return self.visit_assignname(node, parent, node.arg)
-
def visit_nameconstant(self, node, parent):
# in Python 3.4 we have NameConstant for True / False / None
return nodes.NameConstant(node.value, getattr(node, 'lineno', None),