diff options
author | Julien Jehannet <julien.jehannet@logilab.fr> | 2011-01-18 09:50:25 +0100 |
---|---|---|
committer | Julien Jehannet <julien.jehannet@logilab.fr> | 2011-01-18 09:50:25 +0100 |
commit | db7ea8451a98b437ee635e99d83944f2a9d3acc2 (patch) | |
tree | 0316a63d8dcd1244056c5bedf4f77aba9e5ed986 | |
parent | a73ee39ee8abb46d5785f890295aa128766927ed (diff) | |
parent | 3c3c34d9706ff5f9cea065b5544f18a36bb8216c (diff) | |
download | astroid-git-db7ea8451a98b437ee635e99d83944f2a9d3acc2.tar.gz |
(merge py3k changes)
-rw-r--r-- | .hgtags | 2 | ||||
-rw-r--r-- | ChangeLog | 31 | ||||
-rw-r--r-- | __init__.py | 4 | ||||
-rw-r--r-- | __pkginfo__.py | 2 | ||||
-rw-r--r-- | _nodes_ast.py | 725 | ||||
-rw-r--r-- | as_string.py | 16 | ||||
-rw-r--r-- | bases.py | 72 | ||||
-rw-r--r-- | builder.py | 256 | ||||
-rw-r--r-- | debian/changelog | 6 | ||||
-rw-r--r-- | exceptions.py (renamed from _exceptions.py) | 4 | ||||
-rw-r--r-- | inference.py | 45 | ||||
-rw-r--r-- | inspector.py | 2 | ||||
-rw-r--r-- | manager.py | 162 | ||||
-rw-r--r-- | mixins.py | 41 | ||||
-rw-r--r-- | node_classes.py | 143 | ||||
-rw-r--r-- | protocols.py | 7 | ||||
-rw-r--r-- | raw_building.py | 217 | ||||
-rw-r--r-- | rebuilder.py | 902 | ||||
-rw-r--r-- | scoped_nodes.py | 75 | ||||
-rw-r--r-- | test/data/module2.py | 1 | ||||
-rw-r--r-- | test/unittest_builder.py | 66 | ||||
-rw-r--r-- | test/unittest_inference.py | 22 | ||||
-rw-r--r-- | test/unittest_inspector.py | 4 | ||||
-rw-r--r-- | test/unittest_lookup.py | 14 | ||||
-rw-r--r-- | test/unittest_manager.py | 79 | ||||
-rw-r--r-- | test/unittest_nodes.py | 131 | ||||
-rw-r--r-- | test/unittest_regrtest.py | 9 | ||||
-rw-r--r-- | test/unittest_scoped_nodes.py | 38 | ||||
-rw-r--r-- | utils.py | 235 |
29 files changed, 1559 insertions, 1752 deletions
@@ -27,3 +27,5 @@ ba70ecabe1f5d7d87b908aaf33381d0a1c1dd11d logilab-astng-version-0.20.4 b3681c63587356f66af36774e39453b0fb129475 logilab-astng-debian-version-0.20.4-1 fd1a1cfd5d2779619787fa49100c033d700e3061 logilab-astng-version-0.21.0 8c96858bbfa68c19f0fe8939d2f53f0747bf7042 logilab-astng-debian-version-0.21.0-1 +aef5848d64cc1202755502b3d1caf96d6745a2cf logilab-astng-version-0.21.1 +71d957477e3a4840b37ead8dd2d4ec38162427e5 logilab-astng-debian-version-0.21.1-1 @@ -1,6 +1,25 @@ Change log for the astng package ================================ +2011-01-11 -- 0.21.1 + * python3: handle file encoding; fix a lot of tests + + * fix #52006: "True" and "False" can be assigned as variable in Python2x + + * fix #8847: pylint doesn't understand function attributes at all + + * fix #8774: iterator / generator / next method + + * fix bad building of ast from living object w/ container classes + (eg dict, set, list, tuple): contained elements should be turned to + ast as well (not doing it will much probably cause crash later) + + * somewhat fix #57299 and other similar issue: Exception when + trying to validate file using PyQt's PyQt4.QtCore module: we can't + do much about it but at least catch such exception to avoid crash + + + 2010-11-15 -- 0.21.0 * python3.x: first python3.x release @@ -8,8 +27,9 @@ Change log for the astng package * python2.4: drop python < 2.4 support -2010-10-27 -- 0.20.4 + +2010-10-27 -- 0.20.4 * fix #37868 #37665 #33638 #37909: import problems with absolute_import_activated * fix #8969: false positive when importing from zip-safe eggs @@ -20,14 +40,17 @@ Change log for the astng package * important progress on Py3k compatibility -2010-09-28 -- 0.20.3 - * restored python 2.3 compatibility - * fix #45959: AttributeError: 'NoneType' object has no attribute 'frame', due +2010-09-28 -- 0.20.3 + * restored python 2.3 compatibility + + * fix #45959: AttributeError: 'NoneType' object has no attribute 'frame', due to handling of __class__ when importing from living object (because of missing source code or C-compiled object) + + 2010-09-10 -- 0.20.2 * fix astng building bug: we've to set module.package flag at the node creation time otherwise we'll miss this information when infering relative diff --git a/__init__.py b/__init__.py index 840c53c6..e3cd8a08 100644 --- a/__init__.py +++ b/__init__.py @@ -60,7 +60,7 @@ __doctype__ = "restructuredtext en" # WARNING: internal imports order matters ! # make all exception classes accessible from astng package -from logilab.astng._exceptions import * +from logilab.astng.exceptions import * # make all node classes accessible from astng package from logilab.astng.nodes import * @@ -76,7 +76,7 @@ from logilab.astng.scoped_nodes import builtin_lookup # make a manager instance (borg) as well as Project and Package classes # accessible from astng package -from logilab.astng.manager import ASTNGManager, Project, Package +from logilab.astng.manager import ASTNGManager, Project MANAGER = ASTNGManager() del ASTNGManager diff --git a/__pkginfo__.py b/__pkginfo__.py index 12d0d9dc..f29d646a 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -24,7 +24,7 @@ distname = 'logilab-astng' modname = 'astng' subpackage_of = 'logilab' -numversion = (0, 21, 0) +numversion = (0, 21, 1) version = '.'.join([str(num) for num in numversion]) install_requires = ['logilab-common >= 0.53.0'] diff --git a/_nodes_ast.py b/_nodes_ast.py deleted file mode 100644 index 3f48f0a3..00000000 --- a/_nodes_ast.py +++ /dev/null @@ -1,725 +0,0 @@ -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com -# -# This file is part of logilab-astng. -# -# logilab-astng is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by the -# Free Software Foundation, either version 2.1 of the License, or (at your -# option) any later version. -# -# logilab-astng is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License -# for more details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see <http://www.gnu.org/licenses/>. -"""python 2.5 builtin _ast compatibility module - -""" - -__docformat__ = "restructuredtext en" - - -# aliased nodes -from _ast import AST as Node, Expr as Discard -# nodes which are not part of astng -from _ast import ( - # binary operators - Add as _Add, Div as _Div, FloorDiv as _FloorDiv, - Mod as _Mod, Mult as _Mult, Pow as _Pow, Sub as _Sub, - BitAnd as _BitAnd, BitOr as _BitOr, BitXor as _BitXor, - LShift as _LShift, RShift as _RShift, - # logical operators - And as _And, Or as _Or, - # unary operators - UAdd as _UAdd, USub as _USub, Not as _Not, Invert as _Invert, - # comparison operators - Eq as _Eq, Gt as _Gt, GtE as _GtE, In as _In, Is as _Is, - IsNot as _IsNot, Lt as _Lt, LtE as _LtE, NotEq as _NotEq, - NotIn as _NotIn, - # other nodes which are not part of astng - Num as _Num, Str as _Str, Load as _Load, Store as _Store, Del as _Del, - ) - -from logilab.astng import nodes as new -import sys - -_BIN_OP_CLASSES = {_Add: '+', - _BitAnd: '&', - _BitOr: '|', - _BitXor: '^', - _Div: '/', - _FloorDiv: '//', - _Mod: '%', - _Mult: '*', - _Pow: '**', - _Sub: '-', - _LShift: '<<', - _RShift: '>>'} - -_BOOL_OP_CLASSES = {_And: 'and', - _Or: 'or'} - -_UNARY_OP_CLASSES = {_UAdd: '+', - _USub: '-', - _Not: 'not', - _Invert: '~'} - -_CMP_OP_CLASSES = {_Eq: '==', - _Gt: '>', - _GtE: '>=', - _In: 'in', - _Is: 'is', - _IsNot: 'is not', - _Lt: '<', - _LtE: '<=', - _NotEq: '!=', - _NotIn: 'not in'} - - -CONST_NAME_TRANSFORMS = {'None': None, - 'True': True, - 'False': False} - - -def _init_set_doc(node, newnode): - newnode.doc = None - try: - if isinstance(node.body[0], Discard) and isinstance(node.body[0].value, _Str): - newnode.tolineno = node.body[0].lineno - newnode.doc = node.body[0].value.s - node.body = node.body[1:] - - except IndexError: - pass # ast built from scratch - - -from logilab.astng.rebuilder import RebuildVisitor -# _ast rebuilder ############################################################## - -def _lineno_parent(oldnode, newnode, parent): - newnode.parent = parent - if hasattr(oldnode, 'lineno'): - newnode.lineno = oldnode.lineno - -class TreeRebuilder(RebuildVisitor): - """Rebuilds the _ast tree to become an ASTNG tree""" - - def _set_infos(self, oldnode, newnode, parent): - newnode.parent = parent - if hasattr(oldnode, 'lineno'): - newnode.lineno = oldnode.lineno - last = newnode.last_child() - newnode.set_line_info(last) # set_line_info accepts None - - def visit_arguments(self, node, parent): - """visit a Arguments node by returning a fresh instance of it""" - newnode = new.Arguments() - _lineno_parent(node, newnode, parent) - self.asscontext = "Ass" - newnode.args = [self.visit(child, newnode) for child in node.args] - self.asscontext = None - newnode.defaults = [self.visit(child, newnode) for child in node.defaults] - newnode.vararg = node.vararg - newnode.kwarg = node.kwarg - self._save_argument_name(newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_assattr(self, node, parent): - """visit a AssAttr node by returning a fresh instance of it""" - assc, self.asscontext = self.asscontext, None - newnode = new.AssAttr() - _lineno_parent(node, newnode, parent) - newnode.expr = self.visit(node.expr, newnode) - self.asscontext = assc - self._delayed_assattr.append(newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_assert(self, node, parent): - """visit a Assert node by returning a fresh instance of it""" - newnode = new.Assert() - _lineno_parent(node, newnode, parent) - newnode.test = self.visit(node.test, newnode) - newnode.fail = self.visit(node.msg, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_assign(self, node, parent): - """visit a Assign node by returning a fresh instance of it""" - newnode = new.Assign() - _lineno_parent(node, newnode, parent) - self.asscontext = "Ass" - newnode.targets = [self.visit(child, newnode) for child in node.targets] - self.asscontext = None - newnode.value = self.visit(node.value, newnode) - self._set_assign_infos(newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_assname(self, node, parent, node_name=None): - '''visit a node and return a AssName node''' - newnode = new.AssName() - self._set_infos(node, newnode, parent) - newnode.name = node_name - self._save_assignment(newnode) - return newnode - - def visit_augassign(self, node, parent): - """visit a AugAssign node by returning a fresh instance of it""" - newnode = new.AugAssign() - _lineno_parent(node, newnode, parent) - newnode.op = _BIN_OP_CLASSES[node.op.__class__] + "=" - self.asscontext = "Ass" - newnode.target = self.visit(node.target, newnode) - self.asscontext = None - newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_backquote(self, node, parent): - """visit a Backquote node by returning a fresh instance of it""" - newnode = new.Backquote() - _lineno_parent(node, newnode, parent) - newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_binop(self, node, parent): - """visit a BinOp node by returning a fresh instance of it""" - newnode = new.BinOp() - _lineno_parent(node, newnode, parent) - newnode.left = self.visit(node.left, newnode) - newnode.right = self.visit(node.right, newnode) - newnode.op = _BIN_OP_CLASSES[node.op.__class__] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_boolop(self, node, parent): - """visit a BoolOp node by returning a fresh instance of it""" - newnode = new.BoolOp() - _lineno_parent(node, newnode, parent) - newnode.values = [self.visit(child, newnode) for child in node.values] - newnode.op = _BOOL_OP_CLASSES[node.op.__class__] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_callfunc(self, node, parent): - """visit a CallFunc node by returning a fresh instance of it""" - newnode = new.CallFunc() - _lineno_parent(node, newnode, parent) - newnode.func = self.visit(node.func, newnode) - newnode.args = [self.visit(child, newnode) for child in node.args] - newnode.starargs = self.visit(node.starargs, newnode) - newnode.kwargs = self.visit(node.kwargs, newnode) - newnode.args.extend(self.visit(child, newnode) for child in node.keywords) - newnode.set_line_info(newnode.last_child()) - return newnode - - def _visit_class(self, node, parent): - """visit a Class node by returning a fresh instance of it""" - newnode = new.Class(node.name, None) - _lineno_parent(node, newnode, parent) - _init_set_doc(node, newnode) - newnode.bases = [self.visit(child, newnode) for child in node.bases] - newnode.body = [self.visit(child, newnode) for child in node.body] - if 'decorator_list' in node._fields and node.decorator_list:# py >= 2.6 - newnode.decorators = self.visit_decorators(node, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_compare(self, node, parent): - """visit a Compare node by returning a fresh instance of it""" - newnode = new.Compare() - _lineno_parent(node, newnode, parent) - newnode.left = self.visit(node.left, newnode) - newnode.ops = [(_CMP_OP_CLASSES[op.__class__], self.visit(expr, newnode)) - for (op, expr) in zip(node.ops, node.comparators)] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_comprehension(self, node, parent): - """visit a Comprehension node by returning a fresh instance of it""" - newnode = new.Comprehension() - _lineno_parent(node, newnode, parent) - self.asscontext = "Ass" - newnode.target = self.visit(node.target, newnode) - self.asscontext = None - newnode.iter = self.visit(node.iter, newnode) - newnode.ifs = [self.visit(child, newnode) for child in node.ifs] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_decorators(self, node, parent): - """visit a Decorators node by returning a fresh instance of it""" - # /!\ node is actually a _ast.Function node while - # parent is a astng.nodes.Function node - newnode = new.Decorators() - _lineno_parent(node, newnode, parent) - if 'decorators' in node._fields: # py < 2.6, i.e. 2.5 - decorators = node.decorators - else: - decorators= node.decorator_list - newnode.nodes = [self.visit(child, newnode) for child in decorators] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_delete(self, node, parent): - """visit a Delete node by returning a fresh instance of it""" - newnode = new.Delete() - _lineno_parent(node, newnode, parent) - self.asscontext = "Del" - newnode.targets = [self.visit(child, newnode) for child in node.targets] - self.asscontext = None - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_dict(self, node, parent): - """visit a Dict node by returning a fresh instance of it""" - newnode = new.Dict() - _lineno_parent(node, newnode, parent) - newnode.items = [(self.visit(key, newnode), self.visit(value, newnode)) - for key, value in zip(node.keys, node.values)] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_dictcomp(self, node, parent): - """visit a DictComp node by returning a fresh instance of it""" - newnode = new.DictComp() - _lineno_parent(node, newnode, parent) - newnode.key = self.visit(node.key, newnode) - newnode.value = self.visit(node.value, newnode) - newnode.generators = [self.visit(child, newnode) - for child in node.generators] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_discard(self, node, parent): - """visit a Discard node by returning a fresh instance of it""" - newnode = new.Discard() - _lineno_parent(node, newnode, parent) - newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_excepthandler(self, node, parent): - """visit an ExceptHandler node by returning a fresh instance of it""" - newnode = new.ExceptHandler() - _lineno_parent(node, newnode, parent) - newnode.type = self.visit(node.type, newnode) - if node.name is not None: - # /!\ node.name can be a tuple - self.asscontext = "Ass" - newnode.name = self.visit(node.name, newnode) - self.asscontext = None - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_exec(self, node, parent): - """visit an Exec node by returning a fresh instance of it""" - newnode = new.Exec() - _lineno_parent(node, newnode, parent) - newnode.expr = self.visit(node.body, newnode) - newnode.globals = self.visit(node.globals, newnode) - newnode.locals = self.visit(node.locals, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_extslice(self, node, parent): - """visit an ExtSlice node by returning a fresh instance of it""" - newnode = new.ExtSlice() - _lineno_parent(node, newnode, parent) - newnode.dims = [self.visit(dim, newnode) for dim in node.dims] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_for(self, node, parent): - """visit a For node by returning a fresh instance of it""" - newnode = new.For() - _lineno_parent(node, newnode, parent) - self.asscontext = "Ass" - newnode.target = self.visit(node.target, newnode) - self.asscontext = None - newnode.iter = self.visit(node.iter, newnode) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.orelse = [self.visit(child, newnode) for child in node.orelse] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_from(self, node, parent): - """visit a From node by returning a fresh instance of it""" - names = [(alias.name, alias.asname) for alias in node.names] - newnode = new.From(node.module or '', names, node.level) - self._set_infos(node, newnode, parent) - self._store_from_node(newnode) - - return newnode - - def _visit_function(self, node, parent): - """visit a Function node by returning a fresh instance of it""" - newnode = new.Function(node.name, None) - _lineno_parent(node, newnode, parent) - _init_set_doc(node, newnode) - newnode.args = self.visit(node.args, newnode) - newnode.body = [self.visit(child, newnode) for child in node.body] - if 'decorators' in node._fields: # py < 2.6 - attr = 'decorators' - else: - attr = 'decorator_list' - decorators = getattr(node, attr) - if decorators: - newnode.decorators = self.visit_decorators(node, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_genexpr(self, node, parent): - """visit a GenExpr node by returning a fresh instance of it""" - newnode = new.GenExpr() - _lineno_parent(node, newnode, parent) - newnode.elt = self.visit(node.elt, newnode) - newnode.generators = [self.visit(child, newnode) for child in node.generators] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_getattr(self, node, parent): - """visit a Getattr node by returning a fresh instance of it""" - if self.asscontext == "Del": - # FIXME : maybe we should reintroduce and visit_delattr ? - # for instance, deactivating asscontext - newnode = new.DelAttr() - elif self.asscontext == "Ass": - # FIXME : maybe we should call visit_assattr ? - newnode = new.AssAttr() - self._delayed_assattr.append(newnode) - else: - newnode = new.Getattr() - _lineno_parent(node, newnode, parent) - asscontext, self.asscontext = self.asscontext, None - newnode.expr = self.visit(node.value, newnode) - self.asscontext = asscontext - newnode.attrname = node.attr - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_if(self, node, parent): - """visit a If node by returning a fresh instance of it""" - newnode = new.If() - _lineno_parent(node, newnode, parent) - newnode.test = self.visit(node.test, newnode) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.orelse = [self.visit(child, newnode) for child in node.orelse] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_ifexp(self, node, parent): - """visit a IfExp node by returning a fresh instance of it""" - newnode = new.IfExp() - _lineno_parent(node, newnode, parent) - newnode.test = self.visit(node.test, newnode) - newnode.body = self.visit(node.body, newnode) - newnode.orelse = self.visit(node.orelse, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_import(self, node, parent): - """visit a Import node by returning a fresh instance of it""" - newnode = new.Import() - self._set_infos(node, newnode, parent) - newnode.names = [(alias.name, alias.asname) for alias in node.names] - self._save_import_locals(newnode) - return newnode - - def visit_index(self, node, parent): - """visit a Index node by returning a fresh instance of it""" - newnode = new.Index() - _lineno_parent(node, newnode, parent) - newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_keyword(self, node, parent): - """visit a Keyword node by returning a fresh instance of it""" - newnode = new.Keyword() - _lineno_parent(node, newnode, parent) - newnode.arg = node.arg - newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_lambda(self, node, parent): - """visit a Lambda node by returning a fresh instance of it""" - newnode = new.Lambda() - _lineno_parent(node, newnode, parent) - newnode.args = self.visit(node.args, newnode) - newnode.body = self.visit(node.body, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_list(self, node, parent): - """visit a List node by returning a fresh instance of it""" - newnode = new.List() - _lineno_parent(node, newnode, parent) - newnode.elts = [self.visit(child, newnode) for child in node.elts] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_listcomp(self, node, parent): - """visit a ListComp node by returning a fresh instance of it""" - newnode = new.ListComp() - _lineno_parent(node, newnode, parent) - newnode.elt = self.visit(node.elt, newnode) - newnode.generators = [self.visit(child, newnode) - for child in node.generators] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_module(self, node, modname, package): - """visit a Module node by returning a fresh instance of it""" - newnode = new.Module(modname, None) - newnode.package = package - _lineno_parent(node, newnode, parent=None) - _init_set_doc(node, newnode) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_name(self, node, parent): - """visit a Name node by returning a fresh instance of it""" - if node.id in CONST_NAME_TRANSFORMS: - newnode = new.Const(CONST_NAME_TRANSFORMS[node.id]) - self._set_infos(node, newnode, parent) - return newnode - if self.asscontext == "Del": - newnode = new.DelName() - elif self.asscontext is not None: # Ass - assert self.asscontext == "Ass" - newnode = new.AssName() - else: - newnode = new.Name() - _lineno_parent(node, newnode, parent) - newnode.name = node.id - # XXX REMOVE me : - if self.asscontext in ('Del', 'Ass'): # 'Aug' ?? - self._save_assignment(newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_bytes(self, node, parent): - """visit a Bytes node by returning a fresh instance of Const""" - newnode = new.Const(node.s) - self._set_infos(node, newnode, parent) - return newnode - - def visit_num(self, node, parent): - """visit a Num node by returning a fresh instance of Const""" - newnode = new.Const(node.n) - self._set_infos(node, newnode, parent) - return newnode - - def visit_str(self, node, parent): - """visit a Str node by returning a fresh instance of Const""" - newnode = new.Const(node.s) - self._set_infos(node, newnode, parent) - return newnode - - def visit_print(self, node, parent): - """visit a Print node by returning a fresh instance of it""" - newnode = new.Print() - _lineno_parent(node, newnode, parent) - newnode.nl = node.nl - newnode.dest = self.visit(node.dest, newnode) - newnode.values = [self.visit(child, newnode) for child in node.values] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_raise(self, node, parent): - """visit a Raise node by returning a fresh instance of it""" - newnode = new.Raise() - _lineno_parent(node, newnode, parent) - newnode.exc = self.visit(node.type, newnode) - newnode.inst = self.visit(node.inst, newnode) - newnode.tback = self.visit(node.tback, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_return(self, node, parent): - """visit a Return node by returning a fresh instance of it""" - newnode = new.Return() - _lineno_parent(node, newnode, parent) - newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_set(self, node, parent): - """visit a Tuple node by returning a fresh instance of it""" - newnode = new.Set() - _lineno_parent(node, newnode, parent) - newnode.elts = [self.visit(child, newnode) for child in node.elts] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_setcomp(self, node, parent): - """visit a SetComp node by returning a fresh instance of it""" - newnode = new.SetComp() - _lineno_parent(node, newnode, parent) - newnode.elt = self.visit(node.elt, newnode) - newnode.generators = [self.visit(child, newnode) - for child in node.generators] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_slice(self, node, parent): - """visit a Slice node by returning a fresh instance of it""" - newnode = new.Slice() - _lineno_parent(node, newnode, parent) - newnode.lower = self.visit(node.lower, newnode) - newnode.upper = self.visit(node.upper, newnode) - newnode.step = self.visit(node.step, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_subscript(self, node, parent): - """visit a Subscript node by returning a fresh instance of it""" - newnode = new.Subscript() - _lineno_parent(node, newnode, parent) - subcontext, self.asscontext = self.asscontext, None - newnode.value = self.visit(node.value, newnode) - newnode.slice = self.visit(node.slice, newnode) - self.asscontext = subcontext - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_tryexcept(self, node, parent): - """visit a TryExcept node by returning a fresh instance of it""" - newnode = new.TryExcept() - _lineno_parent(node, newnode, parent) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.handlers = [self.visit(child, newnode) for child in node.handlers] - newnode.orelse = [self.visit(child, newnode) for child in node.orelse] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_tryfinally(self, node, parent): - """visit a TryFinally node by returning a fresh instance of it""" - newnode = new.TryFinally() - _lineno_parent(node, newnode, parent) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.finalbody = [self.visit(n, newnode) for n in node.finalbody] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_tuple(self, node, parent): - """visit a Tuple node by returning a fresh instance of it""" - newnode = new.Tuple() - _lineno_parent(node, newnode, parent) - newnode.elts = [self.visit(child, newnode) for child in node.elts] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_unaryop(self, node, parent): - """visit a UnaryOp node by returning a fresh instance of it""" - newnode = new.UnaryOp() - _lineno_parent(node, newnode, parent) - newnode.operand = self.visit(node.operand, newnode) - newnode.op = _UNARY_OP_CLASSES[node.op.__class__] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_while(self, node, parent): - """visit a While node by returning a fresh instance of it""" - newnode = new.While() - _lineno_parent(node, newnode, parent) - newnode.test = self.visit(node.test, newnode) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.orelse = [self.visit(child, newnode) for child in node.orelse] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_with(self, node, parent): - """visit a With node by returning a fresh instance of it""" - newnode = new.With() - _lineno_parent(node, newnode, parent) - newnode.expr = self.visit(node.context_expr, newnode) - self.asscontext = "Ass" - newnode.vars = self.visit(node.optional_vars, newnode) - self.asscontext = None - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_yield(self, node, parent): - """visit a Yield node by returning a fresh instance of it""" - newnode = new.Yield() - _lineno_parent(node, newnode, parent) - newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - -class TreeRebuilder3k(TreeRebuilder): - """extend and overwrite TreeRebuilder for python3k""" - - def visit_arg(self, node, parent): - """visit a arg node by returning a fresh AssName instance""" - # the <arg> node is coming from py>=3.0, but we use AssName in py2.x - # XXX or we should instead introduce a Arg node in astng ? - return self.visit_assname(node, parent, node.arg) - - def visit_excepthandler(self, node, parent): - """visit an ExceptHandler node by returning a fresh instance of it""" - newnode = new.ExceptHandler() - _lineno_parent(node, newnode, parent) - newnode.type = self.visit(node.type, newnode) - if node.name is not None: - newnode.name = self.visit_assname(node, newnode, node.name) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_nonlocal(self, node, parent): - """visit a Nonlocal node and return a new instance of it""" - newnode = new.Nonlocal(node.names) - self._set_infos(node, newnode, parent) - return newnode - - def visit_raise(self, node, parent): - """visit a Raise node by returning a fresh instance of it""" - newnode = new.Raise() - _lineno_parent(node, newnode, parent) - # no traceback; anyway it is not used in Pylint - newnode.exc = self.visit(node.exc, newnode) - newnode.cause = self.visit(node.cause, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - def visit_starred(self, node, parent): - """visit a Starred node and return a new instance of it""" - newnode = new.Starred() - _lineno_parent(node, newnode, parent) - newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) - return newnode - - -if sys.version_info >= (3, 0): - TreeRebuilder = TreeRebuilder3k - - diff --git a/as_string.py b/as_string.py index d9b53dfe..3eeda096 100644 --- a/as_string.py +++ b/as_string.py @@ -34,7 +34,6 @@ It will probably not work on compiler.ast or _ast trees. """ import sys -from logilab.astng.utils import ASTVisitor INDENT = ' ' # 4 spaces ; keep indentation variable @@ -50,7 +49,7 @@ def _import_string(names): return ', '.join(_names) -class AsStringVisitor(ASTVisitor): +class AsStringVisitor(object): """Visitor to render an ASTNG node as string """ def __call__(self, node): @@ -179,7 +178,11 @@ class AsStringVisitor(ASTVisitor): def visit_discard(self, node): """return an astng.Discard node as string""" return node.value.accept(self) - + + def visit_emptynode(self, node): + """dummy method for visiting an Empty node""" + return '' + def visit_excepthandler(self, node): if node.type: if node.name: @@ -225,8 +228,8 @@ class AsStringVisitor(ASTVisitor): def visit_from(self, node): """return an astng.From node as string""" - # XXX level - return 'from %s import %s' % (node.modname, _import_string(node.names)) + return 'from %s import %s' % ('.' * (node.level or 0) + node.modname, + _import_string(node.names)) def visit_function(self, node): """return an astng.Function node as string""" @@ -392,7 +395,8 @@ class AsStringVisitor(ASTVisitor): def visit_yield(self, node): """yield an ast.Yield node as string""" - return 'yield %s' % node.value.accept(self) + yi_val = node.value and (" " + node.value.accept(self)) or "" + return 'yield' + yi_val class AsStringVisitor3k(AsStringVisitor): @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # copyright 2003-2010 Sylvain Thenault, all rights reserved. @@ -30,34 +18,21 @@ # # You should have received a copy of the GNU Lesser General Public License along # with logilab-astng. If not, see <http://www.gnu.org/licenses/>. - """This module contains base classes and functions for the nodes and some inference utils. - """ -from __future__ import generators - __docformat__ = "restructuredtext en" -try: - from _ast import AST - del AST - class BaseClass(object): - pass -except ImportError: - class BaseClass: - pass - from logilab.common.compat import builtins -from logilab.astng._exceptions import InferenceError, ASTNGError, \ +from logilab.astng.exceptions import InferenceError, ASTNGError, \ NotFoundError, UnresolvableName from logilab.astng.as_string import as_string BUILTINS_NAME = builtins.__name__ -class Proxy(BaseClass): +class Proxy(object): """a simple proxy object""" _proxied = None @@ -75,9 +50,8 @@ class Proxy(BaseClass): def infer(self, context=None): yield self -# Inference ################################################################## - +# Inference ################################################################## class InferenceContext(object): __slots__ = ('path', 'lookupname', 'callcontext', 'boundnode') @@ -203,7 +177,7 @@ class Instance(Proxy): """wrap bound methods of attrs in a InstanceMethod proxies""" for attr in attrs: if isinstance(attr, UnboundMethod): - if '__builtin__.property' in attr.decoratornames(): + if BUILTINS_NAME + '.property' in attr.decoratornames(): for infered in attr.infer_call_result(self, context): yield infered else: @@ -264,6 +238,14 @@ class UnboundMethod(Proxy): return iter((self._proxied,)) return super(UnboundMethod, self).igetattr(name, context) + def infer_call_result(self, caller, context): + # If we're unbound method __new__ of builtin object, the result is an + # instance of the class given as first argument. + if (self._proxied.name == '__new__' and + self._proxied.parent.frame().qname() == '__builtin__.object'): + return (x is YES and x or Instance(x) for x in caller.args[0].infer()) + return self._proxied.infer_call_result(caller, context) + class BoundMethod(UnboundMethod): """a special node representing a method bound to an instance""" @@ -280,7 +262,7 @@ class BoundMethod(UnboundMethod): return self._proxied.infer_call_result(caller, context) -class Generator(Proxy): +class Generator(Instance): """a special node representing a generator""" def callable(self): return True @@ -291,6 +273,12 @@ class Generator(Proxy): def display_type(self): return 'Generator' + def __repr__(self): + return '<Generator(%s) l.%s at 0x%s>' % (self._proxied.name, self.lineno, id(self)) + + def __str__(self): + return 'Generator(%s)' % (self._proxied.name) + # decorators ################################################################## @@ -336,7 +324,7 @@ def raise_if_nothing_infered(func): # Node ###################################################################### -class NodeNG(BaseClass): +class NodeNG(object): """Base Class for all ASTNG node classes. It represents a node of the new abstract syntax tree. @@ -574,6 +562,26 @@ class NodeNG(BaseClass): return "\n".join(result) +class Statement(NodeNG): + """Statement node adding a few attributes""" + is_statement = True + + def next_sibling(self): + """return the next sibling statement""" + stmts = self.parent.child_sequence(self) + index = stmts.index(self) + try: + return stmts[index +1] + except IndexError: + pass + + def previous_sibling(self): + """return the previous sibling statement""" + stmts = self.parent.child_sequence(self) + index = stmts.index(self) + if index >= 1: + return stmts[index -1] + INDENT = " " def _repr_tree(node, result, indent='', _done=None, ids=False): @@ -27,45 +27,75 @@ at the same time. __docformat__ = "restructuredtext en" -import sys +import sys, re from os.path import splitext, basename, dirname, exists, abspath -from inspect import isfunction, ismethod, ismethoddescriptor, isclass, \ - isbuiltin -from inspect import isdatadescriptor from logilab.common.modutils import modpath_from_file -from logilab.astng._exceptions import ASTNGBuildingException -from logilab.astng.raw_building import build_module, object_build_class, \ - object_build_function, object_build_datadescriptor, attach_dummy_node, \ - object_build_methoddescriptor, attach_const_node, attach_import_node +from logilab.astng.exceptions import ASTNGBuildingException, InferenceError +from logilab.astng.raw_building import InspectBuilder +from logilab.astng.rebuilder import TreeRebuilder from logilab.astng.manager import ASTNGManager +from logilab.astng.bases import YES, Instance from _ast import PyCF_ONLY_AST def parse(string): return compile(string, "<string>", 'exec', PyCF_ONLY_AST) -from logilab.astng._nodes_ast import TreeRebuilder + +if sys.version_info >= (3, 0): + from tokenize import detect_encoding + + def open_source_file(filename): + byte_stream = open(filename, 'bU') + encoding = detect_encoding(byte_stream.readline)[0] + stream = open(filename, 'U', encoding=encoding) + try: + data = stream.read() + except UnicodeError, uex: # wrong encodingg + # detect_encoding returns utf-8 if no encoding specified + msg = 'Wrong (%s) or no encoding specified' % encoding + raise ASTNGBuildingException(msg) + return stream, encoding, data + +else: + import re + + _ENCODING_RGX = re.compile("[^#]*#*.*coding[:=]\s*([^\s]+)") + + def _guess_encoding(string): + """get encoding from a python file as string or return None if not found + """ + # check for UTF-8 byte-order mark + if string.startswith('\xef\xbb\xbf'): + return 'UTF-8' + for line in string.split('\n', 2)[:2]: + # check for encoding declaration + match = _ENCODING_RGX.match(line) + if match is not None: + return match.group(1) + + def open_source_file(filename): + """get data for parsing a file""" + stream = open(filename, 'U') + data = stream.read() + encoding = _guess_encoding(data) + return stream, encoding, data # ast NG builder ############################################################## -class ASTNGBuilder: - """provide astng building methods - """ +MANAGER = ASTNGManager() + +class ASTNGBuilder(InspectBuilder): + """provide astng building methods""" + rebuilder = TreeRebuilder() def __init__(self, manager=None): - if manager is None: - manager = ASTNGManager() - self._manager = manager - self._module = None - self._done = None - self.rebuilder = TreeRebuilder(manager) - self._dyn_modname_map = {'gtk': 'gtk._gtk'} + self._manager = manager or MANAGER def module_build(self, module, modname=None): """build an astng from a living module instance """ node = None - self._module = module path = getattr(module, '__file__', None) if path is not None: path_, ext = splitext(module.__file__) @@ -77,33 +107,20 @@ class ASTNGBuilder: node = self.inspect_build(module, modname=modname, path=path) return node - def inspect_build(self, module, modname=None, path=None): - """build astng from a living module (i.e. using inspect) - this is used when there is no python source code available (either - because it's a built-in module or because the .py is not available) - """ - self._module = module - if modname is None: - modname = module.__name__ - node = build_module(modname, module.__doc__) - node.file = node.path = path and abspath(path) or path - if self._manager is not None: - self._manager._cache[modname] = node - node.package = hasattr(module, '__path__') - self._done = {} - self.object_build(node, module) - return node - def file_build(self, path, modname=None): """build astng from a source code file (i.e. from an ast) path is expected to be a python source file """ try: - data = open(path, 'U').read() - except IOError, ex: - msg = 'Unable to load file %r (%s)' % (path, ex) + stream, encoding, data = open_source_file(path) + except IOError, exc: + msg = 'Unable to load file %r (%s)' % (path, exc) raise ASTNGBuildingException(msg) + except SyntaxError, exc: # py3k encoding specification error + raise ASTNGBuildingException(exc) + except LookupError, exc: # unknown encoding + raise ASTNGBuildingException(exc) # get module name if necessary, *before modifying sys.path* if modname is None: try: @@ -112,19 +129,31 @@ class ASTNGBuilder: modname = splitext(basename(path))[0] # build astng representation try: - sys.path.insert(0, dirname(path)) + sys.path.insert(0, dirname(path)) # XXX (syt) iirk node = self.string_build(data, modname, path) finally: sys.path.pop(0) - + node.file_encoding = encoding + node.file_stream = stream return node def string_build(self, data, modname='', path=None): - """build astng from a source code stream (i.e. from an ast)""" - return self.ast_build(parse(data + '\n'), modname, path) - - def ast_build(self, node, modname='', path=None): - """build the astng from AST, return the new tree""" + """build astng from source code string and return rebuilded astng""" + module = self._data_build(data, modname, path) + if self._manager is not None: + self._manager.astng_cache[module.name] = module + # post tree building steps after we stored the module in the cache: + for from_node in module._from_nodes: + self.add_from_names_to_locals(from_node) + # handle delayed assattr nodes + for delayed in module._delayed_assattr: + self.delayed_assattr(delayed) + return module + + def _data_build(self, data, modname, path): + """build tree node from data and add some informations""" + # this method could be wrapped with a pickle/cache function + node = parse(data + '\n') if path is not None: node_file = abspath(path) else: @@ -134,86 +163,69 @@ class ASTNGBuilder: package = True else: package = path and path.find('__init__.py') > -1 or False - newnode = self.rebuilder.build(node, modname, node_file, package) - return newnode + self.rebuilder.init() + module = self.rebuilder.visit_module(node, modname, package) + module.file = module.path = node_file + module._from_nodes = self.rebuilder._from_nodes + module._delayed_assattr = self.rebuilder._delayed_assattr + return module + + def add_from_names_to_locals(self, node): + """store imported names to the locals; + resort the locals if coming from a delayed node + """ - # astng from living objects ############################################### - # - # this is actually a really minimal representation, including only Module, - # Function and Class nodes and some others as guessed + _key_func = lambda node: node.fromlineno + def sort_locals(my_list): + my_list.sort(key=_key_func) + for (name, asname) in node.names: + if name == '*': + try: + imported = node.root().import_module(node.modname) + except ASTNGBuildingException: + continue + for name in imported.wildcard_import_names(): + node.parent.set_local(name, node) + sort_locals(node.parent.scope().locals[name]) + else: + node.parent.set_local(asname or name, node) + sort_locals(node.parent.scope().locals[asname or name]) - def object_build(self, node, obj): - """recursive method which create a partial ast from real objects - (only function, class, and method are handled) + def delayed_assattr(self, node): + """visit a AssAttr node -> add name to locals, handle members + definition """ - if obj in self._done: - return self._done[obj] - self._done[obj] = node - for name in dir(obj): - try: - member = getattr(obj, name) - except AttributeError: - # damned ExtensionClass.Base, I know you're there ! - attach_dummy_node(node, name) - continue - if ismethod(member): - member = member.im_func - if isfunction(member): - # verify this is not an imported function - if member.func_code.co_filename != getattr(self._module, '__file__', None): - attach_dummy_node(node, name, member) + try: + frame = node.frame() + for infered in node.expr.infer(): + if infered is YES: continue - object_build_function(node, member, name) - elif isbuiltin(member): - # verify this is not an imported member - if self._member_module(member) != self._module.__name__: - imported_member(node, member, name) + try: + if infered.__class__ is Instance: + infered = infered._proxied + iattrs = infered.instance_attrs + elif isinstance(infered, Instance): + # Const, Tuple, ... we may be wrong, may be not, but + # anyway we don't want to pollute builtin's namespace + continue + elif infered.is_function: + iattrs = infered.instance_attrs + else: + iattrs = infered.locals + except AttributeError: + # XXX log error + #import traceback + #traceback.print_exc() continue - object_build_methoddescriptor(node, member, name) - elif isclass(member): - # verify this is not an imported class - if self._member_module(member) != self._module.__name__: - imported_member(node, member, name) + values = iattrs.setdefault(node.attrname, []) + if node in values: continue - if member in self._done: - class_node = self._done[member] - if not class_node in node.locals.get(name, ()): - node.add_local_node(class_node, name) + # get assign in __init__ first XXX useful ? + if frame.name == '__init__' and values and not \ + values[0].frame().name == '__init__': + values.insert(0, node) else: - class_node = object_build_class(node, member, name) - # recursion - self.object_build(class_node, member) - if name == '__class__' and class_node.parent is None: - class_node.parent = self._done[self._module] - elif ismethoddescriptor(member): - assert isinstance(member, object) - object_build_methoddescriptor(node, member, name) - elif isdatadescriptor(member): - assert isinstance(member, object) - object_build_datadescriptor(node, member, name) - elif isinstance(member, (int, long, float, str, unicode)) or member is None: - attach_const_node(node, name, member) - else: - # create an empty node so that the name is actually defined - attach_dummy_node(node, name, member) - - def _member_module(self, member): - modname = getattr(member, '__module__', None) - return self._dyn_modname_map.get(modname, modname) - - -def imported_member(node, member, name): - """consider a class/builtin member where __module__ != current module name - - check if it's sound valid and then add an import node, else use a dummy node - """ - # /!\ some classes like ExtensionClass doesn't have a - # __module__ attribute ! - member_module = getattr(member, '__module__', '__builtin__') - try: - getattr(sys.modules[member_module], name) - except (KeyError, AttributeError): - attach_dummy_node(node, name, member) - else: - attach_import_node(node, member_module, name) + values.append(node) + except InferenceError: + pass diff --git a/debian/changelog b/debian/changelog index 07752ac5..e6181fbf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +logilab-astng (0.21.1-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 11 Jan 2011 15:38:20 +0100 + logilab-astng (0.21.0-1) unstable; urgency=low * new upstream release diff --git a/_exceptions.py b/exceptions.py index 9cf2e98b..7dd6135e 100644 --- a/_exceptions.py +++ b/exceptions.py @@ -58,7 +58,3 @@ class NoDefault(ASTNGError): no default value """ -class IgnoreChild(Exception): - """exception that maybe raised by visit methods to avoid children traversal - """ - diff --git a/inference.py b/inference.py index 25c5fed5..edc71839 100644 --- a/inference.py +++ b/inference.py @@ -20,59 +20,22 @@ """this module contains a set of functions to handle inference on astng trees """ -from __future__ import generators - __doctype__ = "restructuredtext en" from itertools import chain import sys -try: - GeneratorExit # introduced in py2.5 -except NameError: - class GeneratorExit(Exception): pass +from logilab.astng import nodes -from logilab.astng import nodes, raw_building from logilab.astng.manager import ASTNGManager -from logilab.astng import ASTNGError, InferenceError, UnresolvableName, \ - NoDefault, NotFoundError, ASTNGBuildingException -from logilab.astng.bases import YES, Instance, InferenceContext, \ +from logilab.astng.exceptions import (ASTNGBuildingException, ASTNGError, + InferenceError, NoDefault, NotFoundError, UnresolvableName) +from logilab.astng.bases import YES, Instance, InferenceContext, Generator, \ _infer_stmts, copy_context, path_wrapper, raise_if_nothing_infered from logilab.astng.protocols import _arguments_infer_argname MANAGER = ASTNGManager() -_CONST_PROXY = { - type(None): raw_building.build_class('NoneType'), - bool: MANAGER.astng_from_class(bool), - int: MANAGER.astng_from_class(int), - long: MANAGER.astng_from_class(long), - float: MANAGER.astng_from_class(float), - complex: MANAGER.astng_from_class(complex), - str: MANAGER.astng_from_class(str), - unicode: MANAGER.astng_from_class(unicode), - } -_CONST_PROXY[type(None)].parent = _CONST_PROXY[bool].parent -if sys.version_info >= (2, 6): - _CONST_PROXY[bytes] = MANAGER.astng_from_class(bytes) - -# TODO : find a nicer way to handle this situation; we should at least -# be able to avoid calling MANAGER.astng_from_class(const.value.__class__) -# each time (if we can not avoid the property). However __proxied introduced an -# infinite recursion (see https://bugs.launchpad.net/pylint/+bug/456870) -def _set_proxied(const): - return _CONST_PROXY[const.value.__class__] -nodes.Const._proxied = property(_set_proxied) - -def Const_pytype(self): - return self._proxied.qname() -nodes.Const.pytype = Const_pytype - - -nodes.List._proxied = MANAGER.astng_from_class(list) -nodes.Tuple._proxied = MANAGER.astng_from_class(tuple) -nodes.Dict._proxied = MANAGER.astng_from_class(dict) - class CallContext: """when inferring a function call, this class is used to remember values diff --git a/inspector.py b/inspector.py index 181a3fa3..a4abd1f2 100644 --- a/inspector.py +++ b/inspector.py @@ -43,7 +43,7 @@ from logilab.common.modutils import get_module_part, is_relative, \ is_standard_module from logilab import astng -from logilab.astng import InferenceError +from logilab.astng.exceptions import InferenceError from logilab.astng.utils import LocalsVisitor class IdGeneratorMixIn: @@ -46,7 +46,7 @@ from logilab.common.modutils import NoSourceFile, is_python_source, \ get_module_files, get_source_file, zipimport from logilab.common.configuration import OptionsProviderMixIn -from logilab.astng._exceptions import ASTNGBuildingException +from logilab.astng.exceptions import ASTNGBuildingException def astng_wrapper(func, modname): """wrapper to give to ASTNGManager.project_from_files""" @@ -55,12 +55,14 @@ def astng_wrapper(func, modname): return func(modname) except ASTNGBuildingException, exc: print exc - except KeyboardInterrupt: - raise except Exception, exc: import traceback traceback.print_exc() +def _silent_no_wrap(func, modname): + """silent wrapper that doesn't do anything; can be used for tests""" + return func(modname) + def safe_repr(obj): try: return repr(obj) @@ -95,15 +97,9 @@ class ASTNGManager(OptionsProviderMixIn): OptionsProviderMixIn.__init__(self) self.load_defaults() # NOTE: cache entries are added by the [re]builder - self._cache = {} + self.astng_cache = {} self._mod_file_cache = {} - def from_directory(self, directory, modname=None): - """given a module name, return the astng object""" - # FIXME : seems to be dead or unused code - modname = modname or basename(directory) - directory = abspath(directory) - return Package(directory, modname, self) def astng_from_file(self, filepath, modname=None, fallback=True, source=False): """given a module name, return the astng object""" @@ -117,21 +113,11 @@ class ASTNGManager(OptionsProviderMixIn): modname = '.'.join(modpath_from_file(filepath)) except ImportError: modname = filepath - if modname in self._cache: - return self._cache[modname] + if modname in self.astng_cache: + return self.astng_cache[modname] if source: - try: - from logilab.astng.builder import ASTNGBuilder - return ASTNGBuilder(self).file_build(filepath, modname) - except (SyntaxError, KeyboardInterrupt, SystemExit): - raise - except Exception, ex: - if __debug__: - print 'error while building astng for', filepath - import traceback - traceback.print_exc() - msg = 'Unable to load module %s (%s)' % (modname, ex) - raise ASTNGBuildingException, msg, sys.exc_info()[-1] + from logilab.astng.builder import ASTNGBuilder + return ASTNGBuilder(self).file_build(filepath, modname) elif fallback and modname: return self.astng_from_module_name(modname) raise ASTNGBuildingException('unable to get astng for file %s' % @@ -139,8 +125,8 @@ class ASTNGManager(OptionsProviderMixIn): def astng_from_module_name(self, modname, context_file=None): """given a module name, return the astng object""" - if modname in self._cache: - return self._cache[modname] + if modname in self.astng_cache: + return self.astng_cache[modname] old_cwd = os.getcwd() if context_file: os.chdir(dirname(context_file)) @@ -153,9 +139,7 @@ class ASTNGManager(OptionsProviderMixIn): if filepath is None or not is_python_source(filepath): try: module = load_module_from_name(modname) - # catch SystemError as well, we may get that on badly - # initialized C-module - except (SystemError, ImportError), ex: + except Exception, ex: msg = 'Unable to load module %s (%s)' % (modname, ex) raise ASTNGBuildingException(msg) return self.astng_from_module(module, modname) @@ -203,8 +187,8 @@ class ASTNGManager(OptionsProviderMixIn): def astng_from_module(self, module, modname=None): """given an imported module, return the astng object""" modname = modname or module.__name__ - if modname in self._cache: - return self._cache[modname] + if modname in self.astng_cache: + return self.astng_cache[modname] try: # some builtin modules don't have __file__ attribute filepath = module.__file__ @@ -227,22 +211,21 @@ class ASTNGManager(OptionsProviderMixIn): return modastng.getattr(klass.__name__)[0] # XXX - def infer_astng_from_something(self, obj, modname=None, context=None): + def infer_astng_from_something(self, obj, context=None): """infer astng for the given class""" if hasattr(obj, '__class__') and not isinstance(obj, type): klass = obj.__class__ else: klass = obj - if modname is None: - try: - modname = klass.__module__ - except AttributeError: - raise ASTNGBuildingException( - 'Unable to get module for %s' % safe_repr(klass)) - except Exception, ex: - raise ASTNGBuildingException( - 'Unexpected error while retrieving module for %s: %s' - % (safe_repr(klass), ex)) + try: + modname = klass.__module__ + except AttributeError: + raise ASTNGBuildingException( + 'Unable to get module for %s' % safe_repr(klass)) + except Exception, ex: + raise ASTNGBuildingException( + 'Unexpected error while retrieving module for %s: %s' + % (safe_repr(klass), ex)) try: name = klass.__name__ except AttributeError: @@ -278,6 +261,7 @@ class ASTNGManager(OptionsProviderMixIn): astng = func_wrapper(self.astng_from_file, fpath) if astng is None: continue + # XXX why is first file defining the project.path ? project.path = project.path or astng.file project.add_module(astng) base_name = astng.name @@ -293,99 +277,6 @@ class ASTNGManager(OptionsProviderMixIn): return project - -# FIXME : seems to be dead or unused code -class Package: - """a package using a dictionary like interface - - load submodules lazily, as they are needed - """ - - def __init__(self, path, name, manager): - self.name = name - self.path = abspath(path) - self.manager = manager - self.parent = None - self.lineno = 0 - self.__keys = None - self.__subobjects = None - - def fullname(self): - """return the full name of the package (i.e. prefix by the full name - of the parent package if any - """ - if self.parent is None: - return self.name - return '%s.%s' % (self.parent.fullname(), self.name) - - def get_subobject(self, name): - """method used to get sub-objects lazily : sub package or module are - only build once they are requested - """ - if self.__subobjects is None: - try: - self.__subobjects = dict.fromkeys(self.keys()) - except AttributeError: - # python <= 2.3 - self.__subobjects = dict([(k, None) for k in self.keys()]) - obj = self.__subobjects[name] - if obj is None: - objpath = join(self.path, name) - if isdir(objpath): - obj = Package(objpath, name, self.manager) - obj.parent = self - else: - modname = '%s.%s' % (self.fullname(), name) - obj = self.manager.astng_from_file(objpath + '.py', modname) - self.__subobjects[name] = obj - return obj - - def get_module(self, modname): - """return the Module or Package object with the given name if any - """ - path = modname.split('.') - if path[0] != self.name: - raise KeyError(modname) - obj = self - for part in path[1:]: - obj = obj.get_subobject(part) - return obj - - def keys(self): - if self.__keys is None: - self.__keys = [] - for fname in os.listdir(self.path): - if fname.endswith('.py'): - self.__keys.append(fname[:-3]) - continue - fpath = join(self.path, fname) - if isdir(fpath) and exists(join(fpath, '__init__.py')): - self.__keys.append(fname) - self.__keys.sort() - return self.__keys[:] - - def values(self): - return [self.get_subobject(name) for name in self.keys()] - - def items(self): - return zip(self.keys(), self.values()) - - def get(self, name, default=None): - try: - return self.get_subobject(name) - except KeyError: - return default - - def __getitem__(self, name): - return self.get_subobject(name) - - def __contains__(self, name): - return bool(self.get(name)) - - def __iter__(self): - return iter(self.keys()) - - class Project: """a project handle a set of modules / packages""" def __init__(self, name=''): @@ -413,3 +304,4 @@ class Project: return '<Project %r at %s (%s modules)>' % (self.name, id(self), len(self.modules)) + @@ -30,44 +30,13 @@ # You should have received a copy of the GNU Lesser General Public License along # with logilab-astng. If not, see <http://www.gnu.org/licenses/>. """This module contains some mixins for the different nodes. - """ -from logilab.astng import ASTNGBuildingException, InferenceError, NotFoundError -from logilab.astng.bases import BaseClass - -# /!\ We cannot build a StmtNode(NodeNG) class since modifying "__bases__" -# in "nodes.py" has to work *both* for old-style and new-style classes, -# but we need the StmtMixIn for scoped nodes - -class StmtMixIn(BaseClass): - """StmtMixIn used only for a adding a few attributes""" - is_statement = True - - def replace(self, child, newchild): - sequence = self.child_sequence(child) - newchild.parent = self - child.parent = None - sequence[sequence.index(child)] = newchild - - def next_sibling(self): - """return the next sibling statement""" - stmts = self.parent.child_sequence(self) - index = stmts.index(self) - try: - return stmts[index +1] - except IndexError: - pass - - def previous_sibling(self): - """return the previous sibling statement""" - stmts = self.parent.child_sequence(self) - index = stmts.index(self) - if index >= 1: - return stmts[index -1] +from logilab.astng.exceptions import (ASTNGBuildingException, InferenceError, + NotFoundError) -class BlockRangeMixIn(BaseClass): +class BlockRangeMixIn(object): """override block range """ def set_line_info(self, lastchild): self.fromlineno = self.lineno @@ -124,7 +93,7 @@ class ParentAssignTypeMixin(AssignTypeMixin): -class FromImportMixIn(BaseClass, FilterStmtsMixin): +class FromImportMixIn(FilterStmtsMixin): """MixIn for From and Import Nodes""" def _infer_name(self, frame, name): @@ -146,7 +115,7 @@ class FromImportMixIn(BaseClass, FilterStmtsMixin): return mymodule try: return mymodule.import_module(modname, level=level) - except (ASTNGBuildingException, SyntaxError): + except ASTNGBuildingException: raise InferenceError(modname) def real_name(self, asname): diff --git a/node_classes.py b/node_classes.py index c41cba00..9bc6c568 100644 --- a/node_classes.py +++ b/node_classes.py @@ -22,10 +22,10 @@ import sys -from logilab.astng import NoDefault -from logilab.astng.bases import NodeNG, BaseClass, Instance, copy_context, \ - _infer_stmts, YES -from logilab.astng.mixins import StmtMixIn, BlockRangeMixIn, AssignTypeMixin, \ +from logilab.astng.exceptions import NoDefault +from logilab.astng.bases import (NodeNG, Statement, Instance, InferenceContext, + _infer_stmts, YES) +from logilab.astng.mixins import BlockRangeMixIn, AssignTypeMixin, \ ParentAssignTypeMixin, FromImportMixIn @@ -98,7 +98,7 @@ def are_exclusive(stmt1, stmt2, exceptions=None): return False -class LookupMixIn(BaseClass): +class LookupMixIn(object): """Mixin looking up a name in the right scope """ @@ -115,15 +115,14 @@ class LookupMixIn(BaseClass): """ return self.scope().scope_lookup(self, name) - def ilookup(self, name, context=None): + def ilookup(self, name): """infered lookup return an iterator on infered values of the statements returned by the lookup method """ frame, stmts = self.lookup(name) - context = copy_context(context) - context.lookupname = name + context = InferenceContext() return _infer_stmts(stmts, context, frame) def _filter_stmts(self, stmts, frame, offset): @@ -333,19 +332,19 @@ class AssAttr(NodeNG, ParentAssignTypeMixin): _astng_fields = ('expr',) expr = None -class Assert(StmtMixIn, NodeNG): +class Assert(Statement): """class representing an Assert node""" _astng_fields = ('test', 'fail',) test = None fail = None -class Assign(StmtMixIn, NodeNG, AssignTypeMixin): +class Assign(Statement, AssignTypeMixin): """class representing an Assign node""" _astng_fields = ('targets', 'value',) targets = None value = None -class AugAssign(StmtMixIn, NodeNG, AssignTypeMixin): +class AugAssign(Statement, AssignTypeMixin): """class representing an AugAssign node""" _astng_fields = ('target', 'value',) target = None @@ -367,7 +366,7 @@ class BoolOp(NodeNG): _astng_fields = ('values',) values = None -class Break(StmtMixIn, NodeNG): +class Break(Statement): """class representing a Break node""" @@ -428,7 +427,7 @@ class Comprehension(NodeNG): class Const(NodeNG, Instance): - """represent a Str or Num node""" + """represent a constant node like num, str, bool, None, bytes""" def __init__(self, value=None): self.value = value @@ -446,7 +445,11 @@ class Const(NodeNG, Instance): return self.value raise TypeError() -class Continue(StmtMixIn, NodeNG): + def pytype(self): + return self._proxied.qname() + + +class Continue(Statement): """class representing a Continue node""" @@ -468,7 +471,7 @@ class DelAttr(NodeNG, ParentAssignTypeMixin): expr = None -class Delete(StmtMixIn, NodeNG, AssignTypeMixin): +class Delete(Statement, AssignTypeMixin): """class representing a Delete node""" _astng_fields = ('targets',) targets = None @@ -477,7 +480,13 @@ class Delete(StmtMixIn, NodeNG, AssignTypeMixin): class Dict(NodeNG, Instance): """class representing a Dict node""" _astng_fields = ('items',) - items = None + + def __init__(self, items=None): + if items is None: + self.items = [] + else: + self.items = [(const_factory(k), const_factory(v)) + for k,v in items.iteritems()] def pytype(self): return '__builtin__.dict' @@ -508,7 +517,7 @@ class Dict(NodeNG, Instance): raise IndexError(key) -class Discard(StmtMixIn, NodeNG): +class Discard(Statement): """class representing a Discard node""" _astng_fields = ('value',) value = None @@ -522,7 +531,7 @@ class EmptyNode(NodeNG): """class representing an EmptyNode node""" -class ExceptHandler(StmtMixIn, NodeNG, AssignTypeMixin): +class ExceptHandler(Statement, AssignTypeMixin): """class representing an ExceptHandler node""" _astng_fields = ('type', 'name', 'body',) type = None @@ -550,7 +559,7 @@ class ExceptHandler(StmtMixIn, NodeNG, AssignTypeMixin): return True -class Exec(StmtMixIn, NodeNG): +class Exec(Statement): """class representing an Exec node""" _astng_fields = ('expr', 'globals', 'locals',) expr = None @@ -563,8 +572,7 @@ class ExtSlice(NodeNG): _astng_fields = ('dims',) dims = None - -class For(BlockRangeMixIn, StmtMixIn, AssignTypeMixin, NodeNG): +class For(BlockRangeMixIn, AssignTypeMixin, Statement): """class representing a For node""" _astng_fields = ('target', 'iter', 'body', 'orelse',) target = None @@ -577,7 +585,7 @@ class For(BlockRangeMixIn, StmtMixIn, AssignTypeMixin, NodeNG): return self.iter.tolineno -class From(FromImportMixIn, StmtMixIn, NodeNG): +class From(FromImportMixIn, Statement): """class representing a From node""" def __init__(self, fromname, names, level=0): @@ -591,7 +599,7 @@ class Getattr(NodeNG): expr = None -class Global(StmtMixIn, NodeNG): +class Global(Statement): """class representing a Global node""" def __init__(self, names): @@ -601,7 +609,7 @@ class Global(StmtMixIn, NodeNG): return name -class If(BlockRangeMixIn, StmtMixIn, NodeNG): +class If(BlockRangeMixIn, Statement): """class representing an If node""" _astng_fields = ('test', 'body', 'orelse') test = None @@ -629,7 +637,7 @@ class IfExp(NodeNG): orelse = None -class Import(FromImportMixIn, StmtMixIn, NodeNG): +class Import(FromImportMixIn, Statement): """class representing an Import node""" @@ -647,8 +655,13 @@ class Keyword(NodeNG): class List(NodeNG, Instance, ParentAssignTypeMixin): """class representing a List node""" - _astng_fields = ('elts',) - elts = None + _astng_fields = ('elts',) + + def __init__(self, elts=None): + if elts is None: + self.elts = [] + else: + self.elts = [const_factory(e) for e in elts] def pytype(self): return '__builtin__.list' @@ -660,7 +673,7 @@ class List(NodeNG, Instance, ParentAssignTypeMixin): return self.elts -class Nonlocal(StmtMixIn, NodeNG): +class Nonlocal(Statement): """class representing a Nonlocal node""" def __init__(self, names): @@ -670,19 +683,18 @@ class Nonlocal(StmtMixIn, NodeNG): return name -class Pass(StmtMixIn, NodeNG): +class Pass(Statement): """class representing a Pass node""" -class Print(StmtMixIn, NodeNG): +class Print(Statement): """class representing a Print node""" _astng_fields = ('dest', 'values',) dest = None values = None - -class Raise(StmtMixIn, NodeNG): +class Raise(Statement): """class representing a Raise node""" exc = None if sys.version_info < (3, 0): @@ -691,8 +703,8 @@ class Raise(StmtMixIn, NodeNG): tback = None else: _astng_fields = ('exc', 'cause') - inst = None - tback = None + exc = None + cause = None def raises_not_implemented(self): if not self.exc: @@ -702,7 +714,7 @@ class Raise(StmtMixIn, NodeNG): return True -class Return(StmtMixIn, NodeNG): +class Return(Statement): """class representing a Return node""" _astng_fields = ('value',) value = None @@ -711,7 +723,12 @@ class Return(StmtMixIn, NodeNG): class Set(NodeNG, Instance, ParentAssignTypeMixin): """class representing a Set node""" _astng_fields = ('elts',) - elts = None + + def __init__(self, elts=None): + if elts is None: + self.elts = [] + else: + self.elts = [const_factory(e) for e in elts] def pytype(self): return '__builtin__.set' # XXX __builtin__ vs builtins @@ -740,7 +757,7 @@ class Subscript(NodeNG): slice = None -class TryExcept(BlockRangeMixIn, StmtMixIn, NodeNG): +class TryExcept(BlockRangeMixIn, Statement): """class representing a TryExcept node""" _astng_fields = ('body', 'handlers', 'orelse',) body = None @@ -766,7 +783,7 @@ class TryExcept(BlockRangeMixIn, StmtMixIn, NodeNG): return self._elsed_block_range(lineno, self.orelse, last) -class TryFinally(BlockRangeMixIn, StmtMixIn, NodeNG): +class TryFinally(BlockRangeMixIn, Statement): """class representing a TryFinally node""" _astng_fields = ('body', 'finalbody',) body = None @@ -788,7 +805,12 @@ class TryFinally(BlockRangeMixIn, StmtMixIn, NodeNG): class Tuple(NodeNG, Instance, ParentAssignTypeMixin): """class representing a Tuple node""" _astng_fields = ('elts',) - elts = None + + def __init__(self, elts=None): + if elts is None: + self.elts = [] + else: + self.elts = [const_factory(e) for e in elts] def pytype(self): return '__builtin__.tuple' @@ -806,7 +828,7 @@ class UnaryOp(NodeNG): operand = None -class While(BlockRangeMixIn, StmtMixIn, NodeNG): +class While(BlockRangeMixIn, Statement): """class representing a While node""" _astng_fields = ('test', 'body', 'orelse',) test = None @@ -820,7 +842,8 @@ class While(BlockRangeMixIn, StmtMixIn, NodeNG): """handle block line numbers range for for and while statements""" return self. _elsed_block_range(lineno, self.orelse) -class With(BlockRangeMixIn, StmtMixIn, AssignTypeMixin, NodeNG): + +class With(BlockRangeMixIn, AssignTypeMixin, Statement): """class representing a With node""" _astng_fields = ('expr', 'vars', 'body') expr = None @@ -845,24 +868,30 @@ CONST_CLS = { list: List, tuple: Tuple, dict: Dict, + set: Set, + type(None): Const, } +def _update_const_classes(): + """update constant classes, so the keys of CONST_CLS can be reused""" + klasses = (bool, int, float, complex, str) + if sys.version_info < (3, 0): + klasses += (unicode, long) + if sys.version_info >= (2, 6): + klasses += (bytes,) + for kls in klasses: + CONST_CLS[kls] = Const +_update_const_classes() + def const_factory(value): """return an astng node for a python value""" try: - # if value is of class list, tuple, dict use specific class, not Const - cls = CONST_CLS[value.__class__] - node = cls() - if isinstance(node, Dict): - node.items = () - else: - node.elts = () - except KeyError: - # why was value in (None, False, True) not OK? - assert isinstance(value, (int, long, complex, float, basestring)) or value in (None, False, True) - node = Const() - node.value = value - return node - - - + return CONST_CLS[value.__class__](value) + except (KeyError, AttributeError): + # some constants (like from gtk._gtk) don't have their class in + # CONST_CLS, though we can "assert isinstance(value, tuple(CONST_CLS))" + if isinstance(value, tuple(CONST_CLS)): + return Const(value) + node = EmptyNode() + node.object = value + return None diff --git a/protocols.py b/protocols.py index 301ef0fc..34bbe759 100644 --- a/protocols.py +++ b/protocols.py @@ -19,14 +19,11 @@ # with logilab-astng. If not, see <http://www.gnu.org/licenses/>. """this module contains a set of functions to handle python protocols for nodes where it makes sense. - """ -from __future__ import generators - __doctype__ = "restructuredtext en" -from logilab.astng import InferenceError, NoDefault +from logilab.astng.exceptions import InferenceError, NoDefault from logilab.astng.node_classes import unpack_infer from logilab.astng.bases import copy_context, \ raise_if_nothing_infered, yes_if_nothing_infered, Instance, Generator, YES @@ -198,7 +195,7 @@ def for_assigned_stmts(self, node, context=None, asspath=None): yield item else: for infered in _resolve_looppart(self.iter.infer(context), - asspath, context): + asspath, context): yield infered nodes.For.assigned_stmts = raise_if_nothing_infered(for_assigned_stmts) diff --git a/raw_building.py b/raw_building.py index 534f58c5..f6da03ed 100644 --- a/raw_building.py +++ b/raw_building.py @@ -1,15 +1,3 @@ -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # copyright 2003-2010 Sylvain Thenault, all rights reserved. @@ -36,10 +24,18 @@ __docformat__ = "restructuredtext en" import sys -from inspect import getargspec +from os.path import abspath +from inspect import (getargspec, isdatadescriptor, isfunction, ismethod, + ismethoddescriptor, isclass, isbuiltin) -from logilab.astng import nodes +from logilab.astng.node_classes import CONST_CLS +from logilab.astng.nodes import (Module, Class, Const, const_factory, From, + Function, EmptyNode, Name, Arguments, Dict, List, Set, Tuple) +from logilab.astng.bases import Generator +from logilab.astng.manager import ASTNGManager +MANAGER = ASTNGManager() +_CONSTANTS = tuple(CONST_CLS) # the keys of CONST_CLS eg python builtin types def _attach_local_node(parent, node, name): node.name = name # needed by add_local_node @@ -51,39 +47,39 @@ def attach_dummy_node(node, name, object=_marker): """create a dummy node and register it in the locals of the given node with the specified name """ - enode = nodes.EmptyNode() + enode = EmptyNode() enode.object = object _attach_local_node(node, enode, name) -nodes.EmptyNode.has_underlying_object = lambda self: self.object is not _marker +EmptyNode.has_underlying_object = lambda self: self.object is not _marker def attach_const_node(node, name, value): """create a Const node and register it in the locals of the given node with the specified name """ if not name in node.special_attributes: - _attach_local_node(node, nodes.const_factory(value), name) + _attach_local_node(node, const_factory(value), name) def attach_import_node(node, modname, membername): """create a From node and register it in the locals of the given node with the specified name """ - from_node = nodes.From(modname, [(membername, None)]) + from_node = From(modname, [(membername, None)]) _attach_local_node(node, from_node, membername) def build_module(name, doc=None): """create and initialize a astng Module node""" - node = nodes.Module(name, doc, pure_python=False) + node = Module(name, doc, pure_python=False) node.package = False node.parent = None return node def build_class(name, basenames=(), doc=None): """create and initialize a astng Class node""" - node = nodes.Class(name, doc) + node = Class(name, doc) for base in basenames: - basenode = nodes.Name() + basenode = Name() basenode.name = base node.bases.append(basenode) basenode.parent = node @@ -93,16 +89,16 @@ def build_function(name, args=None, defaults=None, flag=0, doc=None): """create and initialize a astng Function node""" args, defaults = args or [], defaults or [] # first argument is now a list of decorators - func = nodes.Function(name, doc) - func.args = argsnode = nodes.Arguments() + func = Function(name, doc) + func.args = argsnode = Arguments() argsnode.args = [] for arg in args: - argsnode.args.append(nodes.Name()) + argsnode.args.append(Name()) argsnode.args[-1].name = arg argsnode.args[-1].parent = argsnode argsnode.defaults = [] for default in defaults: - argsnode.defaults.append(nodes.const_factory(default)) + argsnode.defaults.append(const_factory(default)) argsnode.defaults[-1].parent = argsnode argsnode.kwarg = None argsnode.vararg = None @@ -112,19 +108,9 @@ def build_function(name, args=None, defaults=None, flag=0, doc=None): return func -# def build_name_assign(name, value): -# """create and initialize an astng Assign for a name assignment""" -# return nodes.Assign([nodes.AssName(name, 'OP_ASSIGN')], nodes.Const(value)) - -# def build_attr_assign(name, value, attr='self'): -# """create and initialize an astng Assign for an attribute assignment""" -# return nodes.Assign([nodes.AssAttr(nodes.Name(attr), name, 'OP_ASSIGN')], -# nodes.Const(value)) - - def build_from_import(fromname, names): """create and initialize an astng From import statement""" - return nodes.From(fromname, [(name, None) for name in names]) + return From(fromname, [(name, None) for name in names]) def register_arguments(func, args=None): """add given arguments to local @@ -139,7 +125,7 @@ def register_arguments(func, args=None): if func.args.kwarg: func.set_local(func.args.kwarg, func.args) for arg in args: - if isinstance(arg, nodes.Name): + if isinstance(arg, Name): func.set_local(arg.name, arg) else: register_arguments(func, arg.elts) @@ -196,7 +182,7 @@ def _base_class_object_build(node, member, basenames, name=None, localname=None) pass else: for name, obj in instdict.items(): - valnode = nodes.EmptyNode() + valnode = EmptyNode() valnode.object = obj valnode.parent = klass valnode.lineno = 1 @@ -204,8 +190,153 @@ def _base_class_object_build(node, member, basenames, name=None, localname=None) return klass -__all__ = ('register_arguments', 'build_module', - 'object_build_class', 'object_build_function', - 'object_build_datadescriptor', 'object_build_methoddescriptor', - 'attach_dummy_node', - 'attach_const_node', 'attach_import_node') + + +class InspectBuilder(object): + """class for building nodes from living object + + this is actually a really minimal representation, including only Module, + Function and Class nodes and some others as guessed. + """ + + # astng from living objects ############################################### + + def __init__(self): + self._done = {} + self._module = None + + def inspect_build(self, module, modname=None, path=None): + """build astng from a living module (i.e. using inspect) + this is used when there is no python source code available (either + because it's a built-in module or because the .py is not available) + """ + self._module = module + if modname is None: + modname = module.__name__ + node = build_module(modname, module.__doc__) + node.file = node.path = path and abspath(path) or path + MANAGER.astng_cache[modname] = node + node.package = hasattr(module, '__path__') + self._done = {} + self.object_build(node, module) + return node + + def object_build(self, node, obj): + """recursive method which create a partial ast from real objects + (only function, class, and method are handled) + """ + if obj in self._done: + return self._done[obj] + self._done[obj] = node + for name in dir(obj): + try: + member = getattr(obj, name) + except AttributeError: + # damned ExtensionClass.Base, I know you're there ! + attach_dummy_node(node, name) + continue + if ismethod(member): + member = member.im_func + if isfunction(member): + # verify this is not an imported function + if member.func_code.co_filename != getattr(self._module, '__file__', None): + attach_dummy_node(node, name, member) + continue + object_build_function(node, member, name) + elif isbuiltin(member): + if self.imported_member(node, member, name): + continue + object_build_methoddescriptor(node, member, name) + elif isclass(member): + if self.imported_member(node, member, name): + continue + if member in self._done: + class_node = self._done[member] + if not class_node in node.locals.get(name, ()): + node.add_local_node(class_node, name) + else: + class_node = object_build_class(node, member, name) + # recursion + self.object_build(class_node, member) + if name == '__class__' and class_node.parent is None: + class_node.parent = self._done[self._module] + elif ismethoddescriptor(member): + assert isinstance(member, object) + object_build_methoddescriptor(node, member, name) + elif isdatadescriptor(member): + assert isinstance(member, object) + object_build_datadescriptor(node, member, name) + elif isinstance(member, _CONSTANTS): + attach_const_node(node, name, member) + else: + # create an empty node so that the name is actually defined + attach_dummy_node(node, name, member) + + def imported_member(self, node, member, name): + """verify this is not an imported class or handle it""" + # /!\ some classes like ExtensionClass doesn't have a __module__ + # attribute ! Also, this may trigger an exception on badly built module + # (see http://www.logilab.org/ticket/57299 for instance) + try: + modname = getattr(member, '__module__', None) + except: + # XXX use logging + print 'unexpected error while building astng from living object' + import traceback + traceback.print_exc() + modname = None + if modname is None: + if name in ('__new__', '__subclasshook__'): + # Python 2.5.1 (r251:54863, Sep 1 2010, 22:03:14) + # >>> print object.__new__.__module__ + # None + modname = '__builtin__' + else: + attach_dummy_node(node, name, member) + return True + if {'gtk': 'gtk._gtk'}.get(modname, modname) != self._module.__name__: + # check if it sounds valid and then add an import node, else use a + # dummy node + try: + getattr(sys.modules[modname], name) + except (KeyError, AttributeError): + attach_dummy_node(node, name, member) + else: + attach_import_node(node, modname, name) + return True + return False + + +### astng boot strapping ################################################### ### + +_CONST_PROXY = {} +def astng_boot_strapping(): + """astng boot strapping the builtins module""" + # this boot strapping is necessary since we need the Const nodes to + # inspect_build builtins, and then we can proxy Const + builder = InspectBuilder() + from logilab.common.compat import builtins + astng_builtin = builder.inspect_build(builtins) + for cls, node_cls in CONST_CLS.items(): + if cls is type(None): + proxy = build_class('NoneType') + proxy.parent = astng_builtin + else: + proxy = astng_builtin.getattr(cls.__name__)[0] # XXX + if cls in (dict, list, set, tuple): + node_cls._proxied = proxy + else: + _CONST_PROXY[cls] = proxy + +astng_boot_strapping() + +# TODO : find a nicer way to handle this situation; +# However __proxied introduced an +# infinite recursion (see https://bugs.launchpad.net/pylint/+bug/456870) +def _set_proxied(const): + return _CONST_PROXY[const.value.__class__] +Const._proxied = property(_set_proxied) + +# FIXME : is it alright that Generator._proxied is not a astng node? +Generator._proxied = MANAGER.infer_astng_from_something(type(a for a in ())) + diff --git a/rebuilder.py b/rebuilder.py index de7ca375..d86013e3 100644 --- a/rebuilder.py +++ b/rebuilder.py @@ -29,14 +29,62 @@ # # You should have received a copy of the GNU Lesser General Public License along # with logilab-astng. If not, see <http://www.gnu.org/licenses/>. -"""this module contains utilities for rebuilding a compiler.ast or _ast tree in +"""this module contains utilities for rebuilding a _ast tree in order to get a single ASTNG representation - """ -from logilab.astng import ASTNGBuildingException, InferenceError -from logilab.astng import nodes -from logilab.astng.bases import YES, Instance +import sys +from _ast import (Expr as Discard, Str, + # binary operators + Add, Div, FloorDiv, Mod, Mult, Pow, Sub, BitAnd, BitOr, BitXor, + LShift, RShift, + # logical operators + And, Or, + # unary operators + UAdd, USub, Not, Invert, + # comparison operators + Eq, Gt, GtE, In, Is, IsNot, Lt, LtE, NotEq, NotIn, + ) + +from logilab.astng.exceptions import ASTNGBuildingException +from logilab.astng import nodes as new + + +_BIN_OP_CLASSES = {Add: '+', + BitAnd: '&', + BitOr: '|', + BitXor: '^', + Div: '/', + FloorDiv: '//', + Mod: '%', + Mult: '*', + Pow: '**', + Sub: '-', + LShift: '<<', + RShift: '>>'} + +_BOOL_OP_CLASSES = {And: 'and', + Or: 'or'} + +_UNARY_OP_CLASSES = {UAdd: '+', + USub: '-', + Not: 'not', + Invert: '~'} + +_CMP_OP_CLASSES = {Eq: '==', + Gt: '>', + GtE: '>=', + In: 'in', + Is: 'is', + IsNot: 'is not', + Lt: '<', + LtE: '<=', + NotEq: '!=', + NotIn: 'not in'} + +CONST_NAME_TRANSFORMS = {'None': None, + 'True': True, + 'False': False} REDIRECT = {'arguments': 'Arguments', 'Attribute': 'Getattr', @@ -52,109 +100,212 @@ REDIRECT = {'arguments': 'Arguments', 'ImportFrom': 'From', 'keyword': 'Keyword', 'Repr': 'Backquote', - - 'Add': 'BinOp', - 'Bitand': 'BinOp', - 'Bitor': 'BinOp', - 'Bitxor': 'BinOp', - 'Div': 'BinOp', - 'FloorDiv': 'BinOp', - 'LeftShift': 'BinOp', - 'Mod': 'BinOp', - 'Mul': 'BinOp', - 'Power': 'BinOp', - 'RightShift': 'BinOp', - 'Sub': 'BinOp', - - 'And': 'BoolOp', - 'Or': 'BoolOp', - - 'UnaryAdd': 'UnaryOp', - 'UnarySub': 'UnaryOp', - 'Not': 'UnaryOp', - 'Invert': 'UnaryOp' } -_key_func = lambda node: node.fromlineno -def sort_locals(my_list): - my_list.sort(key=_key_func) +def _init_set_doc(node, newnode): + newnode.doc = None + try: + if isinstance(node.body[0], Discard) and isinstance(node.body[0].value, Str): + newnode.tolineno = node.body[0].lineno + newnode.doc = node.body[0].value.s + node.body = node.body[1:] -class RebuildVisitor(object): - """Visitor to transform an AST to an ASTNG - """ - def __init__(self, manager): - self._manager = manager - self.asscontext = None - self._metaclass = None - self._global_names = None - self._delayed_assattr = [] + except IndexError: + pass # ast built from scratch - def visit(self, node, parent): - if node is None: # some attributes of some nodes are just None - return None - cls_name = node.__class__.__name__ - visit_name = 'visit_' + REDIRECT.get(cls_name, cls_name).lower() - visit_method = getattr(self, visit_name) - return visit_method(node, parent) - - def build(self, node, modname, module_file, package): - """rebuild the tree starting with an Module node; - return an astng.Module node - """ +def _lineno_parent(oldnode, newnode, parent): + newnode.parent = parent + if hasattr(oldnode, 'lineno'): + newnode.lineno = oldnode.lineno + +def _set_infos(oldnode, newnode, parent): + newnode.parent = parent + if hasattr(oldnode, 'lineno'): + newnode.lineno = oldnode.lineno + newnode.set_line_info(newnode.last_child()) # set_line_info accepts None + + + + +class TreeRebuilder(object): + """Rebuilds the _ast tree to become an ASTNG tree""" + + _visit_meths = {} + def __init__(self): + self.init() + + def init(self): + self.asscontext = None self._metaclass = [''] self._global_names = [] self._from_nodes = [] - module = self.visit_module(node, modname, package) - module.file = module.path = module_file - # init module cache here else we may get some infinite recursion - # errors while infering delayed assignments - if self._manager is not None: - self._manager._cache[module.name] = module - # handle delayed assattr nodes - for from_node in self._from_nodes: - self._add_from_names_to_locals(from_node, delayed=True) - delay_assattr = self.delayed_assattr - for delayed in self._delayed_assattr: - delay_assattr(delayed) - return module - - def _save_argument_name(self, node): - """save argument names in locals""" + self._delayed_assattr = [] + + def visit(self, node, parent): + cls = node.__class__ + if cls in self._visit_meths: + return self._visit_meths[cls](node, parent) + else: + cls_name = cls.__name__ + visit_name = 'visit_' + REDIRECT.get(cls_name, cls_name).lower() + visit_method = getattr(self, visit_name) + self._visit_meths[cls] = visit_method + return visit_method(node, parent) + + def _save_assignment(self, node, name=None): + """save assignement situation since node.parent is not available yet""" + if self._global_names and node.name in self._global_names[-1]: + node.root().set_local(node.name, node) + else: + node.parent.set_local(node.name, node) + + + def visit_arguments(self, node, parent): + """visit a Arguments node by returning a fresh instance of it""" + newnode = new.Arguments() + _lineno_parent(node, newnode, parent) + self.asscontext = "Ass" + newnode.args = [self.visit(child, newnode) for child in node.args] + self.asscontext = None + newnode.defaults = [self.visit(child, newnode) for child in node.defaults] + newnode.vararg = node.vararg + newnode.kwarg = node.kwarg + # save argument names in locals: if node.vararg: - node.parent.set_local(node.vararg, node) + newnode.parent.set_local(newnode.vararg, newnode) if node.kwarg: - node.parent.set_local(node.kwarg, node) + newnode.parent.set_local(newnode.kwarg, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + def visit_assattr(self, node, parent): + """visit a AssAttr node by returning a fresh instance of it""" + assc, self.asscontext = self.asscontext, None + newnode = new.AssAttr() + _lineno_parent(node, newnode, parent) + newnode.expr = self.visit(node.expr, newnode) + self.asscontext = assc + self._delayed_assattr.append(newnode) + newnode.set_line_info(newnode.last_child()) + return newnode - # visit_<node> and delayed_<node> methods ################################# + def visit_assert(self, node, parent): + """visit a Assert node by returning a fresh instance of it""" + newnode = new.Assert() + _lineno_parent(node, newnode, parent) + newnode.test = self.visit(node.test, newnode) + if node.msg is not None: + newnode.fail = self.visit(node.msg, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode - def _set_assign_infos(self, newnode): - """set some function or metaclass infos""" # XXX right ? + def visit_assign(self, node, parent): + """visit a Assign node by returning a fresh instance of it""" + newnode = new.Assign() + _lineno_parent(node, newnode, parent) + self.asscontext = "Ass" + newnode.targets = [self.visit(child, newnode) for child in node.targets] + self.asscontext = None + newnode.value = self.visit(node.value, newnode) + # set some function or metaclass infos XXX explain ? klass = newnode.parent.frame() - if (isinstance(klass, nodes.Class) - and isinstance(newnode.value, nodes.CallFunc) - and isinstance(newnode.value.func, nodes.Name)): + if (isinstance(klass, new.Class) + and isinstance(newnode.value, new.CallFunc) + and isinstance(newnode.value.func, new.Name)): func_name = newnode.value.func.name for ass_node in newnode.targets: try: meth = klass[ass_node.name] - if isinstance(meth, nodes.Function): + if isinstance(meth, new.Function): if func_name in ('classmethod', 'staticmethod'): meth.type = func_name - try: # XXX use setdefault ? - meth.extra_decorators.append(newnode.value) - except AttributeError: - meth.extra_decorators = [newnode.value] + meth.extra_decorators.append(newnode.value) except (AttributeError, KeyError): continue elif getattr(newnode.targets[0], 'name', None) == '__metaclass__': # XXX check more... self._metaclass[-1] = 'type' # XXX get the actual metaclass + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_assname(self, node, parent, node_name=None): + '''visit a node and return a AssName node''' + newnode = new.AssName() + _set_infos(node, newnode, parent) + newnode.name = node_name + self._save_assignment(newnode) + return newnode + + def visit_augassign(self, node, parent): + """visit a AugAssign node by returning a fresh instance of it""" + newnode = new.AugAssign() + _lineno_parent(node, newnode, parent) + newnode.op = _BIN_OP_CLASSES[node.op.__class__] + "=" + self.asscontext = "Ass" + newnode.target = self.visit(node.target, newnode) + self.asscontext = None + newnode.value = self.visit(node.value, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_backquote(self, node, parent): + """visit a Backquote node by returning a fresh instance of it""" + newnode = new.Backquote() + _lineno_parent(node, newnode, parent) + newnode.value = self.visit(node.value, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_binop(self, node, parent): + """visit a BinOp node by returning a fresh instance of it""" + newnode = new.BinOp() + _lineno_parent(node, newnode, parent) + newnode.left = self.visit(node.left, newnode) + newnode.right = self.visit(node.right, newnode) + newnode.op = _BIN_OP_CLASSES[node.op.__class__] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_boolop(self, node, parent): + """visit a BoolOp node by returning a fresh instance of it""" + newnode = new.BoolOp() + _lineno_parent(node, newnode, parent) + newnode.values = [self.visit(child, newnode) for child in node.values] + newnode.op = _BOOL_OP_CLASSES[node.op.__class__] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_break(self, node, parent): + """visit a Break node by returning a fresh instance of it""" + newnode = new.Break() + _set_infos(node, newnode, parent) + return newnode + + def visit_callfunc(self, node, parent): + """visit a CallFunc node by returning a fresh instance of it""" + newnode = new.CallFunc() + _lineno_parent(node, newnode, parent) + newnode.func = self.visit(node.func, newnode) + newnode.args = [self.visit(child, newnode) for child in node.args] + if node.starargs is not None: + newnode.starargs = self.visit(node.starargs, newnode) + if node.kwargs is not None: + newnode.kwargs = self.visit(node.kwargs, newnode) + newnode.args.extend(self.visit(child, newnode) for child in node.keywords) + newnode.set_line_info(newnode.last_child()) + return newnode def visit_class(self, node, parent): """visit a Class node to become astng""" self._metaclass.append(self._metaclass[-1]) - newnode = self._visit_class(node, parent) + newnode = new.Class(node.name, None) + _lineno_parent(node, newnode, parent) + _init_set_doc(node, newnode) + newnode.bases = [self.visit(child, newnode) for child in node.bases] + newnode.body = [self.visit(child, newnode) for child in node.body] + if 'decorator_list' in node._fields and node.decorator_list:# py >= 2.6 + newnode.decorators = self.visit_decorators(node, newnode) + newnode.set_line_info(newnode.last_child()) metaclass = self._metaclass.pop() if not newnode.bases: # no base classes, detect new / style old style according to @@ -163,148 +314,555 @@ class RebuildVisitor(object): newnode.parent.frame().set_local(newnode.name, newnode) return newnode - def visit_break(self, node, parent): - """visit a Break node by returning a fresh instance of it""" - newnode = nodes.Break() - self._set_infos(node, newnode, parent) - return newnode - def visit_const(self, node, parent): """visit a Const node by returning a fresh instance of it""" - newnode = nodes.Const(node.value) - self._set_infos(node, newnode, parent) + newnode = new.Const(node.value) + _set_infos(node, newnode, parent) return newnode def visit_continue(self, node, parent): """visit a Continue node by returning a fresh instance of it""" - newnode = nodes.Continue() - self._set_infos(node, newnode, parent) + newnode = new.Continue() + _set_infos(node, newnode, parent) + return newnode + + def visit_compare(self, node, parent): + """visit a Compare node by returning a fresh instance of it""" + newnode = new.Compare() + _lineno_parent(node, newnode, parent) + newnode.left = self.visit(node.left, newnode) + newnode.ops = [(_CMP_OP_CLASSES[op.__class__], self.visit(expr, newnode)) + for (op, expr) in zip(node.ops, node.comparators)] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_comprehension(self, node, parent): + """visit a Comprehension node by returning a fresh instance of it""" + newnode = new.Comprehension() + _lineno_parent(node, newnode, parent) + self.asscontext = "Ass" + newnode.target = self.visit(node.target, newnode) + self.asscontext = None + newnode.iter = self.visit(node.iter, newnode) + newnode.ifs = [self.visit(child, newnode) for child in node.ifs] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_decorators(self, node, parent): + """visit a Decorators node by returning a fresh instance of it""" + # /!\ node is actually a _ast.Function node while + # parent is a astng.nodes.Function node + newnode = new.Decorators() + _lineno_parent(node, newnode, parent) + if 'decorators' in node._fields: # py < 2.6, i.e. 2.5 + decorators = node.decorators + else: + decorators= node.decorator_list + newnode.nodes = [self.visit(child, newnode) for child in decorators] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_delete(self, node, parent): + """visit a Delete node by returning a fresh instance of it""" + newnode = new.Delete() + _lineno_parent(node, newnode, parent) + self.asscontext = "Del" + newnode.targets = [self.visit(child, newnode) for child in node.targets] + self.asscontext = None + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_dict(self, node, parent): + """visit a Dict node by returning a fresh instance of it""" + newnode = new.Dict() + _lineno_parent(node, newnode, parent) + newnode.items = [(self.visit(key, newnode), self.visit(value, newnode)) + for key, value in zip(node.keys, node.values)] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_dictcomp(self, node, parent): + """visit a DictComp node by returning a fresh instance of it""" + newnode = new.DictComp() + _lineno_parent(node, newnode, parent) + newnode.key = self.visit(node.key, newnode) + newnode.value = self.visit(node.value, newnode) + newnode.generators = [self.visit(child, newnode) + for child in node.generators] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_discard(self, node, parent): + """visit a Discard node by returning a fresh instance of it""" + newnode = new.Discard() + _lineno_parent(node, newnode, parent) + newnode.value = self.visit(node.value, newnode) + newnode.set_line_info(newnode.last_child()) return newnode def visit_ellipsis(self, node, parent): """visit an Ellipsis node by returning a fresh instance of it""" - newnode = nodes.Ellipsis() - self._set_infos(node, newnode, parent) + newnode = new.Ellipsis() + _set_infos(node, newnode, parent) return newnode def visit_emptynode(self, node, parent): """visit an EmptyNode node by returning a fresh instance of it""" - newnode = nodes.EmptyNode() - self._set_infos(node, newnode, parent) + newnode = new.EmptyNode() + _set_infos(node, newnode, parent) return newnode - def _store_from_node(self, node): - """handle From names by adding them to locals now or after""" - # we can not handle wildcard imports if the source module is not - # in the cache since 'import_module' calls the MANAGER and we will - # end up with infinite recursions working with unfinished trees - if node.modname in self._manager._cache: - self._add_from_names_to_locals(node) - else: - self._from_nodes.append(node) - - def _add_from_names_to_locals(self, node, delayed=False): - """store imported names to the locals; - resort the locals if coming from a delayed node - """ - for (name, asname) in node.names: - if name == '*': - try: - imported = node.root().import_module(node.modname) - except ASTNGBuildingException: - continue - for name in imported.wildcard_import_names(): - node.parent.set_local(name, node) - if delayed: - sort_locals(node.parent.scope().locals[name]) + def visit_excepthandler(self, node, parent): + """visit an ExceptHandler node by returning a fresh instance of it""" + newnode = new.ExceptHandler() + _lineno_parent(node, newnode, parent) + if node.type is not None: + newnode.type = self.visit(node.type, newnode) + if node.name is not None: + # /!\ node.name can be a tuple + self.asscontext = "Ass" + newnode.name = self.visit(node.name, newnode) + self.asscontext = None + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.set_line_info(newnode.last_child()) + return newnode - else: - node.parent.set_local(asname or name, node) - if delayed: - sort_locals(node.parent.scope().locals[asname or name]) + def visit_exec(self, node, parent): + """visit an Exec node by returning a fresh instance of it""" + newnode = new.Exec() + _lineno_parent(node, newnode, parent) + newnode.expr = self.visit(node.body, newnode) + if node.globals is not None: + newnode.globals = self.visit(node.globals, newnode) + if node.locals is not None: + newnode.locals = self.visit(node.locals, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_extslice(self, node, parent): + """visit an ExtSlice node by returning a fresh instance of it""" + newnode = new.ExtSlice() + _lineno_parent(node, newnode, parent) + newnode.dims = [self.visit(dim, newnode) for dim in node.dims] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_for(self, node, parent): + """visit a For node by returning a fresh instance of it""" + newnode = new.For() + _lineno_parent(node, newnode, parent) + self.asscontext = "Ass" + newnode.target = self.visit(node.target, newnode) + self.asscontext = None + newnode.iter = self.visit(node.iter, newnode) + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.orelse = [self.visit(child, newnode) for child in node.orelse] + newnode.set_line_info(newnode.last_child()) + return newnode + def visit_from(self, node, parent): + """visit a From node by returning a fresh instance of it""" + names = [(alias.name, alias.asname) for alias in node.names] + newnode = new.From(node.module or '', names, node.level) + _set_infos(node, newnode, parent) + # store From names to add them to locals after building + self._from_nodes.append(newnode) + return newnode def visit_function(self, node, parent): """visit an Function node to become astng""" self._global_names.append({}) - newnode = self._visit_function(node, parent) + newnode = new.Function(node.name, None) + _lineno_parent(node, newnode, parent) + _init_set_doc(node, newnode) + newnode.args = self.visit(node.args, newnode) + newnode.body = [self.visit(child, newnode) for child in node.body] + if 'decorators' in node._fields: # py < 2.6 + attr = 'decorators' + else: + attr = 'decorator_list' + decorators = getattr(node, attr) + if decorators: + newnode.decorators = self.visit_decorators(node, newnode) + newnode.set_line_info(newnode.last_child()) self._global_names.pop() frame = newnode.parent.frame() - if isinstance(frame, nodes.Class): + if isinstance(frame, new.Class): if newnode.name == '__new__': newnode.type = 'classmethod' else: newnode.type = 'method' if newnode.decorators is not None: for decorator_expr in newnode.decorators.nodes: - if isinstance(decorator_expr, nodes.Name) and \ + if isinstance(decorator_expr, new.Name) and \ decorator_expr.name in ('classmethod', 'staticmethod'): newnode.type = decorator_expr.name frame.set_local(newnode.name, newnode) return newnode + def visit_genexpr(self, node, parent): + """visit a GenExpr node by returning a fresh instance of it""" + newnode = new.GenExpr() + _lineno_parent(node, newnode, parent) + newnode.elt = self.visit(node.elt, newnode) + newnode.generators = [self.visit(child, newnode) for child in node.generators] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_getattr(self, node, parent): + """visit a Getattr node by returning a fresh instance of it""" + if self.asscontext == "Del": + # FIXME : maybe we should reintroduce and visit_delattr ? + # for instance, deactivating asscontext + newnode = new.DelAttr() + elif self.asscontext == "Ass": + # FIXME : maybe we should call visit_assattr ? + newnode = new.AssAttr() + self._delayed_assattr.append(newnode) + else: + newnode = new.Getattr() + _lineno_parent(node, newnode, parent) + asscontext, self.asscontext = self.asscontext, None + newnode.expr = self.visit(node.value, newnode) + self.asscontext = asscontext + newnode.attrname = node.attr + newnode.set_line_info(newnode.last_child()) + return newnode + def visit_global(self, node, parent): """visit an Global node to become astng""" - newnode = nodes.Global(node.names) - self._set_infos(node, newnode, parent) + newnode = new.Global(node.names) + _set_infos(node, newnode, parent) if self._global_names: # global at the module level, no effect for name in node.names: self._global_names[-1].setdefault(name, []).append(newnode) return newnode - def _save_import_locals(self, newnode): - """save import names in parent's locals""" + def visit_if(self, node, parent): + """visit a If node by returning a fresh instance of it""" + newnode = new.If() + _lineno_parent(node, newnode, parent) + newnode.test = self.visit(node.test, newnode) + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.orelse = [self.visit(child, newnode) for child in node.orelse] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_ifexp(self, node, parent): + """visit a IfExp node by returning a fresh instance of it""" + newnode = new.IfExp() + _lineno_parent(node, newnode, parent) + newnode.test = self.visit(node.test, newnode) + newnode.body = self.visit(node.body, newnode) + newnode.orelse = self.visit(node.orelse, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_import(self, node, parent): + """visit a Import node by returning a fresh instance of it""" + newnode = new.Import() + _set_infos(node, newnode, parent) + newnode.names = [(alias.name, alias.asname) for alias in node.names] + # save import names in parent's locals: for (name, asname) in newnode.names: name = asname or name newnode.parent.set_local(name.split('.')[0], newnode) + return newnode + + def visit_index(self, node, parent): + """visit a Index node by returning a fresh instance of it""" + newnode = new.Index() + _lineno_parent(node, newnode, parent) + newnode.value = self.visit(node.value, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_keyword(self, node, parent): + """visit a Keyword node by returning a fresh instance of it""" + newnode = new.Keyword() + _lineno_parent(node, newnode, parent) + newnode.arg = node.arg + newnode.value = self.visit(node.value, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_lambda(self, node, parent): + """visit a Lambda node by returning a fresh instance of it""" + newnode = new.Lambda() + _lineno_parent(node, newnode, parent) + newnode.args = self.visit(node.args, newnode) + newnode.body = self.visit(node.body, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_list(self, node, parent): + """visit a List node by returning a fresh instance of it""" + newnode = new.List() + _lineno_parent(node, newnode, parent) + newnode.elts = [self.visit(child, newnode) for child in node.elts] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_listcomp(self, node, parent): + """visit a ListComp node by returning a fresh instance of it""" + newnode = new.ListComp() + _lineno_parent(node, newnode, parent) + newnode.elt = self.visit(node.elt, newnode) + newnode.generators = [self.visit(child, newnode) + for child in node.generators] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_module(self, node, modname, package): + """visit a Module node by returning a fresh instance of it""" + newnode = new.Module(modname, None) + newnode.package = package + _lineno_parent(node, newnode, parent=None) + _init_set_doc(node, newnode) + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_name(self, node, parent): + """visit a Name node by returning a fresh instance of it""" + # True and False can be assigned to something in py2x, so we have to + # check first the asscontext + if self.asscontext == "Del": + newnode = new.DelName() + elif self.asscontext is not None: # Ass + assert self.asscontext == "Ass" + newnode = new.AssName() + elif node.id in CONST_NAME_TRANSFORMS: + newnode = new.Const(CONST_NAME_TRANSFORMS[node.id]) + _set_infos(node, newnode, parent) + return newnode + else: + newnode = new.Name() + _lineno_parent(node, newnode, parent) + newnode.name = node.id + # XXX REMOVE me : + if self.asscontext in ('Del', 'Ass'): # 'Aug' ?? + self._save_assignment(newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_bytes(self, node, parent): + """visit a Bytes node by returning a fresh instance of Const""" + newnode = new.Const(node.s) + _set_infos(node, newnode, parent) + return newnode + + def visit_num(self, node, parent): + """visit a Num node by returning a fresh instance of Const""" + newnode = new.Const(node.n) + _set_infos(node, newnode, parent) + return newnode def visit_pass(self, node, parent): """visit a Pass node by returning a fresh instance of it""" - newnode = nodes.Pass() - self._set_infos(node, newnode, parent) + newnode = new.Pass() + _set_infos(node, newnode, parent) return newnode - def _save_assignment(self, node, name=None): - """save assignement situation since node.parent is not available yet""" - if self._global_names and node.name in self._global_names[-1]: - node.root().set_local(node.name, node) - else: - node.parent.set_local(node.name, node) + def visit_str(self, node, parent): + """visit a Str node by returning a fresh instance of Const""" + newnode = new.Const(node.s) + _set_infos(node, newnode, parent) + return newnode - def delayed_assattr(self, node): - """visit a AssAttr node -> add name to locals, handle members - definition - """ - try: - frame = node.frame() - for infered in node.expr.infer(): - if infered is YES: - continue - try: - if infered.__class__ is Instance: - infered = infered._proxied - iattrs = infered.instance_attrs - elif isinstance(infered, Instance): - # Const, Tuple, ... we may be wrong, may be not, but - # anyway we don't want to pollute builtin's namespace - continue - else: - iattrs = infered.locals - except AttributeError: - # XXX log error - #import traceback - #traceback.print_exc() - continue - values = iattrs.setdefault(node.attrname, []) - if node in values: - continue - # get assign in __init__ first XXX useful ? - if frame.name == '__init__' and values and not \ - values[0].frame().name == '__init__': - values.insert(0, node) - else: - values.append(node) - except InferenceError: - pass + def visit_print(self, node, parent): + """visit a Print node by returning a fresh instance of it""" + newnode = new.Print() + _lineno_parent(node, newnode, parent) + newnode.nl = node.nl + if node.dest is not None: + newnode.dest = self.visit(node.dest, newnode) + newnode.values = [self.visit(child, newnode) for child in node.values] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_raise(self, node, parent): + """visit a Raise node by returning a fresh instance of it""" + newnode = new.Raise() + _lineno_parent(node, newnode, parent) + if node.type is not None: + newnode.exc = self.visit(node.type, newnode) + if node.inst is not None: + newnode.inst = self.visit(node.inst, newnode) + if node.tback is not None: + newnode.tback = self.visit(node.tback, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_return(self, node, parent): + """visit a Return node by returning a fresh instance of it""" + newnode = new.Return() + _lineno_parent(node, newnode, parent) + if node.value is not None: + newnode.value = self.visit(node.value, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_set(self, node, parent): + """visit a Tuple node by returning a fresh instance of it""" + newnode = new.Set() + _lineno_parent(node, newnode, parent) + newnode.elts = [self.visit(child, newnode) for child in node.elts] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_setcomp(self, node, parent): + """visit a SetComp node by returning a fresh instance of it""" + newnode = new.SetComp() + _lineno_parent(node, newnode, parent) + newnode.elt = self.visit(node.elt, newnode) + newnode.generators = [self.visit(child, newnode) + for child in node.generators] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_slice(self, node, parent): + """visit a Slice node by returning a fresh instance of it""" + newnode = new.Slice() + _lineno_parent(node, newnode, parent) + if node.lower is not None: + newnode.lower = self.visit(node.lower, newnode) + if node.upper is not None: + newnode.upper = self.visit(node.upper, newnode) + if node.step is not None: + newnode.step = self.visit(node.step, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_subscript(self, node, parent): + """visit a Subscript node by returning a fresh instance of it""" + newnode = new.Subscript() + _lineno_parent(node, newnode, parent) + subcontext, self.asscontext = self.asscontext, None + newnode.value = self.visit(node.value, newnode) + newnode.slice = self.visit(node.slice, newnode) + self.asscontext = subcontext + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_tryexcept(self, node, parent): + """visit a TryExcept node by returning a fresh instance of it""" + newnode = new.TryExcept() + _lineno_parent(node, newnode, parent) + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.handlers = [self.visit(child, newnode) for child in node.handlers] + newnode.orelse = [self.visit(child, newnode) for child in node.orelse] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_tryfinally(self, node, parent): + """visit a TryFinally node by returning a fresh instance of it""" + newnode = new.TryFinally() + _lineno_parent(node, newnode, parent) + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.finalbody = [self.visit(n, newnode) for n in node.finalbody] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_tuple(self, node, parent): + """visit a Tuple node by returning a fresh instance of it""" + newnode = new.Tuple() + _lineno_parent(node, newnode, parent) + newnode.elts = [self.visit(child, newnode) for child in node.elts] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_unaryop(self, node, parent): + """visit a UnaryOp node by returning a fresh instance of it""" + newnode = new.UnaryOp() + _lineno_parent(node, newnode, parent) + newnode.operand = self.visit(node.operand, newnode) + newnode.op = _UNARY_OP_CLASSES[node.op.__class__] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_while(self, node, parent): + """visit a While node by returning a fresh instance of it""" + newnode = new.While() + _lineno_parent(node, newnode, parent) + newnode.test = self.visit(node.test, newnode) + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.orelse = [self.visit(child, newnode) for child in node.orelse] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_with(self, node, parent): + """visit a With node by returning a fresh instance of it""" + newnode = new.With() + _lineno_parent(node, newnode, parent) + newnode.expr = self.visit(node.context_expr, newnode) + self.asscontext = "Ass" + if node.optional_vars is not None: + newnode.vars = self.visit(node.optional_vars, newnode) + self.asscontext = None + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_yield(self, node, parent): + """visit a Yield node by returning a fresh instance of it""" + newnode = new.Yield() + _lineno_parent(node, newnode, parent) + if node.value is not None: + newnode.value = self.visit(node.value, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + +class TreeRebuilder3k(TreeRebuilder): + """extend and overwrite TreeRebuilder for python3k""" + + def visit_arg(self, node, parent): + """visit a arg node by returning a fresh AssName instance""" + # the <arg> node is coming from py>=3.0, but we use AssName in py2.x + # XXX or we should instead introduce a Arg node in astng ? + return self.visit_assname(node, parent, node.arg) + + def visit_excepthandler(self, node, parent): + """visit an ExceptHandler node by returning a fresh instance of it""" + newnode = new.ExceptHandler() + _lineno_parent(node, newnode, parent) + if node.type is not None: + newnode.type = self.visit(node.type, newnode) + if node.name is not None: + newnode.name = self.visit_assname(node, newnode, node.name) + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_nonlocal(self, node, parent): + """visit a Nonlocal node and return a new instance of it""" + newnode = new.Nonlocal(node.names) + _set_infos(node, newnode, parent) + return newnode + + def visit_raise(self, node, parent): + """visit a Raise node by returning a fresh instance of it""" + newnode = new.Raise() + _lineno_parent(node, newnode, parent) + # no traceback; anyway it is not used in Pylint + if node.exc is not None: + newnode.exc = self.visit(node.exc, newnode) + if node.cause is not None: + newnode.cause = self.visit(node.cause, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + def visit_starred(self, node, parent): + """visit a Starred node and return a new instance of it""" + newnode = new.Starred() + _lineno_parent(node, newnode, parent) + newnode.value = self.visit(node.value, newnode) + newnode.set_line_info(newnode.last_child()) + return newnode + + +if sys.version_info >= (3, 0): + TreeRebuilder = TreeRebuilder3k diff --git a/scoped_nodes.py b/scoped_nodes.py index b5acd364..42f6248a 100644 --- a/scoped_nodes.py +++ b/scoped_nodes.py @@ -20,9 +20,7 @@ """This module contains the classes for "scoped" node, i.e. which are opening a new local scope in the language definition : Module, Class, Function (and Lambda, GenExpr, DictComp and SetComp to some extent). - """ -from __future__ import generators __doctype__ = "restructuredtext en" @@ -32,15 +30,16 @@ from itertools import chain from logilab.common.compat import builtins from logilab.common.decorators import cached -from logilab.astng import NotFoundError, NoDefault, \ +from logilab.astng.exceptions import NotFoundError, NoDefault, \ ASTNGBuildingException, InferenceError from logilab.astng.node_classes import Const, DelName, DelAttr, \ Dict, From, List, Name, Pass, Raise, Return, Tuple, Yield, \ are_exclusive, LookupMixIn, const_factory as cf, unpack_infer -from logilab.astng.bases import NodeNG, BaseClass, InferenceContext, Instance,\ +from logilab.astng.bases import NodeNG, InferenceContext, Instance,\ YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, copy_context, \ BUILTINS_NAME -from logilab.astng.mixins import StmtMixIn, FilterStmtsMixin +from logilab.astng.mixins import FilterStmtsMixin +from logilab.astng.bases import Statement from logilab.astng.manager import ASTNGManager @@ -242,13 +241,6 @@ class Module(LocalsDictNodeNG): self.locals = self.globals = {} self.body = [] - # Module is not a Statement node but needs the replace method (see StmtMixIn) - def replace(self, child, newchild): - sequence = self.child_sequence(child) - newchild.parent = self - child.parent = None - sequence[sequence.index(child)] = newchild - def block_range(self, lineno): """return block line numbers. @@ -271,25 +263,22 @@ class Module(LocalsDictNodeNG): return 'Module' def getattr(self, name, context=None): - if not name in self.special_attributes: - try: - return self.locals[name] - except KeyError: - pass - else: + if name in self.special_attributes: if name == '__file__': return [cf(self.file)] + self.locals.get(name, []) - if name == '__path__': - if self.package: - return [List()] + self.locals.get(name, []) + if name == '__path__' and self.package: + return [List()] + self.locals.get(name, []) return std_special_attributes(self, name) + if name in self.locals: + return self.locals[name] if self.package: try: return [self.import_module(name, relative_only=True)] - except (KeyboardInterrupt, SystemExit): - raise - except: - pass + except ASTNGBuildingException: + raise NotFoundError(name) + except Exception:# XXX pylint tests never pass here; do we need it? + import traceback + traceback.print_exc() raise NotFoundError(name) getattr = remove_nodes(getattr, DelName) @@ -324,11 +313,14 @@ class Module(LocalsDictNodeNG): """module has no sibling""" return - def absolute_import_activated(self): - for stmt in self.locals.get('absolute_import', ()): - if isinstance(stmt, From) and stmt.modname == '__future__': - return True - return False + if sys.version_info < (2, 7): + def absolute_import_activated(self): + for stmt in self.locals.get('absolute_import', ()): + if isinstance(stmt, From) and stmt.modname == '__future__': + return True + return False + else: + absolute_import_activated = lambda self: True def import_module(self, modname, relative_only=False, level=None): """import the given module considering self as context""" @@ -349,15 +341,15 @@ class Module(LocalsDictNodeNG): """ # XXX this returns non sens when called on an absolute import # like 'pylint.checkers.logilab.astng.utils' + # XXX doesn't return absolute name if self.name isn't absolute name if level: - parts = self.name.split('.') if self.package: - parts.append('__init__') - package_name = '.'.join(parts[:-level]) + level = level - 1 + package_name = self.name.rsplit('.', level)[0] elif self.package: package_name = self.name else: - package_name = '.'.join(self.name.split('.')[:-1]) + package_name = self.name.rsplit('.', 1)[0] if package_name: if not modname: return package_name @@ -511,7 +503,7 @@ class Lambda(LocalsDictNodeNG, FilterStmtsMixin): frame = self return frame._scope_lookup(node, name, offset) -class Function(StmtMixIn, Lambda): +class Function(Statement, Lambda): _astng_fields = ('decorators', 'args', 'body') special_attributes = set(('__name__', '__doc__', '__dict__')) @@ -527,6 +519,8 @@ class Function(StmtMixIn, Lambda): self.decorators = None self.name = name self.doc = doc + self.extra_decorators = [] + self.instance_attrs = {} def set_line_info(self, lastchild): self.fromlineno = self.lineno @@ -549,6 +543,8 @@ class Function(StmtMixIn, Lambda): """ if name == '__module__': return [cf(self.root().qname())] + if name in self.instance_attrs: + return self.instance_attrs[name] return std_special_attributes(self, name, False) def is_method(self): @@ -563,7 +559,7 @@ class Function(StmtMixIn, Lambda): decoratornodes = [] if self.decorators is not None: decoratornodes += self.decorators.nodes - decoratornodes += getattr(self, 'extra_decorators', []) + decoratornodes += self.extra_decorators for decnode in decoratornodes: for infnode in decnode.infer(): result.add(infnode.qname()) @@ -666,7 +662,7 @@ def _iface_hdlr(iface_node): return True -class Class(StmtMixIn, LocalsDictNodeNG, FilterStmtsMixin): +class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): # some of the attributes below are set by the builder module or # by a raw factories @@ -675,10 +671,8 @@ class Class(StmtMixIn, LocalsDictNodeNG, FilterStmtsMixin): _astng_fields = ('decorators', 'bases', 'body') # name decorators = None - instance_attrs = None special_attributes = set(('__name__', '__doc__', '__dict__', '__module__', '__bases__', '__mro__', '__subclasses__')) - blockstart_tolineno = None _type = None @@ -859,6 +853,9 @@ class Class(StmtMixIn, LocalsDictNodeNG, FilterStmtsMixin): if name in self.special_attributes: if name == '__module__': return [cf(self.root().qname())] + values + # FIXME : what is expected by passing the list of ancestors to cf: + # you can just do [cf(tuple())] + values without breaking any test + # this is ticket http://www.logilab.org/ticket/52785 if name == '__bases__': return [cf(tuple(self.ancestors(recurs=False, context=context)))] + values # XXX need proper meta class handling + MRO implementation diff --git a/test/data/module2.py b/test/data/module2.py index c8eba5e6..2b729c0d 100644 --- a/test/data/module2.py +++ b/test/data/module2.py @@ -81,6 +81,7 @@ exec 'c = 3' in {}, {} def raise_string(a=2, *args, **kwargs): raise Exception, 'yo' yield 'coucou' + yield a = (b) + (2) c = (b) * (2) c = (b) / (2) diff --git a/test/unittest_builder.py b/test/unittest_builder.py index cbb017a7..54a8dc58 100644 --- a/test/unittest_builder.py +++ b/test/unittest_builder.py @@ -43,8 +43,10 @@ from logilab.astng.nodes import Module from logilab.astng.bases import YES, BUILTINS_NAME from logilab.astng.as_string import as_string from logilab.astng.manager import ASTNGManager + MANAGER = ASTNGManager() + from unittest_inference import get_name_node import data @@ -114,11 +116,15 @@ class FromToLineNoTC(TestCase): self.assertIsInstance(function, nodes.Function) self.assertEqual(function.fromlineno, 15) self.assertEqual(function.tolineno, 18) - self.assertEqual(function.blockstart_tolineno, 17) return_ = function.body[0] self.assertIsInstance(return_, nodes.Return) self.assertEqual(return_.fromlineno, 18) self.assertEqual(return_.tolineno, 18) + if sys.version_info < (3, 0): + self.assertEqual(function.blockstart_tolineno, 17) + else: + self.skipTest('FIXME http://bugs.python.org/issue10445 ' + '(no line number on function args)') def test_decorated_function_lineno(self): astng = builder.ASTNGBuilder().string_build(''' @@ -130,9 +136,13 @@ def function( function = astng['function'] self.assertEqual(function.fromlineno, 3) # XXX discussable, but that's what is expected by pylint right now self.assertEqual(function.tolineno, 5) - self.assertEqual(function.blockstart_tolineno, 4) self.assertEqual(function.decorators.fromlineno, 2) self.assertEqual(function.decorators.tolineno, 2) + if sys.version_info < (3, 0): + self.assertEqual(function.blockstart_tolineno, 4) + else: + self.skipTest('FIXME http://bugs.python.org/issue10445 ' + '(no line number on function args)') def test_class_lineno(self): @@ -294,8 +304,12 @@ class BuilderTC(TestCase): self.assertIsInstance(builtin_astng['None'], nodes.Const) self.assertIsInstance(builtin_astng['True'], nodes.Const) self.assertIsInstance(builtin_astng['False'], nodes.Const) - self.assertIsInstance(builtin_astng['Exception'], nodes.From) - self.assertIsInstance(builtin_astng['NotImplementedError'], nodes.From) + if sys.version_info < (3, 0): + self.assertIsInstance(builtin_astng['Exception'], nodes.From) + self.assertIsInstance(builtin_astng['NotImplementedError'], nodes.From) + else: + self.assertIsInstance(builtin_astng['Exception'], nodes.Class) + self.assertIsInstance(builtin_astng['NotImplementedError'], nodes.Class) def test_inspect_build1(self): time_astng = MANAGER.astng_from_module_name('time') @@ -315,7 +329,7 @@ class BuilderTC(TestCase): #dt_astng.getattr('DateTimeType') def test_inspect_build3(self): - unittest_astng = self.builder.inspect_build(unittest) + self.builder.inspect_build(unittest) def test_inspect_build_instance(self): """test astng tree build from a living object""" @@ -479,8 +493,7 @@ class FileBuildTC(TestCase): keys = sorted(_locals.keys()) should = ['MY_DICT', 'YO', 'YOUPI', '__revision__', 'global_access','modutils', 'four_args', - 'os', 'redirect', 'spawn', 'LocalsVisitor', - 'ASTWalker', 'ASTVisitor'] + 'os', 'redirect', 'spawn', 'LocalsVisitor', 'ASTWalker'] should.sort() self.assertEqual(keys, should) @@ -671,5 +684,44 @@ def func(): self.assertEqual(chain.value, 'None') +if sys.version_info < (3, 0): + guess_encoding = builder._guess_encoding + + class TestGuessEncoding(TestCase): + + def testEmacs(self): + e = guess_encoding('# -*- coding: UTF-8 -*-') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding('# -*- coding:UTF-8 -*-') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding(''' + ### -*- coding: ISO-8859-1 -*- + ''') + self.failUnlessEqual(e, 'ISO-8859-1') + e = guess_encoding(''' + + ### -*- coding: ISO-8859-1 -*- + ''') + self.failUnlessEqual(e, None) + + def testVim(self): + e = guess_encoding('# vim:fileencoding=UTF-8') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding(''' + ### vim:fileencoding=ISO-8859-1 + ''') + self.failUnlessEqual(e, 'ISO-8859-1') + e = guess_encoding(''' + + ### vim:fileencoding= ISO-8859-1 + ''') + self.failUnlessEqual(e, None) + + def testUTF8(self): + e = guess_encoding('\xef\xbb\xbf any UTF-8 data') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding(' any UTF-8 data \xef\xbb\xbf') + self.failUnlessEqual(e, None) + if __name__ == '__main__': unittest_main() diff --git a/test/unittest_inference.py b/test/unittest_inference.py index c10660d9..cf970f76 100644 --- a/test/unittest_inference.py +++ b/test/unittest_inference.py @@ -22,7 +22,8 @@ import sys from StringIO import StringIO from logilab.common.testlib import TestCase, unittest_main -from logilab.astng import InferenceError, builder, nodes, inference +from logilab.astng import InferenceError, builder, nodes +from logilab.astng.inference import infer_end as inference_infer_end from logilab.astng.bases import YES, Instance, BoundMethod, UnboundMethod,\ path_wrapper, BUILTINS_NAME @@ -40,7 +41,7 @@ class InferenceUtilsTC(TestCase): def infer_default(self, *args): raise InferenceError infer_default = path_wrapper(infer_default) - infer_end = path_wrapper(inference.infer_end) + infer_end = path_wrapper(inference_infer_end) self.failUnlessRaises(InferenceError, infer_default(1).next) self.failUnlessEqual(infer_end(1).next(), 1) @@ -1099,5 +1100,22 @@ Z = test() self.assertIsInstance(infered[0]._proxied, nodes.Class) self.assertEqual(infered[0]._proxied.name, 'list') + def test__new__(self): + code = ''' +class NewTest(object): + "doc" + def __new__(cls, arg): + self = object.__new__(cls) + self.arg = arg + return self + +n = NewTest() + ''' + astng = builder.string_build(code, __name__, __file__) + self.assertRaises(InferenceError, list, astng['NewTest'].igetattr('arg')) + n = astng['n'].infer().next() + infered = list(n.igetattr('arg')) + self.assertEqual(len(infered), 1, infered) + if __name__ == '__main__': unittest_main() diff --git a/test/unittest_inspector.py b/test/unittest_inspector.py index c149343d..12d8368e 100644 --- a/test/unittest_inspector.py +++ b/test/unittest_inspector.py @@ -43,7 +43,7 @@ from os.path import join, abspath, dirname from logilab.astng import nodes, inspector from logilab.astng.bases import Instance, YES -from logilab.astng.manager import ASTNGManager +from logilab.astng.manager import ASTNGManager, _silent_no_wrap MANAGER = ASTNGManager() def astng_wrapper(func, modname): @@ -100,7 +100,7 @@ class LinkerTC(unittest.TestCase): class LinkerTC2(LinkerTC): def setUp(self): - self.project = MANAGER.from_directory(DATA2) + self.project = MANAGER.project_from_files([DATA2], func_wrapper=_silent_no_wrap) self.linker = inspector.Linker(self.project) self.linker.visit(self.project) diff --git a/test/unittest_lookup.py b/test/unittest_lookup.py index e0d66904..388c2b2c 100644 --- a/test/unittest_lookup.py +++ b/test/unittest_lookup.py @@ -232,7 +232,19 @@ var var = astng.body[1].value self.assertRaises(UnresolvableName, var.infered) - + def test_generator_attributes(self): + tree = builder.string_build(""" +def count(): + "ntesxt" + yield 0 + +iterer = count() +num = iterer.next() + """) + next = tree.body[2].value.func # Getattr + gener = next.expr.infered()[0] # Genrator + # XXX gener._proxied is a Function which has no instance_attr + self.assertRaises(AttributeError, gener.getattr, 'next') def test_explicit___name__(self): code = ''' diff --git a/test/unittest_manager.py b/test/unittest_manager.py index 6d9388fa..f505c7af 100644 --- a/test/unittest_manager.py +++ b/test/unittest_manager.py @@ -17,25 +17,28 @@ # # You should have received a copy of the GNU Lesser General Public License along # with logilab-astng. If not, see <http://www.gnu.org/licenses/>. -import unittest +from logilab.common.testlib import TestCase, unittest_main + import sys from os.path import join, abspath, dirname -from logilab.astng.manager import ASTNGManager +from logilab.astng.manager import ASTNGManager, _silent_no_wrap from logilab.astng.bases import BUILTINS_NAME DATA = join(dirname(abspath(__file__)), 'data') -class ASTNGManagerTC(unittest.TestCase): +class ASTNGManagerTC(TestCase): def setUp(self): self.manager = ASTNGManager() - + self.manager.astng_cache.clear() + def test_astng_from_module(self): + import unittest astng = self.manager.astng_from_module(unittest) self.assertEqual(astng.pure_python, True) import time astng = self.manager.astng_from_module(time) self.assertEqual(astng.pure_python, False) - + def test_astng_from_class(self): astng = self.manager.astng_from_class(int) self.assertEqual(astng.name, 'int') @@ -45,59 +48,49 @@ class ASTNGManagerTC(unittest.TestCase): self.assertEqual(astng.name, 'object') self.assertEqual(astng.parent.frame().name, BUILTINS_NAME) self.failUnless('__setattr__' in astng) - + def _test_astng_from_zip(self, archive): origpath = sys.path[:] sys.modules.pop('mypypa', None) - sys.path.insert(0, join(DATA, archive)) + archive_path = join(DATA, archive) + sys.path.insert(0, archive_path) try: module = self.manager.astng_from_module_name('mypypa') self.assertEqual(module.name, 'mypypa') self.failUnless(module.file.endswith('%s/mypypa' % archive), module.file) finally: - sys.path = origpath # remove the module, else after importing egg, we don't get the zip - del self.manager._cache['mypypa'] - del self.manager._mod_file_cache[('mypypa', None)] + if 'mypypa' in self.manager.astng_cache: + del self.manager.astng_cache['mypypa'] + del self.manager._mod_file_cache[('mypypa', None)] + if archive_path in sys.path_importer_cache: + del sys.path_importer_cache[archive_path] + sys.path = origpath def test_astng_from_module_name_egg(self): self._test_astng_from_zip('MyPyPa-0.1.0-py2.5.egg') def test_astng_from_module_name_zip(self): - self._test_astng_from_zip('MyPyPa-0.1.0-py2.5.zip') - + self._test_astng_from_zip('MyPyPa-0.1.0-py2.5.zip') + def test_from_directory(self): - obj = self.manager.from_directory(DATA) + obj = self.manager.project_from_files([DATA], _silent_no_wrap, 'data') self.assertEqual(obj.name, 'data') - self.assertEqual(obj.path, DATA) - - def test_package_node(self): - obj = self.manager.from_directory(DATA) - expected_short = ['SSL1', '__init__', 'all', 'appl', 'format', 'module', 'module2', - 'noendingnewline', 'nonregr', 'notall'] - expected_long = ['SSL1', 'data', 'data.all', 'appl', 'data.format', 'data.module', - 'data.module2', 'data.noendingnewline', 'data.nonregr', - 'data.notall'] - self.assertEqual(obj.keys(), expected_short) - self.assertEqual([m.name for m in obj.values()], expected_long) - self.assertEqual([m for m in list(obj)], expected_short) - self.assertEqual([(name, m.name) for name, m in obj.items()], - zip(expected_short, expected_long)) - self.assertEqual([(name, m.name) for name, m in obj.items()], - zip(expected_short, expected_long)) - - self.assertEqual('module' in obj, True) - self.assertTrue(obj.get('module')) - self.assertEqual(obj.get('module').name, 'data.module') - self.assertEqual(obj['module'].name, 'data.module') - self.assertEqual(obj.get('whatever'), None) - self.assertEqual(obj.fullname(), 'data') - # FIXME: test fullname on a subpackage - - - -class BorgASTNGManagerTC(unittest.TestCase): + self.assertEqual(obj.path, join(DATA, '__init__.py')) + + def test_project_node(self): + obj = self.manager.project_from_files([DATA], _silent_no_wrap, 'data') + expected = set(['SSL1', '__init__', 'all', 'appl', 'format', 'module', + 'module2', 'noendingnewline', 'nonregr', 'notall']) + expected = ['data', 'data.SSL1', 'data.SSL1.Connection1', 'data.all', + 'data.appl', 'data.appl.myConnection', 'data.format', + 'data.module', 'data.module2', 'data.noendingnewline', + 'data.nonregr', 'data.notall'] + self.assertListEqual(sorted(k for k in obj.keys()), expected) + + +class BorgASTNGManagerTC(TestCase): def test_borg(self): """test that the ASTNGManager is really a borg, i.e. that two different @@ -111,6 +104,6 @@ class BorgASTNGManagerTC(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest_main() + - diff --git a/test/unittest_nodes.py b/test/unittest_nodes.py index 6a9e2dde..8af5f790 100644 --- a/test/unittest_nodes.py +++ b/test/unittest_nodes.py @@ -34,7 +34,8 @@ import sys from logilab.common import testlib -from logilab.astng import builder, nodes, NotFoundError +from logilab.astng.exceptions import ASTNGBuildingException, NotFoundError +from logilab.astng import builder, nodes from logilab.astng.as_string import as_string from data import module as test_module @@ -45,6 +46,56 @@ DATA = join(dirname(abspath(__file__)), 'data') abuilder = builder.ASTNGBuilder() +class AsString(testlib.TestCase): + + def test_varargs_kwargs_as_string(self): + ast = abuilder.string_build( 'raise_string(*args, **kwargs)').body[0] + self.assertEqual(as_string(ast), 'raise_string(*args, **kwargs)') + + def test_module_as_string(self): + """check as_string on a whole module prepared to be returned identically + """ + data = open(join(DATA, 'module.py')).read() + self.assertMultiLineEqual(as_string(MODULE), data) + data = open(join(DATA, 'module2.py')).read() + self.assertMultiLineEqual(as_string(MODULE2), data) + + def test_2_7_as_string(self): + """check as_string for python syntax >= 2.7""" + if sys.version_info < (2, 7): + self.skipTest("test python >= 2.7 specific") + code = '''one_two = {1, 2} +b = {v: k for (k, v) in enumerate('string')} +cdd = {k for k in b}\n\n''' + ast = abuilder.string_build(code) + self.assertMultiLineEqual(as_string(ast), code) + + def test_3k_as_string(self): + """check as_string for python 3k syntax""" + if sys.version_info < (3, 0): + self.skipTest("test python 3k specific") + code = '''print() + +def function(var): + nonlocal counter + try: + hello + except NameError as nexc: + (*hell, o) = b'hello' + raise AttributeError from nexc +\n''' + # TODO : annotations and keywords for class definition are not yet implemented + _todo = ''' +def function(var:int): + nonlocal counter + +class Language(metaclass=Natural): + """natural language""" + ''' + ast = abuilder.string_build(code) + self.assertEqual(as_string(ast), code) + + class _NodeTC(testlib.TestCase): """test transformation of If Node""" CODE = None @@ -214,59 +265,18 @@ class ImportNodeTC(testlib.TestCase): self.assertRaises(NotFoundError, imp_.real_name, 'data') def test_as_string(self): - ast = MODULE['modutils'] self.assertEqual(as_string(ast), "from logilab.common import modutils") ast = MODULE['spawn'] self.assertEqual(as_string(ast), "from logilab.common.shellutils import Execute as spawn") ast = MODULE['os'] self.assertEqual(as_string(ast), "import os.path") - ast = abuilder.string_build( 'raise_string(*args, **kwargs)').body[0] - self.assertEqual(as_string(ast), 'raise_string(*args, **kwargs)') - - def test_module_as_string(self): - """check as_string on a whole module prepared to be returned identically - """ - data = open(join(DATA, 'module.py')).read() - self.assertMultiLineEqual(as_string(MODULE), data) - data = open(join(DATA, 'module2.py')).read() - self.assertMultiLineEqual(as_string(MODULE2), data) - - def test_2_7_as_string(self): - """check as_string for python syntax >= 2.7""" - if sys.version_info < (2, 7): - self.skipTest("test python >= 2.7 specific") - code = '''one_two = {1, 2} -b = {v: k for (k, v) in enumerate('string')} -cdd = {k for k in b}\n\n''' + code = """from . import here +from .. import door +from .store import bread +from ..cave import wine\n\n""" ast = abuilder.string_build(code) - self.assertMultiLineEqual(as_string(ast), code) - - def test_3k_as_string(self): - """check as_string for python 3k syntax""" - if sys.version_info < (3, 0): - self.skipTest("test python 3k specific") - code = '''print() - -def function(var): - nonlocal counter - try: - hello - except NameError as nexc: - (*hell, o) = b'hello' - raise AttributeError from nexc -\n''' - # TODO : annotations and keywords for class definition are not yet implemented - _todo = ''' -def function(var:int): - nonlocal counter - -class Language(metaclass=Natural): - """natural language""" - ''' - ast = abuilder.string_build(code) - self.assertEqual(as_string(ast), code) - + self.assertMultiLineEqual(ast.as_string(), code) class CmpNodeTC(testlib.TestCase): def test_as_string(self): @@ -306,6 +316,27 @@ class ConstNodeTC(testlib.TestCase): self._test(u'a') +class NameNodeTC(testlib.TestCase): + def test_assign_to_True(self): + """test that True and False assignements don't crash""" + code = """True = False +def hello(False): + pass +del True + """ + if sys.version_info >= (3, 0): + self.assertRaises(SyntaxError,#might become ASTNGBuildingException + abuilder.string_build, code) + else: + ast = abuilder.string_build(code) + ass_true = ast['True'] + self.assertIsInstance(ass_true, nodes.AssName) + self.assertEqual(ass_true.name, "True") + del_true = ast.body[2].targets[0] + self.assertIsInstance(del_true, nodes.DelName) + self.assertEqual(del_true.name, "True") + + class ArgumentsNodeTC(testlib.TestCase): def test_linenumbering(self): ast = abuilder.string_build(''' @@ -314,12 +345,16 @@ def func(a, x = lambda x: None ''') self.assertEqual(ast['func'].args.fromlineno, 2) - self.assertEqual(ast['func'].args.tolineno, 3) self.failIf(ast['func'].args.is_statement) xlambda = ast['x'].infer().next() self.assertEqual(xlambda.args.fromlineno, 4) self.assertEqual(xlambda.args.tolineno, 4) self.failIf(xlambda.args.is_statement) + if sys.version_info < (3, 0): + self.assertEqual(ast['func'].args.tolineno, 3) + else: + self.skipTest('FIXME http://bugs.python.org/issue10445 ' + '(no line number on function args)') class SliceNodeTC(testlib.TestCase): diff --git a/test/unittest_regrtest.py b/test/unittest_regrtest.py index 3a169333..dd6d3b66 100644 --- a/test/unittest_regrtest.py +++ b/test/unittest_regrtest.py @@ -21,17 +21,16 @@ from logilab.common.testlib import unittest_main, TestCase from logilab.astng import ResolveError, MANAGER, Instance, nodes, YES, InferenceError -from logilab.astng.builder import ASTNGBuilder, build_module +from logilab.astng.builder import ASTNGBuilder +from logilab.astng.raw_building import build_module from logilab.astng.manager import ASTNGManager import sys from os.path import join, abspath, dirname class NonRegressionTC(TestCase): - def setUp(self): - sys.path.insert(0, join(dirname(abspath(__file__)), 'regrtest_data')) def tearDown(self): @@ -42,7 +41,7 @@ class NonRegressionTC(TestCase): # avoid caching into the ASTNGManager borg since we get problems # with other tests : manager.__dict__ = {} - manager._cache = {} + manager.astng_cache = {} manager._mod_file_cache = {} return manager @@ -61,7 +60,7 @@ class NonRegressionTC(TestCase): def test_package_sidepackage(self): manager = self.brainless_manager() - assert 'package.sidepackage' not in MANAGER._cache + assert 'package.sidepackage' not in MANAGER.astng_cache package = manager.astng_from_module_name('absimp') self.assertIsInstance(package, nodes.Module) self.assertTrue(package.package) diff --git a/test/unittest_scoped_nodes.py b/test/unittest_scoped_nodes.py index f1502ecc..382a0f57 100644 --- a/test/unittest_scoped_nodes.py +++ b/test/unittest_scoped_nodes.py @@ -112,6 +112,26 @@ del appli self.assertEqual(len(astng.getattr('appli')), 2, astng.getattr('appli')) + def test_relative_to_absolute_name(self): + mod = nodes.Module('very.multi.module', 'doc') + # package + mod.package = True + modname = mod.relative_to_absolute_name('utils', 1) + self.assertEqual(modname, 'very.multi.module.utils') + modname = mod.relative_to_absolute_name('utils', 2) + self.assertEqual(modname, 'very.multi.utils') + modname = mod.relative_to_absolute_name('utils', 0) + self.assertEqual(modname, 'very.multi.module.utils') + # non package + mod.package = False + modname = mod.relative_to_absolute_name('utils', 0) + self.assertEqual(modname, 'very.multi.utils') + modname = mod.relative_to_absolute_name('utils', 1) + self.assertEqual(modname, 'very.multi.utils') + modname = mod.relative_to_absolute_name('utils', 2) + self.assertEqual(modname, 'very.utils') + + class FunctionNodeTC(TestCase): @@ -262,6 +282,24 @@ a = func() self.assertIsInstance(func_vals[0], nodes.Const) self.assertEqual(func_vals[0].value, None) + def test_func_instance_attr(self): + """test instance attributes for functions""" + data= """ +def test(): + print(test.bar) + +test.bar = 1 +test() + """ + astng = abuilder.string_build(data, 'mod', __file__) + func = astng.body[2].value.func.infered()[0] + self.assertIsInstance(func, nodes.Function) + self.assertEqual(func.name, 'test') + one = func.getattr('bar')[0].infered()[0] + self.assertIsInstance(one, nodes.Const) + self.assertEqual(one.value, 1) + + class ClassNodeTC(TestCase): def test_dict_interface(self): @@ -36,196 +36,7 @@ extract information from it __docformat__ = "restructuredtext en" -from logilab.astng._exceptions import IgnoreChild, ASTNGBuildingException - -class ASTVisitor(object): - """Abstract Base Class for Python AST Visitors. - - Visitors inheriting from ASTVisitors could visit - compiler.ast, _ast or astng trees. - - Not all methods will have to be implemented; - so some methods are just empty interfaces for catching - cases where we don't want to do anything on the - concerned node. - """ - - def visit_arguments(self, node): - """dummy method for visiting an Arguments node""" - - def visit_assattr(self, node): - """dummy method for visiting an AssAttr node""" - - def visit_assert(self, node): - """dummy method for visiting an Assert node""" - - def visit_assign(self, node): - """dummy method for visiting an Assign node""" - - def visit_assname(self, node): - """dummy method for visiting an AssName node""" - - def visit_augassign(self, node): - """dummy method for visiting an AugAssign node""" - - def visit_backquote(self, node): - """dummy method for visiting an Backquote node""" - - def visit_binop(self, node): - """dummy method for visiting an BinOp node""" - - def visit_boolop(self, node): - """dummy method for visiting an BoolOp node""" - - def visit_break(self, node): - """dummy method for visiting an Break node""" - - def visit_callfunc(self, node): - """dummy method for visiting an CallFunc node""" - - def visit_class(self, node): - """dummy method for visiting an Class node""" - - def visit_compare(self, node): - """dummy method for visiting an Compare node""" - - def visit_comprehension(self, node): - """dummy method for visiting an Comprehension node""" - - def visit_const(self, node): - """dummy method for visiting an Const node""" - - def visit_continue(self, node): - """dummy method for visiting an Continue node""" - - def visit_decorators(self, node): - """dummy method for visiting an Decorators node""" - - def visit_delattr(self, node): - """dummy method for visiting an DelAttr node""" - - def visit_delete(self, node): - """dummy method for visiting an Delete node""" - - def visit_delname(self, node): - """dummy method for visiting an DelName node""" - - def visit_dict(self, node): - """dummy method for visiting an Dict node""" - - def visit_dictcomp(self, node): - """dummy method for visiting an DictComp node""" - - def visit_discard(self, node): - """dummy method for visiting an Discard node""" - - def visit_emptynode(self, node): - """dummy method for visiting an EmptyNode node""" - - def visit_excepthandler(self, node): - """dummy method for visiting an ExceptHandler node""" - - def visit_ellipsis(self, node): - """dummy method for visiting an Ellipsis node""" - - def visit_empty(self, node): - """dummy method for visiting an Empty node""" - - def visit_exec(self, node): - """dummy method for visiting an Exec node""" - - def visit_extslice(self, node): - """dummy method for visiting an ExtSlice node""" - - def visit_for(self, node): - """dummy method for visiting an For node""" - - def visit_from(self, node): - """dummy method for visiting an From node""" - - def visit_function(self, node): - """dummy method for visiting an Function node""" - - def visit_genexpr(self, node): - """dummy method for visiting an ListComp node""" - - def visit_getattr(self, node): - """dummy method for visiting an Getattr node""" - - def visit_global(self, node): - """dummy method for visiting an Global node""" - - def visit_if(self, node): - """dummy method for visiting an If node""" - - def visit_ifexp(self, node): - """dummy method for visiting an IfExp node""" - - def visit_import(self, node): - """dummy method for visiting an Import node""" - - def visit_index(self, node): - """dummy method for visiting an Index node""" - - def visit_keyword(self, node): - """dummy method for visiting an Keyword node""" - - def visit_lambda(self, node): - """dummy method for visiting an Lambda node""" - - def visit_list(self, node): - """dummy method for visiting an List node""" - - def visit_listcomp(self, node): - """dummy method for visiting an ListComp node""" - - def visit_module(self, node): - """dummy method for visiting an Module node""" - - def visit_name(self, node): - """dummy method for visiting an Name node""" - - def visit_pass(self, node): - """dummy method for visiting an Pass node""" - - def visit_print(self, node): - """dummy method for visiting an Print node""" - - def visit_raise(self, node): - """dummy method for visiting an Raise node""" - - def visit_return(self, node): - """dummy method for visiting an Return node""" - - def visit_setcomp(self, node): - """dummy method for visiting an SetComp node""" - - def visit_slice(self, node): - """dummy method for visiting an Slice node""" - - def visit_subscript(self, node): - """dummy method for visiting an Subscript node""" - - def visit_tryexcept(self, node): - """dummy method for visiting an TryExcept node""" - - def visit_tryfinally(self, node): - """dummy method for visiting an TryFinally node""" - - def visit_tuple(self, node): - """dummy method for visiting an Tuple node""" - - def visit_unaryop(self, node): - """dummy method for visiting an UnaryOp node""" - - def visit_while(self, node): - """dummy method for visiting an While node""" - - def visit_with(self, node): - """dummy method for visiting an With node""" - - def visit_yield(self, node): - """dummy method for visiting an Yield node""" +from logilab.astng.exceptions import ASTNGBuildingException class ASTWalker: @@ -249,19 +60,11 @@ class ASTWalker: if node in _done: raise AssertionError((id(node), node, node.parent)) _done.add(node) - try: - self.visit(node) - except IgnoreChild: - pass - else: - try: - for child_node in node.get_children(): - self.handler.set_context(node, child_node) - assert child_node is not node - self.walk(child_node, _done) - except AttributeError: - print node.__class__, id(node.__class__) - raise + self.visit(node) + for child_node in node.get_children(): + self.handler.set_context(node, child_node) + assert child_node is not node + self.walk(child_node, _done) self.leave(node) assert node.parent is not node @@ -306,16 +109,11 @@ class LocalsVisitor(ASTWalker): return self._visited[node] = 1 # FIXME: use set ? methods = self.get_callbacks(node) - recurse = 1 if methods[0] is not None: - try: - methods[0](node) - except IgnoreChild: - recurse = 0 - if recurse: - if 'locals' in node.__dict__: # skip Instance and other proxy - for name, local_node in node.items(): - self.visit(local_node) + methods[0](node) + if 'locals' in node.__dict__: # skip Instance and other proxy + for name, local_node in node.items(): + self.visit(local_node) if methods[1] is not None: return methods[1](node) @@ -343,8 +141,10 @@ def _check_children(node): _check_children(child) -# XXX This class don't support 'compiler' for now. -# If we want it, there is a "_native_repr_tree" method in _nodes_compiler.py +from _ast import PyCF_ONLY_AST +def parse(string): + return compile(string, "<string>", 'exec', PyCF_ONLY_AST) + class TreeTester(object): '''A helper class to see _ast tree and compare with astng tree @@ -380,9 +180,6 @@ class TreeTester(object): def build_ast(self): """build the _ast tree from the source code""" - from _ast import PyCF_ONLY_AST - def parse(string): - return compile(string, "<string>", 'exec', PyCF_ONLY_AST) self._ast_node = parse(self.sourcecode) def native_tree_repr(self, node=None, indent=''): @@ -444,7 +241,7 @@ class TreeTester(object): """build astng tree from the _ast tree """ from logilab.astng.builder import ASTNGBuilder - tree = ASTNGBuilder().ast_build(self._ast_node) + tree = ASTNGBuilder().string_build(self.sourcecode) return tree def astng_tree_repr(self, ids=False): @@ -453,5 +250,5 @@ class TreeTester(object): return mod.repr_tree(ids) -__all__ = ('LocalsVisitor', 'ASTWalker', 'ASTVisitor',) +__all__ = ('LocalsVisitor', 'ASTWalker',) |