diff options
-rw-r--r-- | bases.py | 67 | ||||
-rw-r--r-- | mixins.py | 151 | ||||
-rw-r--r-- | node_classes.py | 238 | ||||
-rw-r--r-- | nodes.py | 8 | ||||
-rw-r--r-- | scoped_nodes.py | 176 |
5 files changed, 344 insertions, 296 deletions
@@ -11,8 +11,15 @@ # You should have received a copy of the GNU 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. -""" -Module containing the node classes; it is only used for avoiding circular imports + +"""This module contains base classes and functions for the nodes and some inference +utils. + +:author: Sylvain Thenault +:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE) +:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org +:copyright: 2003-2010 Sylvain Thenault +:contact: mailto:thenault@gmail.com """ from __future__ import generators @@ -580,59 +587,3 @@ def _repr_tree(node, result, indent='', _done=None, ids=False): _repr_tree(value, result, indent, _done, ids) - - -# some small MixIns for extending the node classes ####################### - -# /!\ 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] - - -class BlockRangeMixIn(BaseClass): - """override block range """ - def set_line_info(self, lastchild): - self.fromlineno = self.lineno - self.tolineno = lastchild.tolineno - self.blockstart_tolineno = self._blockstart_toline() - - def _elsed_block_range(self, lineno, orelse, last=None): - """handle block line numbers range for try/finally, for, if and while - statements - """ - if lineno == self.fromlineno: - return lineno, lineno - if orelse: - if lineno >= orelse[0].fromlineno: - return lineno, orelse[-1].tolineno - return lineno, orelse[0].fromlineno - 1 - return lineno, last or self.tolineno - - - diff --git a/mixins.py b/mixins.py new file mode 100644 index 00000000..bed4a447 --- /dev/null +++ b/mixins.py @@ -0,0 +1,151 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU 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. +"""This module contains some mixins for the different nodes. + +:author: Sylvain Thenault +:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE) +:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org +:copyright: 2003-2010 Sylvain Thenault +:contact: mailto:thenault@gmail.com +""" + +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] + + +class BlockRangeMixIn(BaseClass): + """override block range """ + def set_line_info(self, lastchild): + self.fromlineno = self.lineno + self.tolineno = lastchild.tolineno + self.blockstart_tolineno = self._blockstart_toline() + + def _elsed_block_range(self, lineno, orelse, last=None): + """handle block line numbers range for try/finally, for, if and while + statements + """ + if lineno == self.fromlineno: + return lineno, lineno + if orelse: + if lineno >= orelse[0].fromlineno: + return lineno, orelse[-1].tolineno + return lineno, orelse[0].fromlineno - 1 + return lineno, last or self.tolineno + +class FilterStmtsMixin(object): + """Mixin for statement filtering and assignment type""" + + def _get_filtered_stmts(self, _, node, _stmts, mystmt): + """method used in _filter_stmts to get statemtents and trigger break""" + if self.statement() is mystmt: + # original node's statement is the assignment, only keep + # current node (gen exp, list comp) + return [node], True + return _stmts, False + + def ass_type(self): + return self + + +class AssignTypeMixin(object): + + def ass_type(self): + return self + + def _get_filtered_stmts(self, lookup_node, node, _stmts, mystmt): + """method used in filter_stmts""" + if self is mystmt: + return _stmts, True + if self.statement() is mystmt: + # original node's statement is the assignment, only keep + # current node (gen exp, list comp) + return [node], True + return _stmts, False + + +class ParentAssignTypeMixin(AssignTypeMixin): + + def ass_type(self): + return self.parent.ass_type() + + + +class FromImportMixIn(BaseClass, FilterStmtsMixin): + """MixIn for From and Import Nodes""" + + def _infer_name(self, frame, name): + return name + + def do_import_module(node, modname): + """return the ast for a module whose name is <modname> imported by <node> + """ + # handle special case where we are on a package node importing a module + # using the same name as the package, which may end in an infinite loop + # on relative imports + # XXX: no more needed ? + mymodule = node.root() + level = getattr(node, 'level', None) # Import as no level + if mymodule.absolute_modname(modname, level) == mymodule.name: + # FIXME: I don't know what to do here... + raise InferenceError('module importing itself: %s' % modname) + try: + return mymodule.import_module(modname, level=level) + except (ASTNGBuildingException, SyntaxError): + raise InferenceError(modname) + + def real_name(self, asname): + """get name from 'as' name""" + for index in range(len(self.names)): + name, _asname = self.names[index] + if name == '*': + return asname + if not _asname: + name = name.split('.', 1)[0] + _asname = name + if asname == _asname: + return name + raise NotFoundError(asname) + + + diff --git a/node_classes.py b/node_classes.py index 98b4ea76..c49acf28 100644 --- a/node_classes.py +++ b/node_classes.py @@ -1,14 +1,34 @@ +# -*- coding: utf-8 -*- +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU 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. +""" +Module for some node classes. More nodes in scoped_nodes.py + +:author: Sylvain Thenault +:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE) +:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org +:copyright: 2003-2010 Sylvain Thenault +:contact: mailto:thenault@gmail.com + +""" from logilab.common.compat import chain, imap from logilab.astng import (ASTNGBuildingException, InferenceError, NotFoundError, NoDefault) -from logilab.astng.bases import (NodeNG, StmtMixIn, BlockRangeMixIn, - BaseClass, Instance) - -""" -Module for all nodes (except scoped nodes). -""" +from logilab.astng.bases import (NodeNG, BaseClass, Instance, copy_context, + _infer_stmts) +from logilab.astng.mixins import (StmtMixIn, BlockRangeMixIn, FilterStmtsMixin, + AssignTypeMixin, ParentAssignTypeMixin, FromImportMixIn) def unpack_infer(stmt, context=None): """return an iterator on nodes inferred by the given statement if the inferred @@ -75,43 +95,160 @@ def are_exclusive(stmt1, stmt2, exceptions=None): node = node.parent return False -class FilterStmtsMixin(object): - """Mixin for statement filtering and assignment type""" - def _get_filtered_stmts(self, _, node, _stmts, mystmt): - """method used in _filter_stmts to get statemtents and trigger break""" - if self.statement() is mystmt: - # original node's statement is the assignment, only keep - # current node (gen exp, list comp) - return [node], True - return _stmts, False - def ass_type(self): - return self -class AssignTypeMixin(object): +class LookupMixIn(BaseClass): + """Mixin looking up a name in the right scope + """ - def ass_type(self): - return self + def lookup(self, name): + """lookup a variable name - def _get_filtered_stmts(self, lookup_node, node, _stmts, mystmt): - """method used in filter_stmts""" - if self is mystmt: - return _stmts, True - if self.statement() is mystmt: - # original node's statement is the assignment, only keep - # current node (gen exp, list comp) - return [node], True - return _stmts, False + return the scope node and the list of assignments associated to the given + name according to the scope where it has been found (locals, globals or + builtin) + The lookup is starting from self's scope. If self is not a frame itself and + the name is found in the inner frame locals, statements will be filtered + to remove ignorable statements according to self's location + """ + return self.scope().scope_lookup(self, name) -class ParentAssignTypeMixin(AssignTypeMixin): + def ilookup(self, name, context=None): + """infered lookup - def ass_type(self): - return self.parent.ass_type() + 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 + return _infer_stmts(stmts, context, frame) + def _filter_stmts(self, stmts, frame, offset): + """filter statements to remove ignorable statements. + If self is not a frame itself and the name is found in the inner + frame locals, statements will be filtered to remove ignorable + statements according to self's location + """ + # if offset == -1, my actual frame is not the inner frame but its parent + # + # class A(B): pass + # + # we need this to resolve B correctly + if offset == -1: + myframe = self.frame().parent.frame() + else: + myframe = self.frame() + if not myframe is frame or self is frame: + return stmts + mystmt = self.statement() + # line filtering if we are in the same frame + # + # take care node may be missing lineno information (this is the case for + # nodes inserted for living objects) + if myframe is frame and mystmt.fromlineno is not None: + assert mystmt.fromlineno is not None, mystmt + mylineno = mystmt.fromlineno + offset + else: + # disabling lineno filtering + mylineno = 0 + _stmts = [] + _stmt_parents = [] + for node in stmts: + stmt = node.statement() + # line filtering is on and we have reached our location, break + if mylineno > 0 and stmt.fromlineno > mylineno: + break + assert hasattr(node, 'ass_type'), (node, node.scope(), + node.scope().locals) + ass_type = node.ass_type() + + if node.has_base(self): + break + + _stmts, done = ass_type._get_filtered_stmts(self, node, _stmts, mystmt) + if done: + break + + optional_assign = isinstance(ass_type, (For, Comprehension)) + if optional_assign and ass_type.parent_of(self): + # we are inside a loop, loop var assigment is hidding previous + # assigment + _stmts = [node] + _stmt_parents = [stmt.parent] + continue + + # XXX comment various branches below!!! + try: + pindex = _stmt_parents.index(stmt.parent) + except ValueError: + pass + else: + # we got a parent index, this means the currently visited node + # is at the same block level as a previously visited node + if _stmts[pindex].ass_type().parent_of(ass_type): + # both statements are not at the same block level + continue + # if currently visited node is following previously considered + # assignement and both are not exclusive, we can drop the + # previous one. For instance in the following code :: + # + # if a: + # x = 1 + # else: + # x = 2 + # print x + # + # we can't remove neither x = 1 nor x = 2 when looking for 'x' + # of 'print x'; while in the following :: + # + # x = 1 + # x = 2 + # print x + # + # we can remove x = 1 when we see x = 2 + # + # moreover, on loop assignment types, assignment won't + # necessarily be done if the loop has no iteration, so we don't + # want to clear previous assigments if any (hence the test on + # optional_assign) + if not (optional_assign or are_exclusive(_stmts[pindex], node)): + del _stmt_parents[pindex] + del _stmts[pindex] + if isinstance(node, AssName): + if not optional_assign and stmt.parent is mystmt.parent: + _stmts = [] + _stmt_parents = [] + elif isinstance(node, DelName): + _stmts = [] + _stmt_parents = [] + continue + if not are_exclusive(self, node): + _stmts.append(node) + _stmt_parents.append(stmt.parent) + return _stmts + +# Name classes + +class AssName(LookupMixIn, ParentAssignTypeMixin, NodeNG): + """class representing an AssName node""" + + +class DelName(LookupMixIn, ParentAssignTypeMixin, NodeNG): + """class representing a DelName node""" + + +class Name(LookupMixIn, NodeNG): + """class representing a Name node""" + + + + +##################### node classes ######################################## class Arguments(NodeNG, AssignTypeMixin): """class representing an Arguments node""" @@ -251,7 +388,6 @@ class Comprehension(NodeNG): def _get_filtered_stmts(self, lookup_node, node, stmts, mystmt): """method used in filter_stmts""" if self is mystmt: - from logilab.astng.nodes import Name # XXX remove me if isinstance(lookup_node, (Const, Name)): return [lookup_node], True @@ -401,42 +537,6 @@ class For(BlockRangeMixIn, StmtMixIn, AssignTypeMixin, NodeNG): return self.iter.tolineno -class FromImportMixIn(BaseClass, FilterStmtsMixin): - """MixIn for From and Import Nodes""" - - def _infer_name(self, frame, name): - return name - - def do_import_module(node, modname): - """return the ast for a module whose name is <modname> imported by <node> - """ - # handle special case where we are on a package node importing a module - # using the same name as the package, which may end in an infinite loop - # on relative imports - # XXX: no more needed ? - mymodule = node.root() - level = getattr(node, 'level', None) # Import as no level - if mymodule.absolute_modname(modname, level) == mymodule.name: - # FIXME: I don't know what to do here... - raise InferenceError('module importing itself: %s' % modname) - try: - return mymodule.import_module(modname, level=level) - except (ASTNGBuildingException, SyntaxError): - raise InferenceError(modname) - - def real_name(self, asname): - """get name from 'as' name""" - for index in range(len(self.names)): - name, _asname = self.names[index] - if name == '*': - return asname - if not _asname: - name = name.split('.', 1)[0] - _asname = name - if asname == _asname: - return name - raise NotFoundError(asname) - class From(FromImportMixIn, StmtMixIn, NodeNG): """class representing a From node""" @@ -38,13 +38,13 @@ on From and Import : __docformat__ = "restructuredtext en" from logilab.astng.node_classes import (Arguments, AssAttr, Assert, - Assign, AugAssign, Backquote, BinOp, BoolOp, Break, CallFunc, Compare, - Comprehension, Const, Continue, Decorators, DelAttr, Delete, + Assign, AssName, AugAssign, Backquote, BinOp, BoolOp, Break, CallFunc, Compare, + Comprehension, Const, Continue, Decorators, DelAttr, DelName, Delete, Dict, Discard, Ellipsis, EmptyNode, ExceptHandler, Exec, ExtSlice, For, From, Getattr, Global, If, IfExp, Import, Index, Keyword, - List, ListComp, Pass, Print, Raise, Return, Slice, Subscript, + List, ListComp, Name, Pass, Print, Raise, Return, Slice, Subscript, TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, const_factory ) -from logilab.astng.scoped_nodes import AssName, DelName, Name, Module, GenExpr, Lambda, Function, Class +from logilab.astng.scoped_nodes import Module, GenExpr, Lambda, Function, Class # astng fields definition #################################################### diff --git a/scoped_nodes.py b/scoped_nodes.py index c6851bad..2b97bcf8 100644 --- a/scoped_nodes.py +++ b/scoped_nodes.py @@ -10,18 +10,14 @@ # You should have received a copy of the GNU 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. -"""This module extends ast "scoped" node, i.e. which are opening a new -local scope in the language definition : Module, Class, Function (and +"""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 and GenExpr to some extends). -All new methods and attributes added on each class are documented -below. - - :author: Sylvain Thenault -:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE) +:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE) :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org -:copyright: 2003-2009 Sylvain Thenault +:copyright: 2003-2010 Sylvain Thenault :contact: mailto:thenault@gmail.com """ from __future__ import generators @@ -36,13 +32,12 @@ from logilab.common.decorators import cached from logilab.astng import MANAGER, NotFoundError, NoDefault, \ ASTNGBuildingException, InferenceError -from logilab.astng.node_classes import (Const, Comprehension, Dict, - From, For, Import, List, Pass, Raise, Return, Tuple, Yield, DelAttr, - are_exclusive, const_factory as cf, unpack_infer, FilterStmtsMixin, - ParentAssignTypeMixin) -from logilab.astng.bases import (NodeNG, StmtMixIn, BaseClass, YES, - InferenceContext, Instance, Generator, - UnboundMethod, BoundMethod, _infer_stmts, copy_context) +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, YES, InferenceContext, Instance, + Generator, UnboundMethod, BoundMethod, _infer_stmts, copy_context) +from logilab.astng.mixins import (StmtMixIn, FilterStmtsMixin) from logilab.astng.nodes_as_string import as_string @@ -78,143 +73,6 @@ def std_special_attributes(self, name, add_locals=True): raise NotFoundError(name) -# MixIns ----------------------------------------------------- - - - -class LookupMixIn(BaseClass): - """Mixin looking up a name in the right scope - """ - - def lookup(self, name): - """lookup a variable name - - return the scope node and the list of assignments associated to the given - name according to the scope where it has been found (locals, globals or - builtin) - - The lookup is starting from self's scope. If self is not a frame itself and - the name is found in the inner frame locals, statements will be filtered - to remove ignorable statements according to self's location - """ - return self.scope().scope_lookup(self, name) - - def ilookup(self, name, context=None): - """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 - return _infer_stmts(stmts, context, frame) - - def _filter_stmts(self, stmts, frame, offset): - """filter statements to remove ignorable statements. - - If self is not a frame itself and the name is found in the inner - frame locals, statements will be filtered to remove ignorable - statements according to self's location - """ - # if offset == -1, my actual frame is not the inner frame but its parent - # - # class A(B): pass - # - # we need this to resolve B correctly - if offset == -1: - myframe = self.frame().parent.frame() - else: - myframe = self.frame() - if not myframe is frame or self is frame: - return stmts - mystmt = self.statement() - # line filtering if we are in the same frame - # - # take care node may be missing lineno information (this is the case for - # nodes inserted for living objects) - if myframe is frame and mystmt.fromlineno is not None: - assert mystmt.fromlineno is not None, mystmt - mylineno = mystmt.fromlineno + offset - else: - # disabling lineno filtering - mylineno = 0 - _stmts = [] - _stmt_parents = [] - for node in stmts: - stmt = node.statement() - # line filtering is on and we have reached our location, break - if mylineno > 0 and stmt.fromlineno > mylineno: - break - assert hasattr(node, 'ass_type'), (node, node.scope(), - node.scope().locals) - ass_type = node.ass_type() - - if node.has_base(self): - break - - _stmts, done = ass_type._get_filtered_stmts(self, node, _stmts, mystmt) - if done: - break - - optional_assign = isinstance(ass_type, (For, Comprehension)) - if optional_assign and ass_type.parent_of(self): - # we are inside a loop, loop var assigment is hidding previous - # assigment - _stmts = [node] - _stmt_parents = [stmt.parent] - continue - - # XXX comment various branches below!!! - try: - pindex = _stmt_parents.index(stmt.parent) - except ValueError: - pass - else: - # we got a parent index, this means the currently visited node - # is at the same block level as a previously visited node - if _stmts[pindex].ass_type().parent_of(ass_type): - # both statements are not at the same block level - continue - # if currently visited node is following previously considered - # assignement and both are not exclusive, we can drop the - # previous one. For instance in the following code :: - # - # if a: - # x = 1 - # else: - # x = 2 - # print x - # - # we can't remove neither x = 1 nor x = 2 when looking for 'x' - # of 'print x'; while in the following :: - # - # x = 1 - # x = 2 - # print x - # - # we can remove x = 1 when we see x = 2 - # - # moreover, on loop assignment types, assignment won't - # necessarily be done if the loop has no iteration, so we don't - # want to clear previous assigments if any (hence the test on - # optional_assign) - if not (optional_assign or are_exclusive(_stmts[pindex], node)): - del _stmt_parents[pindex] - del _stmts[pindex] - if isinstance(node, AssName): - if not optional_assign and stmt.parent is mystmt.parent: - _stmts = [] - _stmt_parents = [] - elif isinstance(node, DelName): - _stmts = [] - _stmt_parents = [] - continue - if not are_exclusive(self, node): - _stmts.append(node) - _stmt_parents.append(stmt.parent) - return _stmts - def builtin_lookup(name): """lookup a name into the builtin module @@ -232,6 +90,7 @@ def builtin_lookup(name): return builtinastng, stmts +# TODO move this Mixin to mixins.py; problem: 'Function' in _scope_lookup class LocalsDictNodeNG(LookupMixIn, NodeNG): """ this class provides locals handling common to Module, Function and Class nodes, including a dict like interface for direct access @@ -350,19 +209,6 @@ class LocalsDictNodeNG(LookupMixIn, NodeNG): __contains__ = has_key -# Name classses - -class AssName(LookupMixIn, ParentAssignTypeMixin, NodeNG): - """class representing an AssName node""" - - -class DelName(LookupMixIn, ParentAssignTypeMixin, NodeNG): - """class representing a DelName node""" - - -class Name(LookupMixIn, NodeNG): - """class representing a Name node""" - # Module ##################################################################### |