| Index: third_party/logilab/astng/bases.py
|
| ===================================================================
|
| --- third_party/logilab/astng/bases.py (revision 292986)
|
| +++ third_party/logilab/astng/bases.py (working copy)
|
| @@ -1,629 +0,0 @@
|
| -# -*- coding: utf-8 -*-
|
| -# copyright 2003-2011 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/>.
|
| -"""This module contains base classes and functions for the nodes and some
|
| -inference utils.
|
| -"""
|
| -
|
| -__docformat__ = "restructuredtext en"
|
| -
|
| -from contextlib import contextmanager
|
| -
|
| -from logilab.common.compat import builtins
|
| -
|
| -from logilab.astng import BUILTINS_MODULE
|
| -from logilab.astng.exceptions import InferenceError, ASTNGError, \
|
| - NotFoundError, UnresolvableName
|
| -from logilab.astng.as_string import as_string
|
| -
|
| -BUILTINS_NAME = builtins.__name__
|
| -
|
| -class Proxy(object):
|
| - """a simple proxy object"""
|
| - _proxied = None
|
| -
|
| - def __init__(self, proxied=None):
|
| - if proxied is not None:
|
| - self._proxied = proxied
|
| -
|
| - def __getattr__(self, name):
|
| - if name == '_proxied':
|
| - return getattr(self.__class__, '_proxied')
|
| - if name in self.__dict__:
|
| - return self.__dict__[name]
|
| - return getattr(self._proxied, name)
|
| -
|
| - def infer(self, context=None):
|
| - yield self
|
| -
|
| -
|
| -# Inference ##################################################################
|
| -
|
| -class InferenceContext(object):
|
| - __slots__ = ('path', 'lookupname', 'callcontext', 'boundnode')
|
| -
|
| - def __init__(self, path=None):
|
| - if path is None:
|
| - self.path = set()
|
| - else:
|
| - self.path = path
|
| - self.lookupname = None
|
| - self.callcontext = None
|
| - self.boundnode = None
|
| -
|
| - def push(self, node):
|
| - name = self.lookupname
|
| - if (node, name) in self.path:
|
| - raise StopIteration()
|
| - self.path.add( (node, name) )
|
| -
|
| - def clone(self):
|
| - # XXX copy lookupname/callcontext ?
|
| - clone = InferenceContext(self.path)
|
| - clone.callcontext = self.callcontext
|
| - clone.boundnode = self.boundnode
|
| - return clone
|
| -
|
| - @contextmanager
|
| - def restore_path(self):
|
| - path = set(self.path)
|
| - yield
|
| - self.path = path
|
| -
|
| -def copy_context(context):
|
| - if context is not None:
|
| - return context.clone()
|
| - else:
|
| - return InferenceContext()
|
| -
|
| -
|
| -def _infer_stmts(stmts, context, frame=None):
|
| - """return an iterator on statements inferred by each statement in <stmts>
|
| - """
|
| - stmt = None
|
| - infered = False
|
| - if context is not None:
|
| - name = context.lookupname
|
| - context = context.clone()
|
| - else:
|
| - name = None
|
| - context = InferenceContext()
|
| - for stmt in stmts:
|
| - if stmt is YES:
|
| - yield stmt
|
| - infered = True
|
| - continue
|
| - context.lookupname = stmt._infer_name(frame, name)
|
| - try:
|
| - for infered in stmt.infer(context):
|
| - yield infered
|
| - infered = True
|
| - except UnresolvableName:
|
| - continue
|
| - except InferenceError:
|
| - yield YES
|
| - infered = True
|
| - if not infered:
|
| - raise InferenceError(str(stmt))
|
| -
|
| -
|
| -# special inference objects (e.g. may be returned as nodes by .infer()) #######
|
| -
|
| -class _Yes(object):
|
| - """a yes object"""
|
| - def __repr__(self):
|
| - return 'YES'
|
| - def __getattribute__(self, name):
|
| - if name.startswith('__') and name.endswith('__'):
|
| - # to avoid inspection pb
|
| - return super(_Yes, self).__getattribute__(name)
|
| - return self
|
| - def __call__(self, *args, **kwargs):
|
| - return self
|
| -
|
| -
|
| -YES = _Yes()
|
| -
|
| -
|
| -class Instance(Proxy):
|
| - """a special node representing a class instance"""
|
| - def getattr(self, name, context=None, lookupclass=True):
|
| - try:
|
| - values = self._proxied.instance_attr(name, context)
|
| - except NotFoundError:
|
| - if name == '__class__':
|
| - return [self._proxied]
|
| - if lookupclass:
|
| - # class attributes not available through the instance
|
| - # unless they are explicitly defined
|
| - if name in ('__name__', '__bases__', '__mro__', '__subclasses__'):
|
| - return self._proxied.local_attr(name)
|
| - return self._proxied.getattr(name, context)
|
| - raise NotFoundError(name)
|
| - # since we've no context information, return matching class members as
|
| - # well
|
| - if lookupclass:
|
| - try:
|
| - return values + self._proxied.getattr(name, context)
|
| - except NotFoundError:
|
| - pass
|
| - return values
|
| -
|
| - def igetattr(self, name, context=None):
|
| - """inferred getattr"""
|
| - try:
|
| - # XXX frame should be self._proxied, or not ?
|
| - get_attr = self.getattr(name, context, lookupclass=False)
|
| - return _infer_stmts(self._wrap_attr(get_attr, context), context,
|
| - frame=self)
|
| - except NotFoundError:
|
| - try:
|
| - # fallback to class'igetattr since it has some logic to handle
|
| - # descriptors
|
| - return self._wrap_attr(self._proxied.igetattr(name, context),
|
| - context)
|
| - except NotFoundError:
|
| - raise InferenceError(name)
|
| -
|
| - def _wrap_attr(self, attrs, context=None):
|
| - """wrap bound methods of attrs in a InstanceMethod proxies"""
|
| - for attr in attrs:
|
| - if isinstance(attr, UnboundMethod):
|
| - if BUILTINS_NAME + '.property' in attr.decoratornames():
|
| - for infered in attr.infer_call_result(self, context):
|
| - yield infered
|
| - else:
|
| - yield BoundMethod(attr, self)
|
| - else:
|
| - yield attr
|
| -
|
| - def infer_call_result(self, caller, context=None):
|
| - """infer what a class instance is returning when called"""
|
| - infered = False
|
| - for node in self._proxied.igetattr('__call__', context):
|
| - for res in node.infer_call_result(caller, context):
|
| - infered = True
|
| - yield res
|
| - if not infered:
|
| - raise InferenceError()
|
| -
|
| - def __repr__(self):
|
| - return '<Instance of %s.%s at 0x%s>' % (self._proxied.root().name,
|
| - self._proxied.name,
|
| - id(self))
|
| - def __str__(self):
|
| - return 'Instance of %s.%s' % (self._proxied.root().name,
|
| - self._proxied.name)
|
| -
|
| - def callable(self):
|
| - try:
|
| - self._proxied.getattr('__call__')
|
| - return True
|
| - except NotFoundError:
|
| - return False
|
| -
|
| - def pytype(self):
|
| - return self._proxied.qname()
|
| -
|
| - def display_type(self):
|
| - return 'Instance of'
|
| -
|
| -
|
| -class UnboundMethod(Proxy):
|
| - """a special node representing a method not bound to an instance"""
|
| - def __repr__(self):
|
| - frame = self._proxied.parent.frame()
|
| - return '<%s %s of %s at 0x%s' % (self.__class__.__name__,
|
| - self._proxied.name,
|
| - frame.qname(), id(self))
|
| -
|
| - def is_bound(self):
|
| - return False
|
| -
|
| - def getattr(self, name, context=None):
|
| - if name == 'im_func':
|
| - return [self._proxied]
|
| - return super(UnboundMethod, self).getattr(name, context)
|
| -
|
| - def igetattr(self, name, context=None):
|
| - if name == 'im_func':
|
| - 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() == '%s.object' % BUILTINS_MODULE):
|
| - 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"""
|
| - def __init__(self, proxy, bound):
|
| - UnboundMethod.__init__(self, proxy)
|
| - self.bound = bound
|
| -
|
| - def is_bound(self):
|
| - return True
|
| -
|
| - def infer_call_result(self, caller, context):
|
| - context = context.clone()
|
| - context.boundnode = self.bound
|
| - return self._proxied.infer_call_result(caller, context)
|
| -
|
| -
|
| -class Generator(Instance):
|
| - """a special node representing a generator"""
|
| - def callable(self):
|
| - return True
|
| -
|
| - def pytype(self):
|
| - return '%s.generator' % BUILTINS_MODULE
|
| -
|
| - 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 ##################################################################
|
| -
|
| -def path_wrapper(func):
|
| - """return the given infer function wrapped to handle the path"""
|
| - def wrapped(node, context=None, _func=func, **kwargs):
|
| - """wrapper function handling context"""
|
| - if context is None:
|
| - context = InferenceContext()
|
| - context.push(node)
|
| - yielded = set()
|
| - for res in _func(node, context, **kwargs):
|
| - # unproxy only true instance, not const, tuple, dict...
|
| - if res.__class__ is Instance:
|
| - ares = res._proxied
|
| - else:
|
| - ares = res
|
| - if not ares in yielded:
|
| - yield res
|
| - yielded.add(ares)
|
| - return wrapped
|
| -
|
| -def yes_if_nothing_infered(func):
|
| - def wrapper(*args, **kwargs):
|
| - infered = False
|
| - for node in func(*args, **kwargs):
|
| - infered = True
|
| - yield node
|
| - if not infered:
|
| - yield YES
|
| - return wrapper
|
| -
|
| -def raise_if_nothing_infered(func):
|
| - def wrapper(*args, **kwargs):
|
| - infered = False
|
| - for node in func(*args, **kwargs):
|
| - infered = True
|
| - yield node
|
| - if not infered:
|
| - raise InferenceError()
|
| - return wrapper
|
| -
|
| -
|
| -# Node ######################################################################
|
| -
|
| -class NodeNG(object):
|
| - """Base Class for all ASTNG node classes.
|
| -
|
| - It represents a node of the new abstract syntax tree.
|
| - """
|
| - is_statement = False
|
| - optional_assign = False # True for For (and for Comprehension if py <3.0)
|
| - is_function = False # True for Function nodes
|
| - # attributes below are set by the builder module or by raw factories
|
| - lineno = None
|
| - fromlineno = None
|
| - tolineno = None
|
| - col_offset = None
|
| - # parent node in the tree
|
| - parent = None
|
| - # attributes containing child node(s) redefined in most concrete classes:
|
| - _astng_fields = ()
|
| -
|
| - def _repr_name(self):
|
| - """return self.name or self.attrname or '' for nice representation"""
|
| - return getattr(self, 'name', getattr(self, 'attrname', ''))
|
| -
|
| - def __str__(self):
|
| - return '%s(%s)' % (self.__class__.__name__, self._repr_name())
|
| -
|
| - def __repr__(self):
|
| - return '<%s(%s) l.%s [%s] at Ox%x>' % (self.__class__.__name__,
|
| - self._repr_name(),
|
| - self.fromlineno,
|
| - self.root().name,
|
| - id(self))
|
| -
|
| -
|
| - def accept(self, visitor):
|
| - klass = self.__class__.__name__
|
| - func = getattr(visitor, "visit_" + self.__class__.__name__.lower())
|
| - return func(self)
|
| -
|
| - def get_children(self):
|
| - for field in self._astng_fields:
|
| - attr = getattr(self, field)
|
| - if attr is None:
|
| - continue
|
| - if isinstance(attr, (list, tuple)):
|
| - for elt in attr:
|
| - yield elt
|
| - else:
|
| - yield attr
|
| -
|
| - def last_child(self):
|
| - """an optimized version of list(get_children())[-1]"""
|
| - for field in self._astng_fields[::-1]:
|
| - attr = getattr(self, field)
|
| - if not attr: # None or empty listy / tuple
|
| - continue
|
| - if isinstance(attr, (list, tuple)):
|
| - return attr[-1]
|
| - else:
|
| - return attr
|
| - return None
|
| -
|
| - def parent_of(self, node):
|
| - """return true if i'm a parent of the given node"""
|
| - parent = node.parent
|
| - while parent is not None:
|
| - if self is parent:
|
| - return True
|
| - parent = parent.parent
|
| - return False
|
| -
|
| - def statement(self):
|
| - """return the first parent node marked as statement node"""
|
| - if self.is_statement:
|
| - return self
|
| - return self.parent.statement()
|
| -
|
| - def frame(self):
|
| - """return the first parent frame node (i.e. Module, Function or Class)
|
| - """
|
| - return self.parent.frame()
|
| -
|
| - def scope(self):
|
| - """return the first node defining a new scope (i.e. Module, Function,
|
| - Class, Lambda but also GenExpr)
|
| - """
|
| - return self.parent.scope()
|
| -
|
| - def root(self):
|
| - """return the root node of the tree, (i.e. a Module)"""
|
| - if self.parent:
|
| - return self.parent.root()
|
| - return self
|
| -
|
| - def child_sequence(self, child):
|
| - """search for the right sequence where the child lies in"""
|
| - for field in self._astng_fields:
|
| - node_or_sequence = getattr(self, field)
|
| - if node_or_sequence is child:
|
| - return [node_or_sequence]
|
| - # /!\ compiler.ast Nodes have an __iter__ walking over child nodes
|
| - if isinstance(node_or_sequence, (tuple, list)) and child in node_or_sequence:
|
| - return node_or_sequence
|
| - else:
|
| - msg = 'Could not found %s in %s\'s children'
|
| - raise ASTNGError(msg % (repr(child), repr(self)))
|
| -
|
| - def locate_child(self, child):
|
| - """return a 2-uple (child attribute name, sequence or node)"""
|
| - for field in self._astng_fields:
|
| - node_or_sequence = getattr(self, field)
|
| - # /!\ compiler.ast Nodes have an __iter__ walking over child nodes
|
| - if child is node_or_sequence:
|
| - return field, child
|
| - if isinstance(node_or_sequence, (tuple, list)) and child in node_or_sequence:
|
| - return field, node_or_sequence
|
| - msg = 'Could not found %s in %s\'s children'
|
| - raise ASTNGError(msg % (repr(child), repr(self)))
|
| - # FIXME : should we merge child_sequence and locate_child ? locate_child
|
| - # is only used in are_exclusive, child_sequence one time in pylint.
|
| -
|
| - def next_sibling(self):
|
| - """return the next sibling statement"""
|
| - return self.parent.next_sibling()
|
| -
|
| - def previous_sibling(self):
|
| - """return the previous sibling statement"""
|
| - return self.parent.previous_sibling()
|
| -
|
| - def nearest(self, nodes):
|
| - """return the node which is the nearest before this one in the
|
| - given list of nodes
|
| - """
|
| - myroot = self.root()
|
| - mylineno = self.fromlineno
|
| - nearest = None, 0
|
| - for node in nodes:
|
| - assert node.root() is myroot, \
|
| - 'nodes %s and %s are not from the same module' % (self, node)
|
| - lineno = node.fromlineno
|
| - if node.fromlineno > mylineno:
|
| - break
|
| - if lineno > nearest[1]:
|
| - nearest = node, lineno
|
| - # FIXME: raise an exception if nearest is None ?
|
| - return nearest[0]
|
| -
|
| - def set_line_info(self, lastchild):
|
| - if self.lineno is None:
|
| - self.fromlineno = self._fixed_source_line()
|
| - else:
|
| - self.fromlineno = self.lineno
|
| - if lastchild is None:
|
| - self.tolineno = self.fromlineno
|
| - else:
|
| - self.tolineno = lastchild.tolineno
|
| - return
|
| - # TODO / FIXME:
|
| - assert self.fromlineno is not None, self
|
| - assert self.tolineno is not None, self
|
| -
|
| - def _fixed_source_line(self):
|
| - """return the line number where the given node appears
|
| -
|
| - we need this method since not all nodes have the lineno attribute
|
| - correctly set...
|
| - """
|
| - line = self.lineno
|
| - _node = self
|
| - try:
|
| - while line is None:
|
| - _node = _node.get_children().next()
|
| - line = _node.lineno
|
| - except StopIteration:
|
| - _node = self.parent
|
| - while _node and line is None:
|
| - line = _node.lineno
|
| - _node = _node.parent
|
| - return line
|
| -
|
| - def block_range(self, lineno):
|
| - """handle block line numbers range for non block opening statements
|
| - """
|
| - return lineno, self.tolineno
|
| -
|
| - def set_local(self, name, stmt):
|
| - """delegate to a scoped parent handling a locals dictionary"""
|
| - self.parent.set_local(name, stmt)
|
| -
|
| - def nodes_of_class(self, klass, skip_klass=None):
|
| - """return an iterator on nodes which are instance of the given class(es)
|
| -
|
| - klass may be a class object or a tuple of class objects
|
| - """
|
| - if isinstance(self, klass):
|
| - yield self
|
| - for child_node in self.get_children():
|
| - if skip_klass is not None and isinstance(child_node, skip_klass):
|
| - continue
|
| - for matching in child_node.nodes_of_class(klass, skip_klass):
|
| - yield matching
|
| -
|
| - def _infer_name(self, frame, name):
|
| - # overridden for From, Import, Global, TryExcept and Arguments
|
| - return None
|
| -
|
| - def infer(self, context=None):
|
| - """we don't know how to resolve a statement by default"""
|
| - # this method is overridden by most concrete classes
|
| - raise InferenceError(self.__class__.__name__)
|
| -
|
| - def infered(self):
|
| - '''return list of infered values for a more simple inference usage'''
|
| - return list(self.infer())
|
| -
|
| - def instanciate_class(self):
|
| - """instanciate a node if it is a Class node, else return self"""
|
| - return self
|
| -
|
| - def has_base(self, node):
|
| - return False
|
| -
|
| - def callable(self):
|
| - return False
|
| -
|
| - def eq(self, value):
|
| - return False
|
| -
|
| - def as_string(self):
|
| - return as_string(self)
|
| -
|
| - def repr_tree(self, ids=False):
|
| - """print a nice astng tree representation.
|
| -
|
| - :param ids: if true, we also print the ids (usefull for debugging)"""
|
| - result = []
|
| - _repr_tree(self, result, ids=ids)
|
| - 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):
|
| - """built a tree representation of a node as a list of lines"""
|
| - if _done is None:
|
| - _done = set()
|
| - if not hasattr(node, '_astng_fields'): # not a astng node
|
| - return
|
| - if node in _done:
|
| - result.append( indent + 'loop in tree: %s' % node )
|
| - return
|
| - _done.add(node)
|
| - node_str = str(node)
|
| - if ids:
|
| - node_str += ' . \t%x' % id(node)
|
| - result.append( indent + node_str )
|
| - indent += INDENT
|
| - for field in node._astng_fields:
|
| - value = getattr(node, field)
|
| - if isinstance(value, (list, tuple) ):
|
| - result.append( indent + field + " = [" )
|
| - for child in value:
|
| - if isinstance(child, (list, tuple) ):
|
| - # special case for Dict # FIXME
|
| - _repr_tree(child[0], result, indent, _done, ids)
|
| - _repr_tree(child[1], result, indent, _done, ids)
|
| - result.append(indent + ',')
|
| - else:
|
| - _repr_tree(child, result, indent, _done, ids)
|
| - result.append( indent + "]" )
|
| - else:
|
| - result.append( indent + field + " = " )
|
| - _repr_tree(value, result, indent, _done, ids)
|
| -
|
| -
|
|
|