summaryrefslogtreecommitdiff
path: root/scoped_nodes.py
diff options
context:
space:
mode:
Diffstat (limited to 'scoped_nodes.py')
-rw-r--r--scoped_nodes.py304
1 files changed, 299 insertions, 5 deletions
diff --git a/scoped_nodes.py b/scoped_nodes.py
index 8702f976..f0795758 100644
--- a/scoped_nodes.py
+++ b/scoped_nodes.py
@@ -36,13 +36,15 @@ from logilab.common.decorators import cached
from logilab.astng import MANAGER, NotFoundError, NoDefault, \
ASTNGBuildingException, InferenceError
-from logilab.astng.node_clases import (Const, Dict, From, List, Name, Pass,
- Raise, Return, Tuple, Yield, AssName, DelAttr, DelName,)
-from logilab.astng._nodes import const_factory as cf, NodeNG, StmtMixIn
+from logilab.astng.node_classes import (Const, Dict, From, List, Pass,
+ Raise, Return, Tuple, Yield, DelAttr, are_exclusive, const_factory as cf,
+ unpack_infer)
+from logilab.astng._nodes import NodeNG, StmtMixIn, BaseClass
from logilab.astng.infutils import YES, InferenceContext, Instance, Generator, \
- UnboundMethod, copy_context, unpack_infer, _infer_stmts
+ UnboundMethod, _infer_stmts, copy_context
+
from logilab.astng.nodes_as_string import as_string
-from logilab.astng.lookup import LocalsDictNodeNG
+
def remove_nodes(func, cls):
def wrapper(*args, **kwargs):
@@ -73,6 +75,298 @@ 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
+ if isinstance(node, nodes.Class) and self in node.bases:
+ break
+ assert hasattr(node, 'ass_type'), (node, node.scope(),
+ node.scope().locals)
+ ass_type = node.ass_type()
+ if ass_type is mystmt and not isinstance(ass_type, (nodes.Class,
+ nodes.Function, nodes.Import, nodes.From, nodes.Lambda)):
+ if not isinstance(ass_type, nodes.Comprehension):
+ break
+ if isinstance(self, (nodes.Const, nodes.Name)):
+ _stmts = [self]
+ break
+ elif ass_type.statement() is mystmt:
+ # original node's statement is the assignment, only keeps
+ # current node (gen exp, list comp)
+ _stmts = [node]
+ break
+ optional_assign = isinstance(ass_type, (nodes.For, nodes.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, nodes.AssName):
+ if not optional_assign and stmt.parent is mystmt.parent:
+ _stmts = []
+ _stmt_parents = []
+ elif isinstance(node, nodes.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
+ return the list of matching statements and the astng for the builtin
+ module
+ """
+ builtinastng = MANAGER.astng_from_module(__builtin__)
+ if name == '__dict__':
+ return builtinastng, ()
+ try:
+ stmts = builtinastng.locals[name]
+ except KeyError:
+ stmts = ()
+ return builtinastng, stmts
+
+
+class LocalsDictNodeNG(LookupMixIn, NodeNG):
+ """ this class provides locals handling common to Module, Function
+ and Class nodes, including a dict like interface for direct access
+ to locals information
+ """
+
+ # attributes below are set by the builder module or by raw factories
+
+ # dictionary of locals with name as key and node defining the local as
+ # value
+ locals = None
+
+ def qname(self):
+ """return the 'qualified' name of the node, eg module.name,
+ module.class.name ...
+ """
+ if self.parent is None:
+ return self.name
+ return '%s.%s' % (self.parent.frame().qname(), self.name)
+
+ def frame(self):
+ """return the first parent frame node (i.e. Module, Function or Class)
+ """
+ return self
+
+ def scope(self):
+ """return the first node defining a new scope (i.e. Module,
+ Function, Class, Lambda but also GenExpr)
+ """
+ return self
+
+
+ def _scope_lookup(self, node, name, offset=0):
+ """XXX method for interfacing the scope lookup"""
+ try:
+ stmts = node._filter_stmts(self.locals[name], self, offset)
+ except KeyError:
+ stmts = ()
+ if stmts:
+ return self, stmts
+ if self.parent: # i.e. not Module
+ # nested scope: if parent scope is a function, that's fine
+ # else jump to the module
+ pscope = self.parent.scope()
+ if not isinstance(pscope, nodes.Function):
+ pscope = pscope.root()
+ return pscope.scope_lookup(node, name)
+ return builtin_lookup(name) # Module
+
+
+
+ def set_local(self, name, stmt):
+ """define <name> in locals (<stmt> is the node defining the name)
+ if the node is a Module node (i.e. has globals), add the name to
+ globals
+
+ if the name is already defined, ignore it
+ """
+ assert self.locals is not None, (self, id(self))
+ #assert not stmt in self.locals.get(name, ()), (self, stmt)
+ self.locals.setdefault(name, []).append(stmt)
+
+ __setitem__ = set_local
+
+ def _append_node(self, child):
+ """append a child, linking it in the tree"""
+ self.body.append(child)
+ child.parent = self
+
+ def add_local_node(self, child_node, name=None):
+ """append a child which should alter locals to the given node"""
+ if name != '__class__':
+ # add __class__ node as a child will cause infinite recursion later!
+ self._append_node(child_node)
+ self.set_local(name or child_node.name, child_node)
+
+
+ def __getitem__(self, item):
+ """method from the `dict` interface returning the first node
+ associated with the given name in the locals dictionary
+
+ :type item: str
+ :param item: the name of the locally defined object
+ :raises KeyError: if the name is not defined
+ """
+ return self.locals[item][0]
+
+ def __iter__(self):
+ """method from the `dict` interface returning an iterator on
+ `self.keys()`
+ """
+ return iter(self.keys())
+
+ def keys(self):
+ """method from the `dict` interface returning a tuple containing
+ locally defined names
+ """
+ return self.locals.keys()
+
+ def values(self):
+ """method from the `dict` interface returning a tuple containing
+ locally defined nodes which are instance of `Function` or `Class`
+ """
+ return [self[key] for key in self.keys()]
+
+ def items(self):
+ """method from the `dict` interface returning a list of tuple
+ containing each locally defined name with its associated node,
+ which is an instance of `Function` or `Class`
+ """
+ return zip(self.keys(), self.values())
+
+ def has_key(self, name):
+ """method from the `dict` interface returning True if the given
+ name is defined in the locals dictionary
+ """
+ return self.locals.has_key(name)
+
+ __contains__ = has_key
+
+# Name classses
+
+class AssName(LookupMixIn, NodeNG):
+ """class representing an AssName node"""
+
+
+class DelName(LookupMixIn, NodeNG):
+ """class representing a DelName node"""
+
+
+class Name(LookupMixIn, NodeNG):
+ """class representing a Name node"""
+
+
# Module #####################################################################
class Module(LocalsDictNodeNG):