| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- # -*- 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)
|