summaryrefslogtreecommitdiff
path: root/infutils.py
diff options
context:
space:
mode:
authorSylvain Thénault <sylvain.thenault@logilab.fr>2009-03-17 17:10:39 +0100
committerSylvain Thénault <sylvain.thenault@logilab.fr>2009-03-17 17:10:39 +0100
commitcd39af0ef8fb8c1d4f9c9c3cfe2cdf86760ef09a (patch)
treea72bf7da3233588ad9edb9c4642b39205ebf133f /infutils.py
parent08f95b4db950d92733729388a0eaf8c2eb3fb637 (diff)
downloadastroid-git-cd39af0ef8fb8c1d4f9c9c3cfe2cdf86760ef09a.tar.gz
limit cyclic dependencies / cleanup by introducing a new infutils module
--HG-- branch : _ast_compat
Diffstat (limited to 'infutils.py')
-rw-r--r--infutils.py282
1 files changed, 282 insertions, 0 deletions
diff --git a/infutils.py b/infutils.py
new file mode 100644
index 00000000..365df00c
--- /dev/null
+++ b/infutils.py
@@ -0,0 +1,282 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""Inference utilities
+
+:author: Sylvain Thenault
+:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2009 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+from __future__ import generators
+
+__doctype__ = "restructuredtext en"
+
+from logilab.common.compat import chain, imap
+
+from logilab.astng._exceptions import InferenceError, NotFoundError, UnresolvableName
+from logilab.astng.nodes import Proxy_, List, Tuple, Function
+
+class Proxy(Proxy_):
+ """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
+
+
+class InferenceContext(object):
+ __slots__ = ('startingfrom', 'path', 'lookupname', 'callcontext', 'boundnode')
+
+ def __init__(self, node=None, path=None):
+ self.startingfrom = node # XXX useful ?
+ if path is None:
+ self.path = []
+ 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.append( (node, name) )
+
+ def pop(self):
+ return self.path.pop()
+
+ def clone(self):
+ # XXX copy lookupname/callcontext ?
+ clone = InferenceContext(self.startingfrom, self.path)
+ clone.callcontext = self.callcontext
+ clone.boundnode = self.boundnode
+ return clone
+
+
+def unpack_infer(stmt, context=None):
+ """return an iterator on nodes infered by the given statement if the infered
+ value is a list or a tuple, recurse on it to get values infered by its
+ content
+ """
+ if isinstance(stmt, (List, Tuple)):
+ # XXX loosing context
+ return chain(*imap(unpack_infer, stmt.elts))
+ infered = stmt.infer(context).next()
+ if infered is stmt:
+ return iter( (stmt,) )
+ return chain(*imap(unpack_infer, stmt.infer(context)))
+
+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 infered 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))
+
+class _Yes(object):
+ """a yes object"""
+ def __repr__(self):
+ return 'YES'
+ def __getattribute__(self, name):
+ return self
+ def __call__(self, *args, **kwargs):
+ return self
+
+
+# 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(node)
+ context.push(node)
+ yielded = []
+ try:
+ 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.append(ares)
+ context.pop()
+ except:
+ context.pop()
+ raise
+ 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
+
+
+# special inference objects (e.g. may be returned as nodes by .infer()) #######
+
+YES = _Yes()
+del _Yes
+
+
+class Instance(Proxy):
+ """a special node representing a class instance"""
+ def getattr(self, name, context=None, lookupclass=True):
+ try:
+ return self._proxied.instance_attr(name, context)
+ except NotFoundError:
+ if name == '__class__':
+ return [self._proxied]
+ if name == '__name__':
+ # access to __name__ gives undefined member on class
+ # instances but not on class objects
+ raise NotFoundError(name)
+ if lookupclass:
+ return self._proxied.getattr(name, context)
+ raise NotFoundError(name)
+
+ def igetattr(self, name, context=None):
+ """infered getattr"""
+ try:
+ # XXX frame should be self._proxied, or not ?
+ return _infer_stmts(
+ self._wrap_attr(self.getattr(name, context, lookupclass=False)),
+ 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))
+ except NotFoundError:
+ raise InferenceError(name)
+
+ def _wrap_attr(self, attrs):
+ """wrap bound methods of attrs in a InstanceMethod proxies"""
+ # Guess which attrs are used in inference.
+ def wrap(attr):
+ if isinstance(attr, Function) and attr.type == 'method':
+ return InstanceMethod(attr)
+ else:
+ return attr
+ return imap(wrap, attrs)
+
+ def infer_call_result(self, caller, context=None):
+ """infer what's 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()
+
+
+class InstanceMethod(Proxy):
+ """a special node representing a function bound to an instance"""
+ def __repr__(self):
+ instance = self._proxied.parent.frame()
+ return '<Bound method %s of %s.%s at 0x%s' % (self._proxied.name,
+ instance.root().name,
+ instance.name,
+ id(self))
+ __str__ = __repr__
+
+ def is_bound(self):
+ return True
+
+
+class Generator(Proxy):
+ """a special node representing a generator"""
+ def callable(self):
+ return True
+
+ def pytype(self):
+ return '__builtin__.generator'
+
+