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) |
- |
- |