Index: third_party/logilab/astroid/scoped_nodes.py |
diff --git a/third_party/logilab/astroid/scoped_nodes.py b/third_party/logilab/astroid/scoped_nodes.py |
index eb60298f6a5fe345dbc9766f763beb4c10ba570d..f9ec7b774f86c4821ff457b8eb19100ab3217d62 100644 |
--- a/third_party/logilab/astroid/scoped_nodes.py |
+++ b/third_party/logilab/astroid/scoped_nodes.py |
@@ -30,6 +30,7 @@ try: |
except ImportError: |
from cStringIO import StringIO as BytesIO |
+import six |
from logilab.common.compat import builtins |
from logilab.common.decorators import cached, cachedproperty |
@@ -39,7 +40,7 @@ from astroid.node_classes import Const, DelName, DelAttr, \ |
Dict, From, List, Pass, Raise, Return, Tuple, Yield, YieldFrom, \ |
LookupMixIn, const_factory as cf, unpack_infer, Name, CallFunc |
from astroid.bases import NodeNG, InferenceContext, Instance,\ |
- YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, copy_context, \ |
+ YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, \ |
BUILTINS |
from astroid.mixins import FilterStmtsMixin |
from astroid.bases import Statement |
@@ -191,7 +192,7 @@ class LocalsDictNodeNG(LookupMixIn, NodeNG): |
"""method from the `dict` interface returning a tuple containing |
locally defined names |
""" |
- return self.locals.keys() |
+ return list(self.locals.keys()) |
def values(self): |
"""method from the `dict` interface returning a tuple containing |
@@ -204,7 +205,7 @@ class LocalsDictNodeNG(LookupMixIn, NodeNG): |
containing each locally defined name with its associated node, |
which is an instance of `Function` or `Class` |
""" |
- return zip(self.keys(), self.values()) |
+ return list(zip(self.keys(), self.values())) |
def __contains__(self, name): |
@@ -256,7 +257,7 @@ class Module(LocalsDictNodeNG): |
self.body = [] |
self.future_imports = set() |
- @property |
+ @cachedproperty |
def file_stream(self): |
if self.file_bytes is not None: |
return BytesIO(self.file_bytes) |
@@ -311,10 +312,10 @@ class Module(LocalsDictNodeNG): |
"""inferred getattr""" |
# set lookup name since this is necessary to infer on import nodes for |
# instance |
- context = copy_context(context) |
- context.lookupname = name |
+ if not context: |
+ context = InferenceContext() |
try: |
- return _infer_stmts(self.getattr(name, context), context, frame=self) |
+ return _infer_stmts(self.getattr(name, context), context, frame=self, lookupname=name) |
except NotFoundError: |
raise InferenceError(name) |
@@ -339,13 +340,17 @@ class Module(LocalsDictNodeNG): |
return |
if sys.version_info < (2, 8): |
- def absolute_import_activated(self): |
+ @cachedproperty |
+ def _absolute_import_activated(self): |
for stmt in self.locals.get('absolute_import', ()): |
if isinstance(stmt, From) and stmt.modname == '__future__': |
return True |
return False |
else: |
- absolute_import_activated = lambda self: True |
+ _absolute_import_activated = True |
+ |
+ def absolute_import_activated(self): |
+ return self._absolute_import_activated |
def import_module(self, modname, relative_only=False, level=None): |
"""import the given module considering self as context""" |
@@ -408,24 +413,43 @@ class Module(LocalsDictNodeNG): |
# |
# We separate the different steps of lookup in try/excepts |
# to avoid catching too many Exceptions |
- # However, we can not analyse dynamically constructed __all__ |
+ default = [name for name in self.keys() if not name.startswith('_')] |
try: |
all = self['__all__'] |
except KeyError: |
- return [name for name in self.keys() if not name.startswith('_')] |
+ return default |
try: |
- explicit = all.assigned_stmts().next() |
+ explicit = next(all.assigned_stmts()) |
except InferenceError: |
- return [name for name in self.keys() if not name.startswith('_')] |
+ return default |
except AttributeError: |
# not an assignment node |
# XXX infer? |
- return [name for name in self.keys() if not name.startswith('_')] |
+ return default |
+ |
+ # Try our best to detect the exported name. |
+ infered = [] |
try: |
- # should be a Tuple/List of constant string / 1 string not allowed |
- return [const.value for const in explicit.elts] |
- except AttributeError: |
- return [name for name in self.keys() if not name.startswith('_')] |
+ explicit = next(explicit.infer()) |
+ except InferenceError: |
+ return default |
+ if not isinstance(explicit, (Tuple, List)): |
+ return default |
+ |
+ str_const = lambda node: (isinstance(node, Const) and |
+ isinstance(node.value, six.string_types)) |
+ for node in explicit.elts: |
+ if str_const(node): |
+ infered.append(node.value) |
+ else: |
+ try: |
+ infered_node = next(node.infer()) |
+ except InferenceError: |
+ continue |
+ if str_const(infered_node): |
+ infered.append(infered_node.value) |
+ return infered |
+ |
class ComprehensionScope(LocalsDictNodeNG): |
@@ -488,7 +512,7 @@ def _infer_decorator_callchain(node): |
while True: |
if isinstance(current, CallFunc): |
try: |
- current = current.func.infer().next() |
+ current = next(current.func.infer()) |
except InferenceError: |
return |
elif isinstance(current, Function): |
@@ -498,7 +522,11 @@ def _infer_decorator_callchain(node): |
# TODO: We don't handle multiple inference results right now, |
# because there's no flow to reason when the return |
# is what we are looking for, a static or a class method. |
- result = current.infer_call_result(current.parent).next() |
+ result = next(current.infer_call_result(current.parent)) |
+ if current is result: |
+ # This will lead to an infinite loop, where a decorator |
+ # returns itself. |
+ return |
except (StopIteration, InferenceError): |
return |
if isinstance(result, (Function, CallFunc)): |
@@ -629,22 +657,25 @@ class Function(Statement, Lambda): |
self.locals = {} |
self.args = [] |
self.body = [] |
- self.decorators = None |
self.name = name |
self.doc = doc |
self.extra_decorators = [] |
self.instance_attrs = {} |
- def set_line_info(self, lastchild): |
- self.fromlineno = self.lineno |
- # lineno is the line number of the first decorator, we want the def statement lineno |
+ @cachedproperty |
+ def fromlineno(self): |
+ # lineno is the line number of the first decorator, we want the def |
+ # statement lineno |
+ lineno = self.lineno |
if self.decorators is not None: |
- self.fromlineno += sum(node.tolineno - node.lineno + 1 |
+ lineno += sum(node.tolineno - node.lineno + 1 |
for node in self.decorators.nodes) |
- if self.args.fromlineno < self.fromlineno: |
- self.args.fromlineno = self.fromlineno |
- self.tolineno = lastchild.tolineno |
- self.blockstart_tolineno = self.args.tolineno |
+ |
+ return lineno |
+ |
+ @cachedproperty |
+ def blockstart_tolineno(self): |
+ return self.args.tolineno |
def block_range(self, lineno): |
"""return block line numbers. |
@@ -697,7 +728,7 @@ class Function(Statement, Lambda): |
if self.decorators: |
for node in self.decorators.nodes: |
try: |
- infered = node.infer().next() |
+ infered = next(node.infer()) |
except InferenceError: |
continue |
if infered and infered.qname() in ('abc.abstractproperty', |
@@ -718,17 +749,32 @@ class Function(Statement, Lambda): |
def is_generator(self): |
"""return true if this is a generator function""" |
# XXX should be flagged, not computed |
- try: |
- return self.nodes_of_class((Yield, YieldFrom), |
- skip_klass=(Function, Lambda)).next() |
- except StopIteration: |
- return False |
+ return next(self.nodes_of_class((Yield, YieldFrom), |
+ skip_klass=(Function, Lambda)), False) |
def infer_call_result(self, caller, context=None): |
"""infer what a function is returning when called""" |
if self.is_generator(): |
yield Generator() |
return |
+ # This is really a gigantic hack to work around metaclass generators |
+ # that return transient class-generating functions. Pylint's AST structure |
+ # cannot handle a base class object that is only used for calling __new__, |
+ # but does not contribute to the inheritance structure itself. We inject |
+ # a fake class into the hierarchy here for several well-known metaclass |
+ # generators, and filter it out later. |
+ if (self.name == 'with_metaclass' and |
+ len(self.args.args) == 1 and |
+ self.args.vararg is not None): |
+ metaclass = next(caller.args[0].infer(context)) |
+ if isinstance(metaclass, Class): |
+ c = Class('temporary_class', None) |
+ c.hide = True |
+ c.parent = self |
+ c.bases = [next(b.infer(context)) for b in caller.args[1:]] |
+ c._metaclass = metaclass |
+ yield c |
+ return |
returns = self.nodes_of_class(Return, skip_klass=Function) |
for returnnode in returns: |
if returnnode.value is None: |
@@ -810,7 +856,6 @@ def _class_type(klass, ancestors=None): |
klass._type = 'class' |
return 'class' |
ancestors.add(klass) |
- # print >> sys.stderr, '_class_type', repr(klass) |
for base in klass.ancestors(recurs=False): |
name = _class_type(base, ancestors) |
if name != 'class': |
@@ -845,6 +890,8 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): |
blockstart_tolineno = None |
_type = None |
+ _metaclass_hack = False |
+ hide = False |
type = property(_class_type, |
doc="class'type, possible values are 'class' | " |
"'metaclass' | 'interface' | 'exception'") |
@@ -880,12 +927,12 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): |
doc="boolean indicating if it's a new style class" |
"or not") |
- def set_line_info(self, lastchild): |
- self.fromlineno = self.lineno |
- self.blockstart_tolineno = self.bases and self.bases[-1].tolineno or self.fromlineno |
- if lastchild is not None: |
- self.tolineno = lastchild.tolineno |
- # else this is a class with only a docstring, then tolineno is (should be) already ok |
+ @cachedproperty |
+ def blockstart_tolineno(self): |
+ if self.bases: |
+ return self.bases[-1].tolineno |
+ else: |
+ return self.fromlineno |
def block_range(self, lineno): |
"""return block line numbers. |
@@ -905,24 +952,25 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): |
def callable(self): |
return True |
- def _is_subtype_of(self, type_name): |
+ def is_subtype_of(self, type_name, context=None): |
if self.qname() == type_name: |
return True |
- for anc in self.ancestors(): |
+ for anc in self.ancestors(context=context): |
if anc.qname() == type_name: |
return True |
def infer_call_result(self, caller, context=None): |
"""infer what a class is returning when called""" |
- if self._is_subtype_of('%s.type' % (BUILTINS,)) and len(caller.args) == 3: |
- name_node = caller.args[0].infer().next() |
- if isinstance(name_node, Const) and isinstance(name_node.value, basestring): |
+ if self.is_subtype_of('%s.type' % (BUILTINS,), context) and len(caller.args) == 3: |
+ name_node = next(caller.args[0].infer(context)) |
+ if (isinstance(name_node, Const) and |
+ isinstance(name_node.value, six.string_types)): |
name = name_node.value |
else: |
yield YES |
return |
result = Class(name, None) |
- bases = caller.args[1].infer().next() |
+ bases = next(caller.args[1].infer(context)) |
if isinstance(bases, (Tuple, List)): |
result.bases = bases.itered() |
else: |
@@ -961,34 +1009,39 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): |
ancestors only |
""" |
# FIXME: should be possible to choose the resolution order |
- # XXX inference make infinite loops possible here (see BaseTransformer |
- # manipulation in the builder module for instance) |
+ # FIXME: inference make infinite loops possible here |
yielded = set([self]) |
if context is None: |
context = InferenceContext() |
+ if sys.version_info[0] >= 3: |
+ if not self.bases and self.qname() != 'builtins.object': |
+ yield builtin_lookup("object")[1][0] |
+ return |
+ |
for stmt in self.bases: |
- with context.restore_path(): |
- try: |
- for baseobj in stmt.infer(context): |
- if not isinstance(baseobj, Class): |
- if isinstance(baseobj, Instance): |
- baseobj = baseobj._proxied |
- else: |
- # duh ? |
- continue |
+ try: |
+ for baseobj in stmt.infer(context): |
+ if not isinstance(baseobj, Class): |
+ if isinstance(baseobj, Instance): |
+ baseobj = baseobj._proxied |
+ else: |
+ # duh ? |
+ continue |
+ if not baseobj.hide: |
if baseobj in yielded: |
continue # cf xxx above |
yielded.add(baseobj) |
yield baseobj |
- if recurs: |
- for grandpa in baseobj.ancestors(True, context): |
- if grandpa in yielded: |
- continue # cf xxx above |
- yielded.add(grandpa) |
- yield grandpa |
- except InferenceError: |
- # XXX log error ? |
- continue |
+ if recurs: |
+ for grandpa in baseobj.ancestors(recurs=True, |
+ context=context): |
+ if grandpa in yielded: |
+ continue # cf xxx above |
+ yielded.add(grandpa) |
+ yield grandpa |
+ except InferenceError: |
+ # XXX log error ? |
+ continue |
def local_attr_ancestors(self, name, context=None): |
"""return an iterator on astroid representation of parent classes |
@@ -1083,11 +1136,11 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): |
""" |
# set lookup name since this is necessary to infer on import nodes for |
# instance |
- context = copy_context(context) |
- context.lookupname = name |
+ if not context: |
+ context = InferenceContext() |
try: |
for infered in _infer_stmts(self.getattr(name, context), context, |
- frame=self): |
+ frame=self, lookupname=name): |
# yield YES object instead of descriptors when necessary |
if not isinstance(infered, Const) and isinstance(infered, Instance): |
try: |
@@ -1178,6 +1231,16 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): |
having a ``__metaclass__`` class attribute, or if there are |
no explicit bases but there is a global ``__metaclass__`` variable. |
""" |
+ for base in self.bases: |
+ try: |
+ for baseobj in base.infer(): |
+ if isinstance(baseobj, Class) and baseobj.hide: |
+ self._metaclass = baseobj._metaclass |
+ self._metaclass_hack = True |
+ break |
+ except InferenceError: |
+ pass |
+ |
if self._metaclass: |
# Expects this from Py3k TreeRebuilder |
try: |
@@ -1202,7 +1265,7 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): |
return None |
try: |
- infered = assignment.infer().next() |
+ infered = next(assignment.infer()) |
except InferenceError: |
return |
if infered is YES: # don't expose this |
@@ -1224,6 +1287,9 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): |
break |
return klass |
+ def has_metaclass_hack(self): |
+ return self._metaclass_hack |
+ |
def _islots(self): |
""" Return an iterator with the inferred slots. """ |
if '__slots__' not in self.locals: |