| Index: third_party/logilab/astroid/bases.py
|
| diff --git a/third_party/logilab/astroid/bases.py b/third_party/logilab/astroid/bases.py
|
| index 37e613b8241494ccfd319b17ae3024453b69f00d..f1f4cc4b0b35f6e9012c76cb8ba4e26e1e8fe36e 100644
|
| --- a/third_party/logilab/astroid/bases.py
|
| +++ b/third_party/logilab/astroid/bases.py
|
| @@ -24,6 +24,8 @@ __docformat__ = "restructuredtext en"
|
| import sys
|
| from contextlib import contextmanager
|
|
|
| +from logilab.common.decorators import cachedproperty
|
| +
|
| from astroid.exceptions import (InferenceError, AstroidError, NotFoundError,
|
| UnresolvableName, UseInferenceDefault)
|
|
|
| @@ -56,63 +58,84 @@ class Proxy(object):
|
|
|
| # Inference ##################################################################
|
|
|
| +MISSING = object()
|
| +
|
| +
|
| class InferenceContext(object):
|
| - __slots__ = ('path', 'lookupname', 'callcontext', 'boundnode')
|
| + __slots__ = ('path', 'callcontext', 'boundnode', 'infered')
|
|
|
| - def __init__(self, path=None):
|
| + def __init__(self,
|
| + path=None, callcontext=None, boundnode=None, infered=None):
|
| if path is None:
|
| - self.path = set()
|
| + self.path = frozenset()
|
| 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
|
| + self.callcontext = callcontext
|
| + self.boundnode = boundnode
|
| + if infered is None:
|
| + self.infered = {}
|
| + else:
|
| + self.infered = infered
|
| +
|
| + def push(self, key):
|
| + # This returns a NEW context with the same attributes, but a new key
|
| + # added to `path`. The intention is that it's only passed to callees
|
| + # and then destroyed; otherwise scope() may not work correctly.
|
| + # The cache will be shared, since it's the same exact dict.
|
| + if key in self.path:
|
| + # End the containing generator
|
| + raise StopIteration
|
| +
|
| + return InferenceContext(
|
| + self.path.union([key]),
|
| + self.callcontext,
|
| + self.boundnode,
|
| + self.infered,
|
| + )
|
|
|
| @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 scope(self, callcontext=MISSING, boundnode=MISSING):
|
| + try:
|
| + orig = self.callcontext, self.boundnode
|
| + if callcontext is not MISSING:
|
| + self.callcontext = callcontext
|
| + if boundnode is not MISSING:
|
| + self.boundnode = boundnode
|
| + yield
|
| + finally:
|
| + self.callcontext, self.boundnode = orig
|
| +
|
| + def cache_generator(self, key, generator):
|
| + results = []
|
| + for result in generator:
|
| + results.append(result)
|
| + yield result
|
| +
|
| + self.infered[key] = tuple(results)
|
| + return
|
|
|
|
|
| -def _infer_stmts(stmts, context, frame=None):
|
| +def _infer_stmts(stmts, context, frame=None, lookupname=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
|
| + if context is None:
|
| context = InferenceContext()
|
| for stmt in stmts:
|
| if stmt is YES:
|
| yield stmt
|
| infered = True
|
| continue
|
| - context.lookupname = stmt._infer_name(frame, name)
|
| +
|
| + kw = {}
|
| + infered_name = stmt._infer_name(frame, lookupname)
|
| + if infered_name is not None:
|
| + # only returns not None if .infer() accepts a lookupname kwarg
|
| + kw['lookupname'] = infered_name
|
| +
|
| try:
|
| - for infered in stmt.infer(context):
|
| + for infered in stmt.infer(context, **kw):
|
| yield infered
|
| infered = True
|
| except UnresolvableName:
|
| @@ -170,20 +193,24 @@ class Instance(Proxy):
|
|
|
| def igetattr(self, name, context=None):
|
| """inferred getattr"""
|
| + if not context:
|
| + context = InferenceContext()
|
| try:
|
| # avoid recursively inferring the same attr on the same class
|
| - if context:
|
| - context.push((self._proxied, name))
|
| + new_context = context.push((self._proxied, name))
|
| # 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)
|
| + get_attr = self.getattr(name, new_context, lookupclass=False)
|
| + return _infer_stmts(
|
| + self._wrap_attr(get_attr, new_context),
|
| + new_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)
|
| + context)
|
| except NotFoundError:
|
| raise InferenceError(name)
|
|
|
| @@ -274,9 +301,9 @@ class BoundMethod(UnboundMethod):
|
| return True
|
|
|
| def infer_call_result(self, caller, context):
|
| - context = context.clone()
|
| - context.boundnode = self.bound
|
| - return self._proxied.infer_call_result(caller, context)
|
| + with context.scope(boundnode=self.bound):
|
| + for infered in self._proxied.infer_call_result(caller, context):
|
| + yield infered
|
|
|
|
|
| class Generator(Instance):
|
| @@ -308,7 +335,8 @@ def path_wrapper(func):
|
| """wrapper function handling context"""
|
| if context is None:
|
| context = InferenceContext()
|
| - context.push(node)
|
| + context = context.push((node, kwargs.get('lookupname')))
|
| +
|
| yielded = set()
|
| for res in _func(node, context, **kwargs):
|
| # unproxy only true instance, not const, tuple, dict...
|
| @@ -377,7 +405,15 @@ class NodeNG(object):
|
| return self._explicit_inference(self, context, **kwargs)
|
| except UseInferenceDefault:
|
| pass
|
| - return self._infer(context, **kwargs)
|
| +
|
| + if not context:
|
| + return self._infer(context, **kwargs)
|
| +
|
| + key = (self, kwargs.get('lookupname'), context.callcontext, context.boundnode)
|
| + if key in context.infered:
|
| + return iter(context.infered[key])
|
| +
|
| + return context.cache_generator(key, self._infer(context, **kwargs))
|
|
|
| def _repr_name(self):
|
| """return self.name or self.attrname or '' for nice representation"""
|
| @@ -415,7 +451,7 @@ class NodeNG(object):
|
| attr = getattr(self, field)
|
| if not attr: # None or empty listy / tuple
|
| continue
|
| - if isinstance(attr, (list, tuple)):
|
| + if attr.__class__ in (list, tuple):
|
| return attr[-1]
|
| else:
|
| return attr
|
| @@ -506,16 +542,28 @@ class NodeNG(object):
|
| # FIXME: raise an exception if nearest is None ?
|
| return nearest[0]
|
|
|
| - def set_line_info(self, lastchild):
|
| + # these are lazy because they're relatively expensive to compute for every
|
| + # single node, and they rarely get looked at
|
| +
|
| + @cachedproperty
|
| + def fromlineno(self):
|
| if self.lineno is None:
|
| - self.fromlineno = self._fixed_source_line()
|
| + return self._fixed_source_line()
|
| + else:
|
| + return self.lineno
|
| +
|
| + @cachedproperty
|
| + def tolineno(self):
|
| + if not self._astroid_fields:
|
| + # can't have children
|
| + lastchild = None
|
| else:
|
| - self.fromlineno = self.lineno
|
| + lastchild = self.last_child()
|
| if lastchild is None:
|
| - self.tolineno = self.fromlineno
|
| + return self.fromlineno
|
| else:
|
| - self.tolineno = lastchild.tolineno
|
| - return
|
| + return lastchild.tolineno
|
| +
|
| # TODO / FIXME:
|
| assert self.fromlineno is not None, self
|
| assert self.tolineno is not None, self
|
| @@ -530,7 +578,7 @@ class NodeNG(object):
|
| _node = self
|
| try:
|
| while line is None:
|
| - _node = _node.get_children().next()
|
| + _node = next(_node.get_children())
|
| line = _node.lineno
|
| except StopIteration:
|
| _node = self.parent
|
|
|