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 |
deleted file mode 100644 |
index eb60298f6a5fe345dbc9766f763beb4c10ba570d..0000000000000000000000000000000000000000 |
--- a/third_party/logilab/astroid/scoped_nodes.py |
+++ /dev/null |
@@ -1,1277 +0,0 @@ |
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
-# |
-# This file is part of astroid. |
-# |
-# astroid is free software: you can redistribute it and/or modify it |
-# under the terms of the GNU Lesser General Public License as published by the |
-# Free Software Foundation, either version 2.1 of the License, or (at your |
-# option) any later version. |
-# |
-# astroid 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 Lesser General Public License |
-# for more details. |
-# |
-# You should have received a copy of the GNU Lesser General Public License along |
-# with astroid. If not, see <http://www.gnu.org/licenses/>. |
-"""This module contains the classes for "scoped" node, i.e. which are opening a |
-new local scope in the language definition : Module, Class, Function (and |
-Lambda, GenExpr, DictComp and SetComp to some extent). |
-""" |
-from __future__ import with_statement |
- |
-__doctype__ = "restructuredtext en" |
- |
-import sys |
-from itertools import chain |
-try: |
- from io import BytesIO |
-except ImportError: |
- from cStringIO import StringIO as BytesIO |
- |
-from logilab.common.compat import builtins |
-from logilab.common.decorators import cached, cachedproperty |
- |
-from astroid.exceptions import NotFoundError, \ |
- AstroidBuildingException, InferenceError |
-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, \ |
- BUILTINS |
-from astroid.mixins import FilterStmtsMixin |
-from astroid.bases import Statement |
-from astroid.manager import AstroidManager |
- |
-ITER_METHODS = ('__iter__', '__getitem__') |
-PY3K = sys.version_info >= (3, 0) |
- |
- |
-def remove_nodes(func, cls): |
- def wrapper(*args, **kwargs): |
- nodes = [n for n in func(*args, **kwargs) if not isinstance(n, cls)] |
- if not nodes: |
- raise NotFoundError() |
- return nodes |
- return wrapper |
- |
- |
-def function_to_method(n, klass): |
- if isinstance(n, Function): |
- if n.type == 'classmethod': |
- return BoundMethod(n, klass) |
- if n.type != 'staticmethod': |
- return UnboundMethod(n) |
- return n |
- |
-def std_special_attributes(self, name, add_locals=True): |
- if add_locals: |
- locals = self.locals |
- else: |
- locals = {} |
- if name == '__name__': |
- return [cf(self.name)] + locals.get(name, []) |
- if name == '__doc__': |
- return [cf(self.doc)] + locals.get(name, []) |
- if name == '__dict__': |
- return [Dict()] + locals.get(name, []) |
- raise NotFoundError(name) |
- |
-MANAGER = AstroidManager() |
-def builtin_lookup(name): |
- """lookup a name into the builtin module |
- return the list of matching statements and the astroid for the builtin |
- module |
- """ |
- builtin_astroid = MANAGER.ast_from_module(builtins) |
- if name == '__dict__': |
- return builtin_astroid, () |
- try: |
- stmts = builtin_astroid.locals[name] |
- except KeyError: |
- stmts = () |
- return builtin_astroid, stmts |
- |
- |
-# TODO move this Mixin to mixins.py; problem: 'Function' in _scope_lookup |
-class LocalsDictNodeNG(LookupMixIn, NodeNG): |
- """ this class provides locals handling common to Module, Function |
- and Class nodes, including a dict like interface for direct access |
- to locals information |
- """ |
- |
- # attributes below are set by the builder module or by raw factories |
- |
- # dictionary of locals with name as key and node defining the local as |
- # value |
- |
- def qname(self): |
- """return the 'qualified' name of the node, eg module.name, |
- module.class.name ... |
- """ |
- if self.parent is None: |
- return self.name |
- return '%s.%s' % (self.parent.frame().qname(), self.name) |
- |
- def frame(self): |
- """return the first parent frame node (i.e. Module, Function or Class) |
- """ |
- return self |
- |
- def scope(self): |
- """return the first node defining a new scope (i.e. Module, |
- Function, Class, Lambda but also GenExpr, DictComp and SetComp) |
- """ |
- return self |
- |
- |
- def _scope_lookup(self, node, name, offset=0): |
- """XXX method for interfacing the scope lookup""" |
- try: |
- stmts = node._filter_stmts(self.locals[name], self, offset) |
- except KeyError: |
- stmts = () |
- if stmts: |
- return self, stmts |
- if self.parent: # i.e. not Module |
- # nested scope: if parent scope is a function, that's fine |
- # else jump to the module |
- pscope = self.parent.scope() |
- if not pscope.is_function: |
- pscope = pscope.root() |
- return pscope.scope_lookup(node, name) |
- return builtin_lookup(name) # Module |
- |
- |
- |
- def set_local(self, name, stmt): |
- """define <name> in locals (<stmt> is the node defining the name) |
- if the node is a Module node (i.e. has globals), add the name to |
- globals |
- |
- if the name is already defined, ignore it |
- """ |
- #assert not stmt in self.locals.get(name, ()), (self, stmt) |
- self.locals.setdefault(name, []).append(stmt) |
- |
- __setitem__ = set_local |
- |
- def _append_node(self, child): |
- """append a child, linking it in the tree""" |
- self.body.append(child) |
- child.parent = self |
- |
- def add_local_node(self, child_node, name=None): |
- """append a child which should alter locals to the given node""" |
- if name != '__class__': |
- # add __class__ node as a child will cause infinite recursion later! |
- self._append_node(child_node) |
- self.set_local(name or child_node.name, child_node) |
- |
- |
- def __getitem__(self, item): |
- """method from the `dict` interface returning the first node |
- associated with the given name in the locals dictionary |
- |
- :type item: str |
- :param item: the name of the locally defined object |
- :raises KeyError: if the name is not defined |
- """ |
- return self.locals[item][0] |
- |
- def __iter__(self): |
- """method from the `dict` interface returning an iterator on |
- `self.keys()` |
- """ |
- return iter(self.keys()) |
- |
- def keys(self): |
- """method from the `dict` interface returning a tuple containing |
- locally defined names |
- """ |
- return self.locals.keys() |
- |
- def values(self): |
- """method from the `dict` interface returning a tuple containing |
- locally defined nodes which are instance of `Function` or `Class` |
- """ |
- return [self[key] for key in self.keys()] |
- |
- def items(self): |
- """method from the `dict` interface returning a list of tuple |
- containing each locally defined name with its associated node, |
- which is an instance of `Function` or `Class` |
- """ |
- return zip(self.keys(), self.values()) |
- |
- |
- def __contains__(self, name): |
- return name in self.locals |
- has_key = __contains__ |
- |
-# Module ##################################################################### |
- |
-class Module(LocalsDictNodeNG): |
- _astroid_fields = ('body',) |
- |
- fromlineno = 0 |
- lineno = 0 |
- |
- # attributes below are set by the builder module or by raw factories |
- |
- # the file from which as been extracted the astroid representation. It may |
- # be None if the representation has been built from a built-in module |
- file = None |
- # Alternatively, if built from a string/bytes, this can be set |
- file_bytes = None |
- # encoding of python source file, so we can get unicode out of it (python2 |
- # only) |
- file_encoding = None |
- # the module name |
- name = None |
- # boolean for astroid built from source (i.e. ast) |
- pure_python = None |
- # boolean for package module |
- package = None |
- # dictionary of globals with name as key and node defining the global |
- # as value |
- globals = None |
- |
- # Future imports |
- future_imports = None |
- |
- # names of python special attributes (handled by getattr impl.) |
- special_attributes = set(('__name__', '__doc__', '__file__', '__path__', |
- '__dict__')) |
- # names of module attributes available through the global scope |
- scope_attrs = set(('__name__', '__doc__', '__file__', '__path__')) |
- |
- def __init__(self, name, doc, pure_python=True): |
- self.name = name |
- self.doc = doc |
- self.pure_python = pure_python |
- self.locals = self.globals = {} |
- self.body = [] |
- self.future_imports = set() |
- |
- @property |
- def file_stream(self): |
- if self.file_bytes is not None: |
- return BytesIO(self.file_bytes) |
- if self.file is not None: |
- return open(self.file, 'rb') |
- return None |
- |
- def block_range(self, lineno): |
- """return block line numbers. |
- |
- start from the beginning whatever the given lineno |
- """ |
- return self.fromlineno, self.tolineno |
- |
- def scope_lookup(self, node, name, offset=0): |
- if name in self.scope_attrs and not name in self.locals: |
- try: |
- return self, self.getattr(name) |
- except NotFoundError: |
- return self, () |
- return self._scope_lookup(node, name, offset) |
- |
- def pytype(self): |
- return '%s.module' % BUILTINS |
- |
- def display_type(self): |
- return 'Module' |
- |
- def getattr(self, name, context=None, ignore_locals=False): |
- if name in self.special_attributes: |
- if name == '__file__': |
- return [cf(self.file)] + self.locals.get(name, []) |
- if name == '__path__' and self.package: |
- return [List()] + self.locals.get(name, []) |
- return std_special_attributes(self, name) |
- if not ignore_locals and name in self.locals: |
- return self.locals[name] |
- if self.package: |
- try: |
- return [self.import_module(name, relative_only=True)] |
- except AstroidBuildingException: |
- raise NotFoundError(name) |
- except SyntaxError: |
- raise NotFoundError(name) |
- except Exception:# XXX pylint tests never pass here; do we need it? |
- import traceback |
- traceback.print_exc() |
- raise NotFoundError(name) |
- getattr = remove_nodes(getattr, DelName) |
- |
- def igetattr(self, name, context=None): |
- """inferred getattr""" |
- # set lookup name since this is necessary to infer on import nodes for |
- # instance |
- context = copy_context(context) |
- context.lookupname = name |
- try: |
- return _infer_stmts(self.getattr(name, context), context, frame=self) |
- except NotFoundError: |
- raise InferenceError(name) |
- |
- def fully_defined(self): |
- """return True if this module has been built from a .py file |
- and so contains a complete representation including the code |
- """ |
- return self.file is not None and self.file.endswith('.py') |
- |
- def statement(self): |
- """return the first parent node marked as statement node |
- consider a module as a statement... |
- """ |
- return self |
- |
- def previous_sibling(self): |
- """module has no sibling""" |
- return |
- |
- def next_sibling(self): |
- """module has no sibling""" |
- return |
- |
- if sys.version_info < (2, 8): |
- 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 |
- |
- def import_module(self, modname, relative_only=False, level=None): |
- """import the given module considering self as context""" |
- if relative_only and level is None: |
- level = 0 |
- absmodname = self.relative_to_absolute_name(modname, level) |
- try: |
- return MANAGER.ast_from_module_name(absmodname) |
- except AstroidBuildingException: |
- # we only want to import a sub module or package of this module, |
- # skip here |
- if relative_only: |
- raise |
- return MANAGER.ast_from_module_name(modname) |
- |
- def relative_to_absolute_name(self, modname, level): |
- """return the absolute module name for a relative import. |
- |
- The relative import can be implicit or explicit. |
- """ |
- # XXX this returns non sens when called on an absolute import |
- # like 'pylint.checkers.astroid.utils' |
- # XXX doesn't return absolute name if self.name isn't absolute name |
- if self.absolute_import_activated() and level is None: |
- return modname |
- if level: |
- if self.package: |
- level = level - 1 |
- package_name = self.name.rsplit('.', level)[0] |
- elif self.package: |
- package_name = self.name |
- else: |
- package_name = self.name.rsplit('.', 1)[0] |
- if package_name: |
- if not modname: |
- return package_name |
- return '%s.%s' % (package_name, modname) |
- return modname |
- |
- |
- def wildcard_import_names(self): |
- """return the list of imported names when this module is 'wildcard |
- imported' |
- |
- It doesn't include the '__builtins__' name which is added by the |
- current CPython implementation of wildcard imports. |
- """ |
- # take advantage of a living module if it exists |
- try: |
- living = sys.modules[self.name] |
- except KeyError: |
- pass |
- else: |
- try: |
- return living.__all__ |
- except AttributeError: |
- return [name for name in living.__dict__.keys() |
- if not name.startswith('_')] |
- # else lookup the astroid |
- # |
- # We separate the different steps of lookup in try/excepts |
- # to avoid catching too many Exceptions |
- # However, we can not analyse dynamically constructed __all__ |
- try: |
- all = self['__all__'] |
- except KeyError: |
- return [name for name in self.keys() if not name.startswith('_')] |
- try: |
- explicit = all.assigned_stmts().next() |
- except InferenceError: |
- return [name for name in self.keys() if not name.startswith('_')] |
- except AttributeError: |
- # not an assignment node |
- # XXX infer? |
- return [name for name in self.keys() if not name.startswith('_')] |
- 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('_')] |
- |
- |
-class ComprehensionScope(LocalsDictNodeNG): |
- def frame(self): |
- return self.parent.frame() |
- |
- scope_lookup = LocalsDictNodeNG._scope_lookup |
- |
- |
-class GenExpr(ComprehensionScope): |
- _astroid_fields = ('elt', 'generators') |
- |
- def __init__(self): |
- self.locals = {} |
- self.elt = None |
- self.generators = [] |
- |
- |
-class DictComp(ComprehensionScope): |
- _astroid_fields = ('key', 'value', 'generators') |
- |
- def __init__(self): |
- self.locals = {} |
- self.key = None |
- self.value = None |
- self.generators = [] |
- |
- |
-class SetComp(ComprehensionScope): |
- _astroid_fields = ('elt', 'generators') |
- |
- def __init__(self): |
- self.locals = {} |
- self.elt = None |
- self.generators = [] |
- |
- |
-class _ListComp(NodeNG): |
- """class representing a ListComp node""" |
- _astroid_fields = ('elt', 'generators') |
- elt = None |
- generators = None |
- |
-if sys.version_info >= (3, 0): |
- class ListComp(_ListComp, ComprehensionScope): |
- """class representing a ListComp node""" |
- def __init__(self): |
- self.locals = {} |
-else: |
- class ListComp(_ListComp): |
- """class representing a ListComp node""" |
- |
-# Function ################################################################### |
- |
-def _infer_decorator_callchain(node): |
- """ Detect decorator call chaining and see if the |
- end result is a static or a classmethod. |
- """ |
- current = node |
- while True: |
- if isinstance(current, CallFunc): |
- try: |
- current = current.func.infer().next() |
- except InferenceError: |
- return |
- elif isinstance(current, Function): |
- if not current.parent: |
- return |
- try: |
- # 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() |
- except (StopIteration, InferenceError): |
- return |
- if isinstance(result, (Function, CallFunc)): |
- current = result |
- else: |
- if isinstance(result, Instance): |
- result = result._proxied |
- if isinstance(result, Class): |
- if (result.name == 'classmethod' and |
- result.root().name == BUILTINS): |
- return 'classmethod' |
- elif (result.name == 'staticmethod' and |
- result.root().name == BUILTINS): |
- return 'staticmethod' |
- else: |
- return |
- else: |
- # We aren't interested in anything else returned, |
- # so go back to the function type inference. |
- return |
- else: |
- return |
- |
-def _function_type(self): |
- """ |
- Function type, possible values are: |
- method, function, staticmethod, classmethod. |
- """ |
- # Can't infer that this node is decorated |
- # with a subclass of `classmethod` where `type` is first set, |
- # so do it here. |
- if self.decorators: |
- for node in self.decorators.nodes: |
- if isinstance(node, CallFunc): |
- _type = _infer_decorator_callchain(node) |
- if _type is None: |
- continue |
- else: |
- return _type |
- if not isinstance(node, Name): |
- continue |
- try: |
- for infered in node.infer(): |
- if not isinstance(infered, Class): |
- continue |
- for ancestor in infered.ancestors(): |
- if isinstance(ancestor, Class): |
- if (ancestor.name == 'classmethod' and |
- ancestor.root().name == BUILTINS): |
- return 'classmethod' |
- elif (ancestor.name == 'staticmethod' and |
- ancestor.root().name == BUILTINS): |
- return 'staticmethod' |
- except InferenceError: |
- pass |
- return self._type |
- |
- |
-class Lambda(LocalsDictNodeNG, FilterStmtsMixin): |
- _astroid_fields = ('args', 'body',) |
- name = '<lambda>' |
- |
- # function's type, 'function' | 'method' | 'staticmethod' | 'classmethod' |
- type = 'function' |
- |
- def __init__(self): |
- self.locals = {} |
- self.args = [] |
- self.body = [] |
- |
- def pytype(self): |
- if 'method' in self.type: |
- return '%s.instancemethod' % BUILTINS |
- return '%s.function' % BUILTINS |
- |
- def display_type(self): |
- if 'method' in self.type: |
- return 'Method' |
- return 'Function' |
- |
- def callable(self): |
- return True |
- |
- def argnames(self): |
- """return a list of argument names""" |
- if self.args.args: # maybe None with builtin functions |
- names = _rec_get_names(self.args.args) |
- else: |
- names = [] |
- if self.args.vararg: |
- names.append(self.args.vararg) |
- if self.args.kwarg: |
- names.append(self.args.kwarg) |
- return names |
- |
- def infer_call_result(self, caller, context=None): |
- """infer what a function is returning when called""" |
- return self.body.infer(context) |
- |
- def scope_lookup(self, node, name, offset=0): |
- if node in self.args.defaults or node in self.args.kw_defaults: |
- frame = self.parent.frame() |
- # line offset to avoid that def func(f=func) resolve the default |
- # value to the defined function |
- offset = -1 |
- else: |
- # check this is not used in function decorators |
- frame = self |
- return frame._scope_lookup(node, name, offset) |
- |
- |
-class Function(Statement, Lambda): |
- if PY3K: |
- _astroid_fields = ('decorators', 'args', 'body', 'returns') |
- returns = None |
- else: |
- _astroid_fields = ('decorators', 'args', 'body') |
- |
- special_attributes = set(('__name__', '__doc__', '__dict__')) |
- is_function = True |
- # attributes below are set by the builder module or by raw factories |
- blockstart_tolineno = None |
- decorators = None |
- _type = "function" |
- type = cachedproperty(_function_type) |
- |
- def __init__(self, name, doc): |
- 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 |
- if self.decorators is not None: |
- self.fromlineno += 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 |
- |
- def block_range(self, lineno): |
- """return block line numbers. |
- |
- start from the "def" position whatever the given lineno |
- """ |
- return self.fromlineno, self.tolineno |
- |
- def getattr(self, name, context=None): |
- """this method doesn't look in the instance_attrs dictionary since it's |
- done by an Instance proxy at inference time. |
- """ |
- if name == '__module__': |
- return [cf(self.root().qname())] |
- if name in self.instance_attrs: |
- return self.instance_attrs[name] |
- return std_special_attributes(self, name, False) |
- |
- def is_method(self): |
- """return true if the function node should be considered as a method""" |
- # check we are defined in a Class, because this is usually expected |
- # (e.g. pylint...) when is_method() return True |
- return self.type != 'function' and isinstance(self.parent.frame(), Class) |
- |
- def decoratornames(self): |
- """return a list of decorator qualified names""" |
- result = set() |
- decoratornodes = [] |
- if self.decorators is not None: |
- decoratornodes += self.decorators.nodes |
- decoratornodes += self.extra_decorators |
- for decnode in decoratornodes: |
- for infnode in decnode.infer(): |
- result.add(infnode.qname()) |
- return result |
- decoratornames = cached(decoratornames) |
- |
- def is_bound(self): |
- """return true if the function is bound to an Instance or a class""" |
- return self.type == 'classmethod' |
- |
- def is_abstract(self, pass_is_abstract=True): |
- """Returns True if the method is abstract. |
- |
- A method is considered abstract if |
- - the only statement is 'raise NotImplementedError', or |
- - the only statement is 'pass' and pass_is_abstract is True, or |
- - the method is annotated with abc.astractproperty/abc.abstractmethod |
- """ |
- if self.decorators: |
- for node in self.decorators.nodes: |
- try: |
- infered = node.infer().next() |
- except InferenceError: |
- continue |
- if infered and infered.qname() in ('abc.abstractproperty', |
- 'abc.abstractmethod'): |
- return True |
- |
- for child_node in self.body: |
- if isinstance(child_node, Raise): |
- if child_node.raises_not_implemented(): |
- return True |
- if pass_is_abstract and isinstance(child_node, Pass): |
- return True |
- return False |
- # empty function is the same as function with a single "pass" statement |
- if pass_is_abstract: |
- return True |
- |
- 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 |
- |
- def infer_call_result(self, caller, context=None): |
- """infer what a function is returning when called""" |
- if self.is_generator(): |
- yield Generator() |
- return |
- returns = self.nodes_of_class(Return, skip_klass=Function) |
- for returnnode in returns: |
- if returnnode.value is None: |
- yield Const(None) |
- else: |
- try: |
- for infered in returnnode.value.infer(context): |
- yield infered |
- except InferenceError: |
- yield YES |
- |
- |
-def _rec_get_names(args, names=None): |
- """return a list of all argument names""" |
- if names is None: |
- names = [] |
- for arg in args: |
- if isinstance(arg, Tuple): |
- _rec_get_names(arg.elts, names) |
- else: |
- names.append(arg.name) |
- return names |
- |
- |
-# Class ###################################################################### |
- |
- |
-def _is_metaclass(klass, seen=None): |
- """ Return if the given class can be |
- used as a metaclass. |
- """ |
- if klass.name == 'type': |
- return True |
- if seen is None: |
- seen = set() |
- for base in klass.bases: |
- try: |
- for baseobj in base.infer(): |
- if baseobj in seen: |
- continue |
- else: |
- seen.add(baseobj) |
- if isinstance(baseobj, Instance): |
- # not abstract |
- return False |
- if baseobj is YES: |
- continue |
- if baseobj is klass: |
- continue |
- if not isinstance(baseobj, Class): |
- continue |
- if baseobj._type == 'metaclass': |
- return True |
- if _is_metaclass(baseobj, seen): |
- return True |
- except InferenceError: |
- continue |
- return False |
- |
- |
-def _class_type(klass, ancestors=None): |
- """return a Class node type to differ metaclass, interface and exception |
- from 'regular' classes |
- """ |
- # XXX we have to store ancestors in case we have a ancestor loop |
- if klass._type is not None: |
- return klass._type |
- if _is_metaclass(klass): |
- klass._type = 'metaclass' |
- elif klass.name.endswith('Interface'): |
- klass._type = 'interface' |
- elif klass.name.endswith('Exception'): |
- klass._type = 'exception' |
- else: |
- if ancestors is None: |
- ancestors = set() |
- if klass in ancestors: |
- # XXX we are in loop ancestors, and have found no type |
- 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': |
- if name == 'metaclass' and not _is_metaclass(klass): |
- # don't propagate it if the current class |
- # can't be a metaclass |
- continue |
- klass._type = base.type |
- break |
- if klass._type is None: |
- klass._type = 'class' |
- return klass._type |
- |
-def _iface_hdlr(iface_node): |
- """a handler function used by interfaces to handle suspicious |
- interface nodes |
- """ |
- return True |
- |
- |
-class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): |
- |
- # some of the attributes below are set by the builder module or |
- # by a raw factories |
- |
- # a dictionary of class instances attributes |
- _astroid_fields = ('decorators', 'bases', 'body') # name |
- |
- decorators = None |
- special_attributes = set(('__name__', '__doc__', '__dict__', '__module__', |
- '__bases__', '__mro__', '__subclasses__')) |
- blockstart_tolineno = None |
- |
- _type = None |
- type = property(_class_type, |
- doc="class'type, possible values are 'class' | " |
- "'metaclass' | 'interface' | 'exception'") |
- |
- def __init__(self, name, doc): |
- self.instance_attrs = {} |
- self.locals = {} |
- self.bases = [] |
- self.body = [] |
- self.name = name |
- self.doc = doc |
- |
- def _newstyle_impl(self, context=None): |
- if context is None: |
- context = InferenceContext() |
- if self._newstyle is not None: |
- return self._newstyle |
- for base in self.ancestors(recurs=False, context=context): |
- if base._newstyle_impl(context): |
- self._newstyle = True |
- break |
- klass = self._explicit_metaclass() |
- # could be any callable, we'd need to infer the result of klass(name, |
- # bases, dict). punt if it's not a class node. |
- if klass is not None and isinstance(klass, Class): |
- self._newstyle = klass._newstyle_impl(context) |
- if self._newstyle is None: |
- self._newstyle = False |
- return self._newstyle |
- |
- _newstyle = None |
- newstyle = property(_newstyle_impl, |
- 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 |
- |
- def block_range(self, lineno): |
- """return block line numbers. |
- |
- start from the "class" position whatever the given lineno |
- """ |
- return self.fromlineno, self.tolineno |
- |
- def pytype(self): |
- if self.newstyle: |
- return '%s.type' % BUILTINS |
- return '%s.classobj' % BUILTINS |
- |
- def display_type(self): |
- return 'Class' |
- |
- def callable(self): |
- return True |
- |
- def _is_subtype_of(self, type_name): |
- if self.qname() == type_name: |
- return True |
- for anc in self.ancestors(): |
- 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): |
- name = name_node.value |
- else: |
- yield YES |
- return |
- result = Class(name, None) |
- bases = caller.args[1].infer().next() |
- if isinstance(bases, (Tuple, List)): |
- result.bases = bases.itered() |
- else: |
- # There is currently no AST node that can represent an 'unknown' |
- # node (YES is not an AST node), therefore we simply return YES here |
- # although we know at least the name of the class. |
- yield YES |
- return |
- result.parent = caller.parent |
- yield result |
- else: |
- yield Instance(self) |
- |
- def scope_lookup(self, node, name, offset=0): |
- if node in self.bases: |
- frame = self.parent.frame() |
- # line offset to avoid that class A(A) resolve the ancestor to |
- # the defined class |
- offset = -1 |
- else: |
- frame = self |
- return frame._scope_lookup(node, name, offset) |
- |
- # list of parent class as a list of string (i.e. names as they appear |
- # in the class definition) XXX bw compat |
- def basenames(self): |
- return [bnode.as_string() for bnode in self.bases] |
- basenames = property(basenames) |
- |
- def ancestors(self, recurs=True, context=None): |
- """return an iterator on the node base classes in a prefixed |
- depth first order |
- |
- :param recurs: |
- boolean indicating if it should recurse or return direct |
- 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) |
- yielded = set([self]) |
- if context is None: |
- context = InferenceContext() |
- 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 |
- 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 |
- |
- def local_attr_ancestors(self, name, context=None): |
- """return an iterator on astroid representation of parent classes |
- which have <name> defined in their locals |
- """ |
- for astroid in self.ancestors(context=context): |
- if name in astroid: |
- yield astroid |
- |
- def instance_attr_ancestors(self, name, context=None): |
- """return an iterator on astroid representation of parent classes |
- which have <name> defined in their instance attribute dictionary |
- """ |
- for astroid in self.ancestors(context=context): |
- if name in astroid.instance_attrs: |
- yield astroid |
- |
- def has_base(self, node): |
- return node in self.bases |
- |
- def local_attr(self, name, context=None): |
- """return the list of assign node associated to name in this class |
- locals or in its parents |
- |
- :raises `NotFoundError`: |
- if no attribute with this name has been find in this class or |
- its parent classes |
- """ |
- try: |
- return self.locals[name] |
- except KeyError: |
- # get if from the first parent implementing it if any |
- for class_node in self.local_attr_ancestors(name, context): |
- return class_node.locals[name] |
- raise NotFoundError(name) |
- local_attr = remove_nodes(local_attr, DelAttr) |
- |
- def instance_attr(self, name, context=None): |
- """return the astroid nodes associated to name in this class instance |
- attributes dictionary and in its parents |
- |
- :raises `NotFoundError`: |
- if no attribute with this name has been find in this class or |
- its parent classes |
- """ |
- # Return a copy, so we don't modify self.instance_attrs, |
- # which could lead to infinite loop. |
- values = list(self.instance_attrs.get(name, [])) |
- # get all values from parents |
- for class_node in self.instance_attr_ancestors(name, context): |
- values += class_node.instance_attrs[name] |
- if not values: |
- raise NotFoundError(name) |
- return values |
- instance_attr = remove_nodes(instance_attr, DelAttr) |
- |
- def instanciate_class(self): |
- """return Instance of Class node, else return self""" |
- return Instance(self) |
- |
- def getattr(self, name, context=None): |
- """this method doesn't look in the instance_attrs dictionary since it's |
- done by an Instance proxy at inference time. |
- |
- It may return a YES object if the attribute has not been actually |
- found but a __getattr__ or __getattribute__ method is defined |
- """ |
- values = self.locals.get(name, []) |
- if name in self.special_attributes: |
- if name == '__module__': |
- return [cf(self.root().qname())] + values |
- # FIXME: do we really need the actual list of ancestors? |
- # returning [Tuple()] + values don't break any test |
- # this is ticket http://www.logilab.org/ticket/52785 |
- # XXX need proper meta class handling + MRO implementation |
- if name == '__bases__' or (name == '__mro__' and self.newstyle): |
- node = Tuple() |
- node.items = self.ancestors(recurs=True, context=context) |
- return [node] + values |
- return std_special_attributes(self, name) |
- # don't modify the list in self.locals! |
- values = list(values) |
- for classnode in self.ancestors(recurs=True, context=context): |
- values += classnode.locals.get(name, []) |
- if not values: |
- raise NotFoundError(name) |
- return values |
- |
- def igetattr(self, name, context=None): |
- """inferred getattr, need special treatment in class to handle |
- descriptors |
- """ |
- # set lookup name since this is necessary to infer on import nodes for |
- # instance |
- context = copy_context(context) |
- context.lookupname = name |
- try: |
- for infered in _infer_stmts(self.getattr(name, context), context, |
- frame=self): |
- # yield YES object instead of descriptors when necessary |
- if not isinstance(infered, Const) and isinstance(infered, Instance): |
- try: |
- infered._proxied.getattr('__get__', context) |
- except NotFoundError: |
- yield infered |
- else: |
- yield YES |
- else: |
- yield function_to_method(infered, self) |
- except NotFoundError: |
- if not name.startswith('__') and self.has_dynamic_getattr(context): |
- # class handle some dynamic attributes, return a YES object |
- yield YES |
- else: |
- raise InferenceError(name) |
- |
- def has_dynamic_getattr(self, context=None): |
- """return True if the class has a custom __getattr__ or |
- __getattribute__ method |
- """ |
- # need to explicitly handle optparse.Values (setattr is not detected) |
- if self.name == 'Values' and self.root().name == 'optparse': |
- return True |
- try: |
- self.getattr('__getattr__', context) |
- return True |
- except NotFoundError: |
- #if self.newstyle: XXX cause an infinite recursion error |
- try: |
- getattribute = self.getattr('__getattribute__', context)[0] |
- if getattribute.root().name != BUILTINS: |
- # class has a custom __getattribute__ defined |
- return True |
- except NotFoundError: |
- pass |
- return False |
- |
- def methods(self): |
- """return an iterator on all methods defined in the class and |
- its ancestors |
- """ |
- done = {} |
- for astroid in chain(iter((self,)), self.ancestors()): |
- for meth in astroid.mymethods(): |
- if meth.name in done: |
- continue |
- done[meth.name] = None |
- yield meth |
- |
- def mymethods(self): |
- """return an iterator on all methods defined in the class""" |
- for member in self.values(): |
- if isinstance(member, Function): |
- yield member |
- |
- def interfaces(self, herited=True, handler_func=_iface_hdlr): |
- """return an iterator on interfaces implemented by the given |
- class node |
- """ |
- # FIXME: what if __implements__ = (MyIFace, MyParent.__implements__)... |
- try: |
- implements = Instance(self).getattr('__implements__')[0] |
- except NotFoundError: |
- return |
- if not herited and not implements.frame() is self: |
- return |
- found = set() |
- missing = False |
- for iface in unpack_infer(implements): |
- if iface is YES: |
- missing = True |
- continue |
- if not iface in found and handler_func(iface): |
- found.add(iface) |
- yield iface |
- if missing: |
- raise InferenceError() |
- |
- _metaclass = None |
- def _explicit_metaclass(self): |
- """ Return the explicit defined metaclass |
- for the current class. |
- |
- An explicit defined metaclass is defined |
- either by passing the ``metaclass`` keyword argument |
- in the class definition line (Python 3) or (Python 2) by |
- having a ``__metaclass__`` class attribute, or if there are |
- no explicit bases but there is a global ``__metaclass__`` variable. |
- """ |
- if self._metaclass: |
- # Expects this from Py3k TreeRebuilder |
- try: |
- return next(node for node in self._metaclass.infer() |
- if node is not YES) |
- except (InferenceError, StopIteration): |
- return None |
- if sys.version_info >= (3, ): |
- return None |
- |
- if '__metaclass__' in self.locals: |
- assignment = self.locals['__metaclass__'][-1] |
- elif self.bases: |
- return None |
- elif '__metaclass__' in self.root().locals: |
- assignments = [ass for ass in self.root().locals['__metaclass__'] |
- if ass.lineno < self.lineno] |
- if not assignments: |
- return None |
- assignment = assignments[-1] |
- else: |
- return None |
- |
- try: |
- infered = assignment.infer().next() |
- except InferenceError: |
- return |
- if infered is YES: # don't expose this |
- return None |
- return infered |
- |
- def metaclass(self): |
- """ Return the metaclass of this class. |
- |
- If this class does not define explicitly a metaclass, |
- then the first defined metaclass in ancestors will be used |
- instead. |
- """ |
- klass = self._explicit_metaclass() |
- if klass is None: |
- for parent in self.ancestors(): |
- klass = parent.metaclass() |
- if klass is not None: |
- break |
- return klass |
- |
- def _islots(self): |
- """ Return an iterator with the inferred slots. """ |
- if '__slots__' not in self.locals: |
- return |
- for slots in self.igetattr('__slots__'): |
- # check if __slots__ is a valid type |
- for meth in ITER_METHODS: |
- try: |
- slots.getattr(meth) |
- break |
- except NotFoundError: |
- continue |
- else: |
- continue |
- |
- if isinstance(slots, Const): |
- # a string. Ignore the following checks, |
- # but yield the node, only if it has a value |
- if slots.value: |
- yield slots |
- continue |
- if not hasattr(slots, 'itered'): |
- # we can't obtain the values, maybe a .deque? |
- continue |
- |
- if isinstance(slots, Dict): |
- values = [item[0] for item in slots.items] |
- else: |
- values = slots.itered() |
- if values is YES: |
- continue |
- |
- for elt in values: |
- try: |
- for infered in elt.infer(): |
- if infered is YES: |
- continue |
- if (not isinstance(infered, Const) or |
- not isinstance(infered.value, str)): |
- continue |
- if not infered.value: |
- continue |
- yield infered |
- except InferenceError: |
- continue |
- |
- # Cached, because inferring them all the time is expensive |
- @cached |
- def slots(self): |
- """ Return all the slots for this node. """ |
- return list(self._islots()) |