| Index: third_party/logilab/astroid/bases.py
|
| ===================================================================
|
| --- third_party/logilab/astroid/bases.py (revision 0)
|
| +++ third_party/logilab/astroid/bases.py (working copy)
|
| @@ -0,0 +1,617 @@
|
| +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
|
| +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
| +#
|
| +# This file is part of astroid.
|
| +#
|
| +# astroid 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.
|
| +#
|
| +# astroid 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 astroid. 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"
|
| +
|
| +import sys
|
| +from contextlib import contextmanager
|
| +
|
| +from astroid.exceptions import (InferenceError, AstroidError, NotFoundError,
|
| + UnresolvableName, UseInferenceDefault)
|
| +
|
| +
|
| +if sys.version_info >= (3, 0):
|
| + BUILTINS = 'builtins'
|
| +else:
|
| + BUILTINS = '__builtin__'
|
| +
|
| +
|
| +class Proxy(object):
|
| + """a simple proxy object"""
|
| +
|
| + _proxied = None # proxied object may be set by class or by instance
|
| +
|
| + 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 == 'next':
|
| + raise AttributeError('next method should not be called')
|
| + 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:
|
| + # avoid recursively inferring the same attr on the same class
|
| + if context:
|
| + context.push((self._proxied, name))
|
| + # 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 + '.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):
|
| + if node is YES:
|
| + continue
|
| + 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):
|
| + infer = caller.args[0].infer() if caller.args else []
|
| + return ((x is YES and x or Instance(x)) for x in 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.
|
| +
|
| + Proxied class is set once for all in raw_building.
|
| + """
|
| + def callable(self):
|
| + return False
|
| +
|
| + def pytype(self):
|
| + return '%s.generator' % BUILTINS
|
| +
|
| + 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 Astroid 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:
|
| + _astroid_fields = ()
|
| + # instance specific inference function infer(node, context)
|
| + _explicit_inference = None
|
| +
|
| + def infer(self, context=None, **kwargs):
|
| + """main interface to the interface system, return a generator on infered
|
| + values.
|
| +
|
| + If the instance has some explicit inference function set, it will be
|
| + called instead of the default interface.
|
| + """
|
| + if self._explicit_inference is not None:
|
| + # explicit_inference is not bound, give it self explicitly
|
| + try:
|
| + return self._explicit_inference(self, context, **kwargs)
|
| + except UseInferenceDefault:
|
| + pass
|
| + return self._infer(context, **kwargs)
|
| +
|
| + 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 0x%x>' % (self.__class__.__name__,
|
| + self._repr_name(),
|
| + self.fromlineno,
|
| + self.root().name,
|
| + id(self))
|
| +
|
| +
|
| + def accept(self, visitor):
|
| + func = getattr(visitor, "visit_" + self.__class__.__name__.lower())
|
| + return func(self)
|
| +
|
| + def get_children(self):
|
| + for field in self._astroid_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._astroid_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._astroid_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 find %s in %s\'s children'
|
| + raise AstroidError(msg % (repr(child), repr(self)))
|
| +
|
| + def locate_child(self, child):
|
| + """return a 2-uple (child attribute name, sequence or node)"""
|
| + for field in self._astroid_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 find %s in %s\'s children'
|
| + raise AstroidError(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):
|
| + from astroid.as_string import to_code
|
| + return to_code(self)
|
| +
|
| + def repr_tree(self, ids=False):
|
| + from astroid.as_string import dump
|
| + return dump(self)
|
| +
|
| +
|
| +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]
|
|
|