summaryrefslogtreecommitdiff
path: root/node_classes.py
diff options
context:
space:
mode:
authorEmile Anclin <emile.anclin@logilab.fr>2010-03-15 15:03:00 +0100
committerEmile Anclin <emile.anclin@logilab.fr>2010-03-15 15:03:00 +0100
commitbec4ee12322d3f10df273e0764290e41cc6c701b (patch)
tree16cabedcffebe75683efb333b375c415dfe0188f /node_classes.py
parent7f263c79e19db7cfc39455e9016c33aa30b02db5 (diff)
downloadastroid-git-bec4ee12322d3f10df273e0764290e41cc6c701b.tar.gz
add a "mixins.py" module and move some classes around.
- move some Mixins to mixins.py - move LookupMixIn and *Name to node_classes It needs more polymorphism, i.e. removing 'isinstance' calls, to move LookupMixIn and LocalsDictNodeNG to mixins.py . --HG-- branch : rebuild
Diffstat (limited to 'node_classes.py')
-rw-r--r--node_classes.py238
1 files changed, 169 insertions, 69 deletions
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"""