summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmile Anclin <emile.anclin@logilab.fr>2010-03-04 12:45:36 +0100
committerEmile Anclin <emile.anclin@logilab.fr>2010-03-04 12:45:36 +0100
commitfb9de009d05656e580528fb86e0d2d13ce1c104b (patch)
treef9936d1e5749e13c9c4b1d77285858035c002110
parent6999b05af2c2f3577c21768138f00e4d0e1cb762 (diff)
downloadastroid-git-fb9de009d05656e580528fb86e0d2d13ce1c104b.tar.gz
move all infutils stuff to bases.py
We will need path_wrapper in the default NodeNG.infer method and have to avoid circular import. --HG-- branch : rebuild
-rw-r--r--__init__.py3
-rw-r--r--bases.py291
-rw-r--r--inference.py2
-rw-r--r--infutils.py309
-rw-r--r--node_classes.py4
-rw-r--r--protocols.py2
-rw-r--r--rebuilder.py2
-rw-r--r--scoped_nodes.py6
-rw-r--r--test/unittest_builder.py2
-rw-r--r--test/unittest_inference.py2
-rw-r--r--test/unittest_inspector.py2
-rw-r--r--test/unittest_scoped_nodes.py2
12 files changed, 304 insertions, 323 deletions
diff --git a/__init__.py b/__init__.py
index 8946a776..b15010ae 100644
--- a/__init__.py
+++ b/__init__.py
@@ -62,4 +62,5 @@ from logilab.astng import inference
# more stuff available
from logilab.astng import raw_building
-from logilab.astng.infutils import YES, Instance, BoundMethod, UnboundMethod
+from logilab.astng.bases import YES, Instance, BoundMethod, UnboundMethod
+
diff --git a/bases.py b/bases.py
index 85457a97..248d16a9 100644
--- a/bases.py
+++ b/bases.py
@@ -28,8 +28,297 @@ try:
except ImportError:
class BaseClass:
pass
+
+
+from logilab.common.compat import chain, imap
+
from logilab.astng.utils import REDIRECT
-from logilab.astng._exceptions import InferenceError, ASTNGError
+from logilab.astng._exceptions import InferenceError, ASTNGError, NotFoundError, UnresolvableName
+
+
+class Proxy(BaseClass):
+ """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__ = ('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 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__'):
+ 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 ?
+ return _infer_stmts(
+ self._wrap_attr(self.getattr(name, context, lookupclass=False), 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 '__builtin__.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)
+
+
+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(Proxy):
+ """a special node representing a generator"""
+ def callable(self):
+ return True
+
+ def pytype(self):
+ return '__builtin__.generator'
+
+ def display_type(self):
+ return 'Generator'
+
+
+# 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
# Node ######################################################################
diff --git a/inference.py b/inference.py
index e21785a6..0e5ddcc3 100644
--- a/inference.py
+++ b/inference.py
@@ -33,7 +33,7 @@ except NameError:
from logilab.astng import MANAGER, nodes, raw_building
from logilab.astng import ASTNGError, InferenceError, UnresolvableName, \
NoDefault, NotFoundError, ASTNGBuildingException
-from logilab.astng.infutils import YES, Instance, InferenceContext, \
+from logilab.astng.bases import YES, Instance, InferenceContext, \
_infer_stmts, copy_context, path_wrapper, raise_if_nothing_infered
from logilab.astng.protocols import _arguments_infer_argname
diff --git a/infutils.py b/infutils.py
deleted file mode 100644
index 919f4734..00000000
--- a/infutils.py
+++ /dev/null
@@ -1,309 +0,0 @@
-# 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.bases import BaseClass
-
-
-class Proxy(BaseClass):
- """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 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))
-
-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
-
-
-# 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()
-
-
-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__'):
- 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 ?
- return _infer_stmts(
- self._wrap_attr(self.getattr(name, context, lookupclass=False), 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 '__builtin__.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)
-
-
-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(Proxy):
- """a special node representing a generator"""
- def callable(self):
- return True
-
- def pytype(self):
- return '__builtin__.generator'
-
- def display_type(self):
- return 'Generator'
-
diff --git a/node_classes.py b/node_classes.py
index 542d3563..5250f975 100644
--- a/node_classes.py
+++ b/node_classes.py
@@ -3,8 +3,8 @@ from logilab.common.compat import chain, imap
from logilab.astng import (ASTNGBuildingException, InferenceError,
NotFoundError, NoDefault)
-from logilab.astng.bases import NodeNG, StmtMixIn, BlockRangeMixIn, BaseClass
-from logilab.astng.infutils import Instance
+from logilab.astng.bases import (NodeNG, StmtMixIn, BlockRangeMixIn,
+ BaseClass, Instance)
"""
Module for all nodes (except scoped nodes).
diff --git a/protocols.py b/protocols.py
index ff3d0e50..9b5c744b 100644
--- a/protocols.py
+++ b/protocols.py
@@ -26,7 +26,7 @@ __doctype__ = "restructuredtext en"
from logilab.astng import InferenceError, NoDefault
from logilab.astng.node_classes import unpack_infer
-from logilab.astng.infutils import copy_context, \
+from logilab.astng.bases import copy_context, \
raise_if_nothing_infered, yes_if_nothing_infered, Instance, Generator, YES
from logilab.astng.nodes import const_factory
from logilab.astng import nodes
diff --git a/rebuilder.py b/rebuilder.py
index dcb46fac..687b2164 100644
--- a/rebuilder.py
+++ b/rebuilder.py
@@ -23,7 +23,7 @@ order to get a single ASTNG representation
from logilab.astng import ASTNGBuildingException, InferenceError
from logilab.astng import nodes
from logilab.astng.utils import ASTVisitor, REDIRECT
-from logilab.astng.infutils import YES, Instance
+from logilab.astng.bases import YES, Instance
diff --git a/scoped_nodes.py b/scoped_nodes.py
index f0a694c1..1b16f47e 100644
--- a/scoped_nodes.py
+++ b/scoped_nodes.py
@@ -39,9 +39,9 @@ from logilab.astng import MANAGER, NotFoundError, NoDefault, \
from logilab.astng.node_classes import (Const, Comprehension, Dict,
From, For, Import, List, Pass, Raise, Return, Tuple, Yield, DelAttr,
are_exclusive, const_factory as cf, unpack_infer)
-from logilab.astng.bases import NodeNG, StmtMixIn, BaseClass
-from logilab.astng.infutils import YES, InferenceContext, Instance, Generator, \
- UnboundMethod, BoundMethod, _infer_stmts, copy_context
+from logilab.astng.bases import (NodeNG, StmtMixIn, BaseClass, YES,
+ InferenceContext, Instance, Generator,
+ UnboundMethod, BoundMethod, _infer_stmts, copy_context)
from logilab.astng.nodes_as_string import as_string
diff --git a/test/unittest_builder.py b/test/unittest_builder.py
index 01c9a749..06922472 100644
--- a/test/unittest_builder.py
+++ b/test/unittest_builder.py
@@ -22,7 +22,7 @@ from pprint import pprint
from logilab.astng import builder, nodes, patchcomptransformer, MANAGER, \
InferenceError, NotFoundError
from logilab.astng.nodes import Module
-from logilab.astng.infutils import YES
+from logilab.astng.bases import YES
from logilab.astng.nodes_as_string import as_string
from unittest_inference import get_name_node
diff --git a/test/unittest_inference.py b/test/unittest_inference.py
index f8462638..2516191e 100644
--- a/test/unittest_inference.py
+++ b/test/unittest_inference.py
@@ -18,7 +18,7 @@ from StringIO import StringIO
from logilab.common.testlib import TestCase, unittest_main
from logilab.astng import InferenceError, builder, nodes, inference
-from logilab.astng.infutils import YES, Instance, BoundMethod, UnboundMethod, path_wrapper
+from logilab.astng.bases import YES, Instance, BoundMethod, UnboundMethod, path_wrapper
def get_name_node(start_from, name, index=0):
return [n for n in start_from.nodes_of_class(nodes.Name) if n.name == name][index]
diff --git a/test/unittest_inspector.py b/test/unittest_inspector.py
index f911a0db..ec0a265f 100644
--- a/test/unittest_inspector.py
+++ b/test/unittest_inspector.py
@@ -21,7 +21,7 @@ import unittest
import sys
from logilab.astng import MANAGER, nodes, inspector
-from logilab.astng.infutils import Instance, YES
+from logilab.astng.bases import Instance, YES
def astng_wrapper(func, modname):
return func(modname)
diff --git a/test/unittest_scoped_nodes.py b/test/unittest_scoped_nodes.py
index 9816f1d6..f8d786fb 100644
--- a/test/unittest_scoped_nodes.py
+++ b/test/unittest_scoped_nodes.py
@@ -10,7 +10,7 @@ from logilab.common.compat import sorted
from logilab.astng import builder, nodes, scoped_nodes, \
InferenceError, NotFoundError
-from logilab.astng.infutils import Instance, BoundMethod, UnboundMethod
+from logilab.astng.bases import Instance, BoundMethod, UnboundMethod
abuilder = builder.ASTNGBuilder()
MODULE = abuilder.file_build('data/module.py', 'data.module')