diff options
author | Emile Anclin <emile.anclin@logilab.fr> | 2010-03-04 12:45:36 +0100 |
---|---|---|
committer | Emile Anclin <emile.anclin@logilab.fr> | 2010-03-04 12:45:36 +0100 |
commit | fb9de009d05656e580528fb86e0d2d13ce1c104b (patch) | |
tree | f9936d1e5749e13c9c4b1d77285858035c002110 | |
parent | 6999b05af2c2f3577c21768138f00e4d0e1cb762 (diff) | |
download | astroid-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__.py | 3 | ||||
-rw-r--r-- | bases.py | 291 | ||||
-rw-r--r-- | inference.py | 2 | ||||
-rw-r--r-- | infutils.py | 309 | ||||
-rw-r--r-- | node_classes.py | 4 | ||||
-rw-r--r-- | protocols.py | 2 | ||||
-rw-r--r-- | rebuilder.py | 2 | ||||
-rw-r--r-- | scoped_nodes.py | 6 | ||||
-rw-r--r-- | test/unittest_builder.py | 2 | ||||
-rw-r--r-- | test/unittest_inference.py | 2 | ||||
-rw-r--r-- | test/unittest_inspector.py | 2 | ||||
-rw-r--r-- | test/unittest_scoped_nodes.py | 2 |
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 + @@ -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') |