summaryrefslogtreecommitdiff
path: root/venv/Lib/site-packages/astroid/scoped_nodes.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/Lib/site-packages/astroid/scoped_nodes.py')
-rw-r--r--venv/Lib/site-packages/astroid/scoped_nodes.py2836
1 files changed, 2836 insertions, 0 deletions
diff --git a/venv/Lib/site-packages/astroid/scoped_nodes.py b/venv/Lib/site-packages/astroid/scoped_nodes.py
new file mode 100644
index 0000000..d02b653
--- /dev/null
+++ b/venv/Lib/site-packages/astroid/scoped_nodes.py
@@ -0,0 +1,2836 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
+# Copyright (c) 2010 Daniel Harding <dharding@gmail.com>
+# Copyright (c) 2011, 2013-2015 Google, Inc.
+# Copyright (c) 2013-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2013 Phil Schaf <flying-sheep@web.de>
+# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
+# Copyright (c) 2015-2016 Florian Bruhin <me@the-compiler.org>
+# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
+# Copyright (c) 2015 Rene Zhang <rz99@cornell.edu>
+# Copyright (c) 2015 Philip Lorenz <philip@bithub.de>
+# Copyright (c) 2016-2017 Derek Gustafson <degustaf@gmail.com>
+# Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
+# Copyright (c) 2017-2018 Ashley Whetter <ashley@awhetter.co.uk>
+# Copyright (c) 2017 Ɓukasz Rogalski <rogalski.91@gmail.com>
+# Copyright (c) 2017 David Euresti <david@dropbox.com>
+# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
+# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
+
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
+
+
+"""
+This module contains the classes for "scoped" node, i.e. which are opening a
+new local scope in the language definition : Module, ClassDef, FunctionDef (and
+Lambda, GeneratorExp, DictComp and SetComp to some extent).
+"""
+
+import builtins
+import sys
+import io
+import itertools
+from typing import Optional, List
+
+from astroid import bases
+from astroid import context as contextmod
+from astroid import exceptions
+from astroid import decorators as decorators_mod
+from astroid.interpreter import objectmodel
+from astroid.interpreter import dunder_lookup
+from astroid import manager
+from astroid import mixins
+from astroid import node_classes
+from astroid import util
+
+
+BUILTINS = builtins.__name__
+ITER_METHODS = ("__iter__", "__getitem__")
+EXCEPTION_BASE_CLASSES = frozenset({"Exception", "BaseException"})
+objects = util.lazy_import("objects")
+
+
+def _c3_merge(sequences, cls, context):
+ """Merges MROs in *sequences* to a single MRO using the C3 algorithm.
+
+ Adapted from http://www.python.org/download/releases/2.3/mro/.
+
+ """
+ result = []
+ while True:
+ sequences = [s for s in sequences if s] # purge empty sequences
+ if not sequences:
+ return result
+ for s1 in sequences: # find merge candidates among seq heads
+ candidate = s1[0]
+ for s2 in sequences:
+ if candidate in s2[1:]:
+ candidate = None
+ break # reject the current head, it appears later
+ else:
+ break
+ if not candidate:
+ # Show all the remaining bases, which were considered as
+ # candidates for the next mro sequence.
+ raise exceptions.InconsistentMroError(
+ message="Cannot create a consistent method resolution order "
+ "for MROs {mros} of class {cls!r}.",
+ mros=sequences,
+ cls=cls,
+ context=context,
+ )
+
+ result.append(candidate)
+ # remove the chosen candidate
+ for seq in sequences:
+ if seq[0] == candidate:
+ del seq[0]
+ return None
+
+
+def clean_duplicates_mro(sequences, cls, context):
+ for sequence in sequences:
+ names = [
+ (node.lineno, node.qname()) if node.name else None for node in sequence
+ ]
+ last_index = dict(map(reversed, enumerate(names)))
+ if names and names[0] is not None and last_index[names[0]] != 0:
+ raise exceptions.DuplicateBasesError(
+ message="Duplicates found in MROs {mros} for {cls!r}.",
+ mros=sequences,
+ cls=cls,
+ context=context,
+ )
+ yield [
+ node
+ for i, (node, name) in enumerate(zip(sequence, names))
+ if name is None or last_index[name] == i
+ ]
+
+
+def function_to_method(n, klass):
+ if isinstance(n, FunctionDef):
+ if n.type == "classmethod":
+ return bases.BoundMethod(n, klass)
+ if n.type != "staticmethod":
+ return bases.UnboundMethod(n)
+ return n
+
+
+MANAGER = 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: 'FunctionDef' in _scope_lookup
+class LocalsDictNodeNG(node_classes.LookupMixIn, node_classes.NodeNG):
+ """ this class provides locals handling common to Module, FunctionDef
+ and ClassDef nodes, including a dict like interface for direct access
+ to locals information
+ """
+
+ # attributes below are set by the builder module or by raw factories
+
+ locals = {}
+ """A map of the name of a local variable to the node defining the local.
+
+ :type: dict(str, NodeNG)
+ """
+
+ def qname(self):
+ """Get the 'qualified' name of the node.
+
+ For example: module.name, module.class.name ...
+
+ :returns: The qualified name.
+ :rtype: str
+ """
+ # pylint: disable=no-member; github.com/pycqa/astroid/issues/278
+ if self.parent is None:
+ return self.name
+ return "%s.%s" % (self.parent.frame().qname(), self.name)
+
+ def frame(self):
+ """The first parent frame node.
+
+ A frame node is a :class:`Module`, :class:`FunctionDef`,
+ or :class:`ClassDef`.
+
+ :returns: The first parent frame node.
+ :rtype: Module or FunctionDef or ClassDef
+ """
+ return self
+
+ def scope(self):
+ """The first parent node defining a new scope.
+
+ :returns: The first parent scope node.
+ :rtype: Module or FunctionDef or ClassDef or Lambda or GenExpr
+ """
+ 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 that the given name is declared in the given statement node.
+
+ .. seealso:: :meth:`scope`
+
+ :param name: The name that is being defined.
+ :type name: str
+
+ :param stmt: The statement that defines the given name.
+ :type stmt: NodeNG
+ """
+ # 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"""
+ # pylint: disable=no-member; depending by the class
+ # which uses the current class as a mixin or base class.
+ # It's rewritten in 2.0, so it makes no sense for now
+ # to spend development time on it.
+ self.body.append(child)
+ child.parent = self
+
+ def add_local_node(self, child_node, name=None):
+ """Append a child that should alter the locals of this scope node.
+
+ :param child_node: The child node that will alter locals.
+ :type child_node: NodeNG
+
+ :param name: The name of the local that will be altered by
+ the given child node.
+ :type name: str or None
+ """
+ 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):
+ """The first node the defines the given local.
+
+ :param item: The name of the locally defined object.
+ :type item: str
+
+ :raises KeyError: If the name is not defined.
+ """
+ return self.locals[item][0]
+
+ def __iter__(self):
+ """Iterate over the names of locals defined in this scoped node.
+
+ :returns: The names of the defined locals.
+ :rtype: iterable(str)
+ """
+ return iter(self.keys())
+
+ def keys(self):
+ """The names of locals defined in this scoped node.
+
+ :returns: The names of the defined locals.
+ :rtype: list(str)
+ """
+ return list(self.locals.keys())
+
+ def values(self):
+ """The nodes that define the locals in this scoped node.
+
+ :returns: The nodes that define locals.
+ :rtype: list(NodeNG)
+ """
+ return [self[key] for key in self.keys()]
+
+ def items(self):
+ """Get the names of the locals and the node that defines the local.
+
+ :returns: The names of locals and their associated node.
+ :rtype: list(tuple(str, NodeNG))
+ """
+ return list(zip(self.keys(), self.values()))
+
+ def __contains__(self, name):
+ """Check if a local is defined in this scope.
+
+ :param name: The name of the local to check for.
+ :type name: str
+
+ :returns: True if this node has a local of the given name,
+ False otherwise.
+ :rtype: bool
+ """
+ return name in self.locals
+
+
+class Module(LocalsDictNodeNG):
+ """Class representing an :class:`ast.Module` node.
+
+ >>> node = astroid.extract_node('import astroid')
+ >>> node
+ <Import l.1 at 0x7f23b2e4e5c0>
+ >>> node.parent
+ <Module l.0 at 0x7f23b2e4eda0>
+ """
+
+ _astroid_fields = ("body",)
+
+ fromlineno = 0
+ """The first line that this node appears on in the source code.
+
+ :type: int or None
+ """
+ lineno = 0
+ """The line that this node appears on in the source code.
+
+ :type: int or None
+ """
+
+ # attributes below are set by the builder module or by raw factories
+
+ file = None
+ """The path to the file that this ast has been extracted from.
+
+ This will be ``None`` when the representation has been built from a
+ built-in module.
+
+ :type: str or None
+ """
+ file_bytes = None
+ """The string/bytes that this ast was built from.
+
+ :type: str or bytes or None
+ """
+ file_encoding = None
+ """The encoding of the source file.
+
+ This is used to get unicode out of a source file.
+ Python 2 only.
+
+ :type: str or None
+ """
+ name = None
+ """The name of the module.
+
+ :type: str or None
+ """
+ pure_python = None
+ """Whether the ast was built from source.
+
+ :type: bool or None
+ """
+ package = None
+ """Whether the node represents a package or a module.
+
+ :type: bool or None
+ """
+ globals = None
+ """A map of the name of a global variable to the node defining the global.
+
+ :type: dict(str, NodeNG)
+ """
+
+ # Future imports
+ future_imports = None
+ """The imports from ``__future__``.
+
+ :type: set(str) or None
+ """
+ special_attributes = objectmodel.ModuleModel()
+ """The names of special attributes that this module has.
+
+ :type: objectmodel.ModuleModel
+ """
+
+ # names of module attributes available through the global scope
+ scope_attrs = {"__name__", "__doc__", "__file__", "__path__", "__package__"}
+ """The names of module attributes available through the global scope.
+
+ :type: str(str)
+ """
+
+ _other_fields = (
+ "name",
+ "doc",
+ "file",
+ "path",
+ "package",
+ "pure_python",
+ "future_imports",
+ )
+ _other_other_fields = ("locals", "globals")
+
+ def __init__(
+ self,
+ name,
+ doc,
+ file=None,
+ path: Optional[List[str]] = None,
+ package=None,
+ parent=None,
+ pure_python=True,
+ ):
+ """
+ :param name: The name of the module.
+ :type name: str
+
+ :param doc: The module docstring.
+ :type doc: str
+
+ :param file: The path to the file that this ast has been extracted from.
+ :type file: str or None
+
+ :param path:
+ :type path: Optional[List[str]]
+
+ :param package: Whether the node represents a package or a module.
+ :type package: bool or None
+
+ :param parent: The parent node in the syntax tree.
+ :type parent: NodeNG or None
+
+ :param pure_python: Whether the ast was built from source.
+ :type pure_python: bool or None
+ """
+ self.name = name
+ self.doc = doc
+ self.file = file
+ self.path = path
+ self.package = package
+ self.parent = parent
+ self.pure_python = pure_python
+ self.locals = self.globals = {}
+ """A map of the name of a local variable to the node defining the local.
+
+ :type: dict(str, NodeNG)
+ """
+ self.body = []
+ """The contents of the module.
+
+ :type: list(NodeNG) or None
+ """
+ self.future_imports = set()
+
+ # pylint: enable=redefined-builtin
+
+ def postinit(self, body=None):
+ """Do some setup after initialisation.
+
+ :param body: The contents of the module.
+ :type body: list(NodeNG) or None
+ """
+ self.body = body
+
+ def _get_stream(self):
+ if self.file_bytes is not None:
+ return io.BytesIO(self.file_bytes)
+ if self.file is not None:
+ stream = open(self.file, "rb")
+ return stream
+ return None
+
+ def stream(self):
+ """Get a stream to the underlying file or bytes.
+
+ :type: file or io.BytesIO or None
+ """
+ return self._get_stream()
+
+ def block_range(self, lineno):
+ """Get a range from where this node starts to where this node ends.
+
+ :param lineno: Unused.
+ :type lineno: int
+
+ :returns: The range of line numbers that this node belongs to.
+ :rtype: tuple(int, int)
+ """
+ return self.fromlineno, self.tolineno
+
+ def scope_lookup(self, node, name, offset=0):
+ """Lookup where the given variable is assigned.
+
+ :param node: The node to look for assignments up to.
+ Any assignments after the given node are ignored.
+ :type node: NodeNG
+
+ :param name: The name of the variable to find assignments for.
+ :type name: str
+
+ :param offset: The line offset to filter statements up to.
+ :type offset: int
+
+ :returns: This scope node and the list of assignments associated to the
+ given name according to the scope where it has been found (locals,
+ globals or builtin).
+ :rtype: tuple(str, list(NodeNG))
+ """
+ if name in self.scope_attrs and name not in self.locals:
+ try:
+ return self, self.getattr(name)
+ except exceptions.AttributeInferenceError:
+ return self, ()
+ return self._scope_lookup(node, name, offset)
+
+ def pytype(self):
+ """Get the name of the type that this node represents.
+
+ :returns: The name of the type.
+ :rtype: str
+ """
+ return "%s.module" % BUILTINS
+
+ def display_type(self):
+ """A human readable type of this node.
+
+ :returns: The type of this node.
+ :rtype: str
+ """
+ return "Module"
+
+ def getattr(self, name, context=None, ignore_locals=False):
+ result = []
+ name_in_locals = name in self.locals
+
+ if name in self.special_attributes and not ignore_locals and not name_in_locals:
+ result = [self.special_attributes.lookup(name)]
+ elif not ignore_locals and name_in_locals:
+ result = self.locals[name]
+ elif self.package:
+ try:
+ result = [self.import_module(name, relative_only=True)]
+ except (exceptions.AstroidBuildingError, SyntaxError) as exc:
+ raise exceptions.AttributeInferenceError(
+ target=self, attribute=name, context=context
+ ) from exc
+ result = [n for n in result if not isinstance(n, node_classes.DelName)]
+ if result:
+ return result
+ raise exceptions.AttributeInferenceError(
+ target=self, attribute=name, context=context
+ )
+
+ def igetattr(self, name, context=None):
+ """Infer the possible values of the given variable.
+
+ :param name: The name of the variable to infer.
+ :type name: str
+
+ :returns: The inferred possible values.
+ :rtype: iterable(NodeNG) or None
+ """
+ # set lookup name since this is necessary to infer on import nodes for
+ # instance
+ context = contextmod.copy_context(context)
+ context.lookupname = name
+ try:
+ return bases._infer_stmts(self.getattr(name, context), context, frame=self)
+ except exceptions.AttributeInferenceError as error:
+ raise exceptions.InferenceError(
+ error.message, target=self, attribute=name, context=context
+ ) from error
+
+ def fully_defined(self):
+ """Check if this module has been build from a .py file.
+
+ If so, the module contains a complete representation,
+ including the code.
+
+ :returns: True if the module has been built from a .py file.
+ :rtype: bool
+ """
+ return self.file is not None and self.file.endswith(".py")
+
+ def statement(self):
+ """The first parent node, including self, marked as statement node.
+
+ :returns: The first parent statement.
+ :rtype: NodeNG
+ """
+ return self
+
+ def previous_sibling(self):
+ """The previous sibling statement.
+
+ :returns: The previous sibling statement node.
+ :rtype: NodeNG or None
+ """
+
+ def next_sibling(self):
+ """The next sibling statement node.
+
+ :returns: The next sibling statement node.
+ :rtype: NodeNG or None
+ """
+
+ _absolute_import_activated = True
+
+ def absolute_import_activated(self):
+ """Whether :pep:`328` absolute import behaviour has been enabled.
+
+ :returns: True if :pep:`328` has been enabled, False otherwise.
+ :rtype: bool
+ """
+ return self._absolute_import_activated
+
+ def import_module(self, modname, relative_only=False, level=None):
+ """Get the ast for a given module as if imported from this module.
+
+ :param modname: The name of the module to "import".
+ :type modname: str
+
+ :param relative_only: Whether to only consider relative imports.
+ :type relative_only: bool
+
+ :param level: The level of relative import.
+ :type level: int or None
+
+ :returns: The imported module ast.
+ :rtype: NodeNG
+ """
+ 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 exceptions.AstroidBuildingError:
+ # 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):
+ """Get the absolute module name for a relative import.
+
+ The relative import can be implicit or explicit.
+
+ :param modname: The module name to convert.
+ :type modname: str
+
+ :param level: The level of relative import.
+ :type level: int
+
+ :returns: The absolute module name.
+ :rtype: str
+
+ :raises TooManyLevelsError: When the relative import refers to a
+ module too far above this one.
+ """
+ # 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
+ if level and self.name.count(".") < level:
+ raise exceptions.TooManyLevelsError(level=level, name=self.name)
+
+ 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):
+ """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.
+
+ :returns: The list of imported names.
+ :rtype: list(str)
+ """
+ # We separate the different steps of lookup in try/excepts
+ # to avoid catching too many Exceptions
+ default = [name for name in self.keys() if not name.startswith("_")]
+ try:
+ all_values = self["__all__"]
+ except KeyError:
+ return default
+
+ try:
+ explicit = next(all_values.assigned_stmts())
+ except exceptions.InferenceError:
+ return default
+ except AttributeError:
+ # not an assignment node
+ # XXX infer?
+ return default
+
+ # Try our best to detect the exported name.
+ inferred = []
+ try:
+ explicit = next(explicit.infer())
+ except exceptions.InferenceError:
+ return default
+ if not isinstance(explicit, (node_classes.Tuple, node_classes.List)):
+ return default
+
+ str_const = lambda node: (
+ isinstance(node, node_classes.Const) and isinstance(node.value, str)
+ )
+ for node in explicit.elts:
+ if str_const(node):
+ inferred.append(node.value)
+ else:
+ try:
+ inferred_node = next(node.infer())
+ except exceptions.InferenceError:
+ continue
+ if str_const(inferred_node):
+ inferred.append(inferred_node.value)
+ return inferred
+
+ def public_names(self):
+ """The list of the names that are publicly available in this module.
+
+ :returns: The list of publc names.
+ :rtype: list(str)
+ """
+ return [name for name in self.keys() if not name.startswith("_")]
+
+ def bool_value(self):
+ """Determine the boolean value of this node.
+
+ :returns: The boolean value of this node.
+ For a :class:`Module` this is always ``True``.
+ :rtype: bool
+ """
+ return True
+
+ def get_children(self):
+ yield from self.body
+
+
+class ComprehensionScope(LocalsDictNodeNG):
+ """Scoping for different types of comprehensions."""
+
+ def frame(self):
+ """The first parent frame node.
+
+ A frame node is a :class:`Module`, :class:`FunctionDef`,
+ or :class:`ClassDef`.
+
+ :returns: The first parent frame node.
+ :rtype: Module or FunctionDef or ClassDef
+ """
+ return self.parent.frame()
+
+ scope_lookup = LocalsDictNodeNG._scope_lookup
+
+
+class GeneratorExp(ComprehensionScope):
+ """Class representing an :class:`ast.GeneratorExp` node.
+
+ >>> node = astroid.extract_node('(thing for thing in things if thing)')
+ >>> node
+ <GeneratorExp l.1 at 0x7f23b2e4e400>
+ """
+
+ _astroid_fields = ("elt", "generators")
+ _other_other_fields = ("locals",)
+ elt = None
+ """The element that forms the output of the expression.
+
+ :type: NodeNG or None
+ """
+ generators = None
+ """The generators that are looped through.
+
+ :type: list(Comprehension) or None
+ """
+
+ def __init__(self, lineno=None, col_offset=None, parent=None):
+ """
+ :param lineno: The line that this node appears on in the source code.
+ :type lineno: int or None
+
+ :param col_offset: The column that this node appears on in the
+ source code.
+ :type col_offset: int or None
+
+ :param parent: The parent node in the syntax tree.
+ :type parent: NodeNG or None
+ """
+ self.locals = {}
+ """A map of the name of a local variable to the node defining the local.
+
+ :type: dict(str, NodeNG)
+ """
+
+ super(GeneratorExp, self).__init__(lineno, col_offset, parent)
+
+ def postinit(self, elt=None, generators=None):
+ """Do some setup after initialisation.
+
+ :param elt: The element that forms the output of the expression.
+ :type elt: NodeNG or None
+
+ :param generators: The generators that are looped through.
+ :type generators: list(Comprehension) or None
+ """
+ self.elt = elt
+ if generators is None:
+ self.generators = []
+ else:
+ self.generators = generators
+
+ def bool_value(self):
+ """Determine the boolean value of this node.
+
+ :returns: The boolean value of this node.
+ For a :class:`GeneratorExp` this is always ``True``.
+ :rtype: bool
+ """
+ return True
+
+ def get_children(self):
+ yield self.elt
+
+ yield from self.generators
+
+
+class DictComp(ComprehensionScope):
+ """Class representing an :class:`ast.DictComp` node.
+
+ >>> node = astroid.extract_node('{k:v for k, v in things if k > v}')
+ >>> node
+ <DictComp l.1 at 0x7f23b2e41d68>
+ """
+
+ _astroid_fields = ("key", "value", "generators")
+ _other_other_fields = ("locals",)
+ key = None
+ """What produces the keys.
+
+ :type: NodeNG or None
+ """
+ value = None
+ """What produces the values.
+
+ :type: NodeNG or None
+ """
+ generators = None
+ """The generators that are looped through.
+
+ :type: list(Comprehension) or None
+ """
+
+ def __init__(self, lineno=None, col_offset=None, parent=None):
+ """
+ :param lineno: The line that this node appears on in the source code.
+ :type lineno: int or None
+
+ :param col_offset: The column that this node appears on in the
+ source code.
+ :type col_offset: int or None
+
+ :param parent: The parent node in the syntax tree.
+ :type parent: NodeNG or None
+ """
+ self.locals = {}
+ """A map of the name of a local variable to the node defining the local.
+
+ :type: dict(str, NodeNG)
+ """
+
+ super(DictComp, self).__init__(lineno, col_offset, parent)
+
+ def postinit(self, key=None, value=None, generators=None):
+ """Do some setup after initialisation.
+
+ :param key: What produces the keys.
+ :type key: NodeNG or None
+
+ :param value: What produces the values.
+ :type value: NodeNG or None
+
+ :param generators: The generators that are looped through.
+ :type generators: list(Comprehension) or None
+ """
+ self.key = key
+ self.value = value
+ if generators is None:
+ self.generators = []
+ else:
+ self.generators = generators
+
+ def bool_value(self):
+ """Determine the boolean value of this node.
+
+ :returns: The boolean value of this node.
+ For a :class:`DictComp` this is always :class:`Uninferable`.
+ :rtype: Uninferable
+ """
+ return util.Uninferable
+
+ def get_children(self):
+ yield self.key
+ yield self.value
+
+ yield from self.generators
+
+
+class SetComp(ComprehensionScope):
+ """Class representing an :class:`ast.SetComp` node.
+
+ >>> node = astroid.extract_node('{thing for thing in things if thing}')
+ >>> node
+ <SetComp l.1 at 0x7f23b2e41898>
+ """
+
+ _astroid_fields = ("elt", "generators")
+ _other_other_fields = ("locals",)
+ elt = None
+ """The element that forms the output of the expression.
+
+ :type: NodeNG or None
+ """
+ generators = None
+ """The generators that are looped through.
+
+ :type: list(Comprehension) or None
+ """
+
+ def __init__(self, lineno=None, col_offset=None, parent=None):
+ """
+ :param lineno: The line that this node appears on in the source code.
+ :type lineno: int or None
+
+ :param col_offset: The column that this node appears on in the
+ source code.
+ :type col_offset: int or None
+
+ :param parent: The parent node in the syntax tree.
+ :type parent: NodeNG or None
+ """
+ self.locals = {}
+ """A map of the name of a local variable to the node defining the local.
+
+ :type: dict(str, NodeNG)
+ """
+
+ super(SetComp, self).__init__(lineno, col_offset, parent)
+
+ def postinit(self, elt=None, generators=None):
+ """Do some setup after initialisation.
+
+ :param elt: The element that forms the output of the expression.
+ :type elt: NodeNG or None
+
+ :param generators: The generators that are looped through.
+ :type generators: list(Comprehension) or None
+ """
+ self.elt = elt
+ if generators is None:
+ self.generators = []
+ else:
+ self.generators = generators
+
+ def bool_value(self):
+ """Determine the boolean value of this node.
+
+ :returns: The boolean value of this node.
+ For a :class:`SetComp` this is always :class:`Uninferable`.
+ :rtype: Uninferable
+ """
+ return util.Uninferable
+
+ def get_children(self):
+ yield self.elt
+
+ yield from self.generators
+
+
+class _ListComp(node_classes.NodeNG):
+ """Class representing an :class:`ast.ListComp` node.
+
+ >>> node = astroid.extract_node('[thing for thing in things if thing]')
+ >>> node
+ <ListComp l.1 at 0x7f23b2e418d0>
+ """
+
+ _astroid_fields = ("elt", "generators")
+ elt = None
+ """The element that forms the output of the expression.
+
+ :type: NodeNG or None
+ """
+ generators = None
+ """The generators that are looped through.
+
+ :type: list(Comprehension) or None
+ """
+
+ def postinit(self, elt=None, generators=None):
+ """Do some setup after initialisation.
+
+ :param elt: The element that forms the output of the expression.
+ :type elt: NodeNG or None
+
+ :param generators: The generators that are looped through.
+ :type generators: list(Comprehension) or None
+ """
+ self.elt = elt
+ self.generators = generators
+
+ def bool_value(self):
+ """Determine the boolean value of this node.
+
+ :returns: The boolean value of this node.
+ For a :class:`ListComp` this is always :class:`Uninferable`.
+ :rtype: Uninferable
+ """
+ return util.Uninferable
+
+ def get_children(self):
+ yield self.elt
+
+ yield from self.generators
+
+
+class ListComp(_ListComp, ComprehensionScope):
+ """Class representing an :class:`ast.ListComp` node.
+
+ >>> node = astroid.extract_node('[thing for thing in things if thing]')
+ >>> node
+ <ListComp l.1 at 0x7f23b2e418d0>
+ """
+
+ _other_other_fields = ("locals",)
+
+ def __init__(self, lineno=None, col_offset=None, parent=None):
+ self.locals = {}
+ """A map of the name of a local variable to the node defining it.
+
+ :type: dict(str, NodeNG)
+ """
+
+ super(ListComp, self).__init__(lineno, col_offset, parent)
+
+
+def _infer_decorator_callchain(node):
+ """Detect decorator call chaining and see if the end result is a
+ static or a classmethod.
+ """
+ if not isinstance(node, FunctionDef):
+ return None
+ if not node.parent:
+ return None
+ try:
+ result = next(node.infer_call_result(node.parent))
+ except exceptions.InferenceError:
+ return None
+ if isinstance(result, bases.Instance):
+ result = result._proxied
+ if isinstance(result, ClassDef):
+ if result.is_subtype_of("%s.classmethod" % BUILTINS):
+ return "classmethod"
+ if result.is_subtype_of("%s.staticmethod" % BUILTINS):
+ return "staticmethod"
+ return None
+
+
+class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG):
+ """Class representing an :class:`ast.Lambda` node.
+
+ >>> node = astroid.extract_node('lambda arg: arg + 1')
+ >>> node
+ <Lambda.<lambda> l.1 at 0x7f23b2e41518>
+ """
+
+ _astroid_fields = ("args", "body")
+ _other_other_fields = ("locals",)
+ name = "<lambda>"
+ is_lambda = True
+
+ def implicit_parameters(self):
+ return 0
+
+ # function's type, 'function' | 'method' | 'staticmethod' | 'classmethod'
+ @property
+ def type(self):
+ """Whether this is a method or function.
+
+ :returns: 'method' if this is a method, 'function' otherwise.
+ :rtype: str
+ """
+ # pylint: disable=no-member
+ if self.args.args and self.args.args[0].name == "self":
+ if isinstance(self.parent.scope(), ClassDef):
+ return "method"
+ return "function"
+
+ def __init__(self, lineno=None, col_offset=None, parent=None):
+ """
+ :param lineno: The line that this node appears on in the source code.
+ :type lineno: int or None
+
+ :param col_offset: The column that this node appears on in the
+ source code.
+ :type col_offset: int or None
+
+ :param parent: The parent node in the syntax tree.
+ :type parent: NodeNG or None
+ """
+ self.locals = {}
+ """A map of the name of a local variable to the node defining it.
+
+ :type: dict(str, NodeNG)
+ """
+
+ self.args = []
+ """The arguments that the function takes.
+
+ :type: Arguments or list
+ """
+
+ self.body = []
+ """The contents of the function body.
+
+ :type: list(NodeNG)
+ """
+
+ super(Lambda, self).__init__(lineno, col_offset, parent)
+
+ def postinit(self, args, body):
+ """Do some setup after initialisation.
+
+ :param args: The arguments that the function takes.
+ :type args: Arguments
+
+ :param body: The contents of the function body.
+ :type body: list(NodeNG)
+ """
+ self.args = args
+ self.body = body
+
+ def pytype(self):
+ """Get the name of the type that this node represents.
+
+ :returns: The name of the type.
+ :rtype: str
+ """
+ if "method" in self.type:
+ return "%s.instancemethod" % BUILTINS
+ return "%s.function" % BUILTINS
+
+ def display_type(self):
+ """A human readable type of this node.
+
+ :returns: The type of this node.
+ :rtype: str
+ """
+ if "method" in self.type:
+ return "Method"
+ return "Function"
+
+ def callable(self):
+ """Whether this node defines something that is callable.
+
+ :returns: True if this defines something that is callable,
+ False otherwise.
+ For a :class:`Lambda` this is always ``True``.
+ :rtype: bool
+ """
+ return True
+
+ def argnames(self):
+ """Get the names of each of the arguments.
+
+ :returns: The names of the arguments.
+ :rtype: list(str)
+ """
+ # pylint: disable=no-member; github.com/pycqa/astroid/issues/291
+ # args is in fact redefined later on by postinit. Can't be changed
+ # to None due to a strong interaction between Lambda and FunctionDef.
+
+ 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 the function returns when called.
+
+ :param caller: Unused
+ :type caller: object
+ """
+ # pylint: disable=no-member; github.com/pycqa/astroid/issues/291
+ # args is in fact redefined later on by postinit. Can't be changed
+ # to None due to a strong interaction between Lambda and FunctionDef.
+ return self.body.infer(context)
+
+ def scope_lookup(self, node, name, offset=0):
+ """Lookup where the given names is assigned.
+
+ :param node: The node to look for assignments up to.
+ Any assignments after the given node are ignored.
+ :type node: NodeNG
+
+ :param name: The name to find assignments for.
+ :type name: str
+
+ :param offset: The line offset to filter statements up to.
+ :type offset: int
+
+ :returns: This scope node and the list of assignments associated to the
+ given name according to the scope where it has been found (locals,
+ globals or builtin).
+ :rtype: tuple(str, list(NodeNG))
+ """
+ # pylint: disable=no-member; github.com/pycqa/astroid/issues/291
+ # args is in fact redefined later on by postinit. Can't be changed
+ # to None due to a strong interaction between Lambda and FunctionDef.
+
+ 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)
+
+ def bool_value(self):
+ """Determine the boolean value of this node.
+
+ :returns: The boolean value of this node.
+ For a :class:`Lambda` this is always ``True``.
+ :rtype: bool
+ """
+ return True
+
+ def get_children(self):
+ yield self.args
+ yield self.body
+
+
+class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
+ """Class representing an :class:`ast.FunctionDef`.
+
+ >>> node = astroid.extract_node('''
+ ... def my_func(arg):
+ ... return arg + 1
+ ... ''')
+ >>> node
+ <FunctionDef.my_func l.2 at 0x7f23b2e71e10>
+ """
+
+ _astroid_fields = ("decorators", "args", "returns", "body")
+ _multi_line_block_fields = ("body",)
+ returns = None
+ decorators = None
+ """The decorators that are applied to this method or function.
+
+ :type: Decorators or None
+ """
+ special_attributes = objectmodel.FunctionModel()
+ """The names of special attributes that this function has.
+
+ :type: objectmodel.FunctionModel
+ """
+ is_function = True
+ """Whether this node indicates a function.
+
+ For a :class:`FunctionDef` this is always ``True``.
+
+ :type: bool
+ """
+ type_annotation = None
+ """If present, this will contain the type annotation passed by a type comment
+
+ :type: NodeNG or None
+ """
+ type_comment_args = None
+ """
+ If present, this will contain the type annotation for arguments
+ passed by a type comment
+ """
+ type_comment_returns = None
+ """If present, this will contain the return type annotation, passed by a type comment"""
+ # attributes below are set by the builder module or by raw factories
+ _other_fields = ("name", "doc")
+ _other_other_fields = (
+ "locals",
+ "_type",
+ "type_comment_returns",
+ "type_comment_args",
+ )
+ _type = None
+
+ def __init__(self, name=None, doc=None, lineno=None, col_offset=None, parent=None):
+ """
+ :param name: The name of the function.
+ :type name: str or None
+
+ :param doc: The function's docstring.
+ :type doc: str or None
+
+ :param lineno: The line that this node appears on in the source code.
+ :type lineno: int or None
+
+ :param col_offset: The column that this node appears on in the
+ source code.
+ :type col_offset: int or None
+
+ :param parent: The parent node in the syntax tree.
+ :type parent: NodeNG or None
+ """
+ self.name = name
+ """The name of the function.
+
+ :type name: str or None
+ """
+
+ self.doc = doc
+ """The function's docstring.
+
+ :type doc: str or None
+ """
+
+ self.instance_attrs = {}
+ super(FunctionDef, self).__init__(lineno, col_offset, parent)
+ if parent:
+ frame = parent.frame()
+ frame.set_local(name, self)
+
+ # pylint: disable=arguments-differ; different than Lambdas
+ def postinit(
+ self,
+ args,
+ body,
+ decorators=None,
+ returns=None,
+ type_comment_returns=None,
+ type_comment_args=None,
+ ):
+ """Do some setup after initialisation.
+
+ :param args: The arguments that the function takes.
+ :type args: Arguments or list
+
+ :param body: The contents of the function body.
+ :type body: list(NodeNG)
+
+ :param decorators: The decorators that are applied to this
+ method or function.
+ :type decorators: Decorators or None
+ :params type_comment_returns:
+ The return type annotation passed via a type comment.
+ :params type_comment_args:
+ The args type annotation passed via a type comment.
+ """
+ self.args = args
+ self.body = body
+ self.decorators = decorators
+ self.returns = returns
+ self.type_comment_returns = type_comment_returns
+ self.type_comment_args = type_comment_args
+
+ @decorators_mod.cachedproperty
+ def extra_decorators(self):
+ """The extra decorators that this function can have.
+
+ Additional decorators are considered when they are used as
+ assignments, as in ``method = staticmethod(method)``.
+ The property will return all the callables that are used for
+ decoration.
+
+ :type: list(NodeNG)
+ """
+ frame = self.parent.frame()
+ if not isinstance(frame, ClassDef):
+ return []
+
+ decorators = []
+ for assign in frame._get_assign_nodes():
+ if isinstance(assign.value, node_classes.Call) and isinstance(
+ assign.value.func, node_classes.Name
+ ):
+ for assign_node in assign.targets:
+ if not isinstance(assign_node, node_classes.AssignName):
+ # Support only `name = callable(name)`
+ continue
+
+ if assign_node.name != self.name:
+ # Interested only in the assignment nodes that
+ # decorates the current method.
+ continue
+ try:
+ meth = frame[self.name]
+ except KeyError:
+ continue
+ else:
+ # Must be a function and in the same frame as the
+ # original method.
+ if (
+ isinstance(meth, FunctionDef)
+ and assign_node.frame() == frame
+ ):
+ decorators.append(assign.value)
+ return decorators
+
+ @decorators_mod.cachedproperty
+ def type(self): # pylint: disable=invalid-overridden-method
+ """The function type for this node.
+
+ Possible values are: method, function, staticmethod, classmethod.
+
+ :type: str
+ """
+ builtin_descriptors = {"classmethod", "staticmethod"}
+
+ for decorator in self.extra_decorators:
+ if decorator.func.name in builtin_descriptors:
+ return decorator.func.name
+
+ frame = self.parent.frame()
+ type_name = "function"
+ if isinstance(frame, ClassDef):
+ if self.name == "__new__":
+ return "classmethod"
+ if sys.version_info >= (3, 6) and self.name == "__init_subclass__":
+ return "classmethod"
+
+ type_name = "method"
+
+ if not self.decorators:
+ return type_name
+
+ for node in self.decorators.nodes:
+ if isinstance(node, node_classes.Name):
+ if node.name in builtin_descriptors:
+ return node.name
+
+ if isinstance(node, node_classes.Call):
+ # Handle the following case:
+ # @some_decorator(arg1, arg2)
+ # def func(...)
+ #
+ try:
+ current = next(node.func.infer())
+ except exceptions.InferenceError:
+ continue
+ _type = _infer_decorator_callchain(current)
+ if _type is not None:
+ return _type
+
+ try:
+ for inferred in node.infer():
+ # Check to see if this returns a static or a class method.
+ _type = _infer_decorator_callchain(inferred)
+ if _type is not None:
+ return _type
+
+ if not isinstance(inferred, ClassDef):
+ continue
+ for ancestor in inferred.ancestors():
+ if not isinstance(ancestor, ClassDef):
+ continue
+ if ancestor.is_subtype_of("%s.classmethod" % BUILTINS):
+ return "classmethod"
+ if ancestor.is_subtype_of("%s.staticmethod" % BUILTINS):
+ return "staticmethod"
+ except exceptions.InferenceError:
+ pass
+ return type_name
+
+ @decorators_mod.cachedproperty
+ def fromlineno(self):
+ """The first line that this node appears on in the source code.
+
+ :type: int or None
+ """
+ # lineno is the line number of the first decorator, we want the def
+ # statement lineno
+ lineno = self.lineno
+ if self.decorators is not None:
+ lineno += sum(
+ node.tolineno - node.lineno + 1 for node in self.decorators.nodes
+ )
+
+ return lineno
+
+ @decorators_mod.cachedproperty
+ def blockstart_tolineno(self):
+ """The line on which the beginning of this block ends.
+
+ :type: int
+ """
+ return self.args.tolineno
+
+ def block_range(self, lineno):
+ """Get a range from the given line number to where this node ends.
+
+ :param lineno: Unused.
+ :type lineno: int
+
+ :returns: The range of line numbers that this node belongs to,
+ :rtype: tuple(int, int)
+ """
+ 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 in self.instance_attrs:
+ return self.instance_attrs[name]
+ if name in self.special_attributes:
+ return [self.special_attributes.lookup(name)]
+ raise exceptions.AttributeInferenceError(target=self, attribute=name)
+
+ def igetattr(self, name, context=None):
+ """Inferred getattr, which returns an iterator of inferred statements."""
+ try:
+ return bases._infer_stmts(self.getattr(name, context), context, frame=self)
+ except exceptions.AttributeInferenceError as error:
+ raise exceptions.InferenceError(
+ error.message, target=self, attribute=name, context=context
+ ) from error
+
+ def is_method(self):
+ """Check if this function node represents a method.
+
+ :returns: True if this is a method, False otherwise.
+ :rtype: bool
+ """
+ # check we are defined in a ClassDef, because this is usually expected
+ # (e.g. pylint...) when is_method() return True
+ return self.type != "function" and isinstance(self.parent.frame(), ClassDef)
+
+ @decorators_mod.cached
+ def decoratornames(self):
+ """Get the qualified names of each of the decorators on this function.
+
+ :returns: The names of the decorators.
+ :rtype: set(str)
+ """
+ result = set()
+ decoratornodes = []
+ if self.decorators is not None:
+ decoratornodes += self.decorators.nodes
+ decoratornodes += self.extra_decorators
+ for decnode in decoratornodes:
+ try:
+ for infnode in decnode.infer():
+ result.add(infnode.qname())
+ except exceptions.InferenceError:
+ continue
+ return result
+
+ def is_bound(self):
+ """Check if the function is bound to an instance or class.
+
+ :returns: True if the function is bound to an instance or class,
+ False otherwise.
+ :rtype: bool
+ """
+ return self.type == "classmethod"
+
+ def is_abstract(self, pass_is_abstract=True):
+ """Check if the method is abstract.
+
+ A method is considered abstract if any of the following is true:
+ * The only statement is 'raise NotImplementedError'
+ * The only statement is 'pass' and pass_is_abstract is True
+ * The method is annotated with abc.astractproperty/abc.abstractmethod
+
+ :returns: True if the method is abstract, False otherwise.
+ :rtype: bool
+ """
+ if self.decorators:
+ for node in self.decorators.nodes:
+ try:
+ inferred = next(node.infer())
+ except exceptions.InferenceError:
+ continue
+ if inferred and inferred.qname() in (
+ "abc.abstractproperty",
+ "abc.abstractmethod",
+ ):
+ return True
+
+ for child_node in self.body:
+ if isinstance(child_node, node_classes.Raise):
+ if child_node.raises_not_implemented():
+ return True
+ return pass_is_abstract and isinstance(child_node, node_classes.Pass)
+ # empty function is the same as function with a single "pass" statement
+ if pass_is_abstract:
+ return True
+
+ def is_generator(self):
+ """Check if this is a generator function.
+
+ :returns: True is this is a generator function, False otherwise.
+ :rtype: bool
+ """
+ return next(self._get_yield_nodes_skip_lambdas(), False)
+
+ def infer_call_result(self, caller=None, context=None):
+ """Infer what the function returns when called.
+
+ :returns: What the function returns.
+ :rtype: iterable(NodeNG or Uninferable) or None
+ """
+ if self.is_generator():
+ if isinstance(self, AsyncFunctionDef):
+ generator_cls = bases.AsyncGenerator
+ else:
+ generator_cls = bases.Generator
+ result = generator_cls(self)
+ yield result
+ 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, ClassDef):
+ class_bases = [next(arg.infer(context)) for arg in caller.args[1:]]
+ new_class = ClassDef(name="temporary_class")
+ new_class.hide = True
+ new_class.parent = self
+ new_class.postinit(
+ bases=[base for base in class_bases if base != util.Uninferable],
+ body=[],
+ decorators=[],
+ metaclass=metaclass,
+ )
+ yield new_class
+ return
+ returns = self._get_return_nodes_skip_functions()
+
+ first_return = next(returns, None)
+ if not first_return:
+ if self.body and isinstance(self.body[-1], node_classes.Assert):
+ yield node_classes.Const(None)
+ return
+
+ raise exceptions.InferenceError(
+ "The function does not have any return statements"
+ )
+
+ for returnnode in itertools.chain((first_return,), returns):
+ if returnnode.value is None:
+ yield node_classes.Const(None)
+ else:
+ try:
+ yield from returnnode.value.infer(context)
+ except exceptions.InferenceError:
+ yield util.Uninferable
+
+ def bool_value(self):
+ """Determine the boolean value of this node.
+
+ :returns: The boolean value of this node.
+ For a :class:`FunctionDef` this is always ``True``.
+ :rtype: bool
+ """
+ return True
+
+ def get_children(self):
+ if self.decorators is not None:
+ yield self.decorators
+
+ yield self.args
+
+ if self.returns is not None:
+ yield self.returns
+
+ yield from self.body
+
+ def scope_lookup(self, node, name, offset=0):
+ """Lookup where the given name is assigned."""
+ if name == "__class__":
+ # __class__ is an implicit closure reference created by the compiler
+ # if any methods in a class body refer to either __class__ or super.
+ # In our case, we want to be able to look it up in the current scope
+ # when `__class__` is being used.
+ frame = self.parent.frame()
+ if isinstance(frame, ClassDef):
+ return self, [frame]
+ return super().scope_lookup(node, name, offset)
+
+
+class AsyncFunctionDef(FunctionDef):
+ """Class representing an :class:`ast.FunctionDef` node.
+
+ A :class:`AsyncFunctionDef` is an asynchronous function
+ created with the `async` keyword.
+
+ >>> node = astroid.extract_node('''
+ async def func(things):
+ async for thing in things:
+ print(thing)
+ ''')
+ >>> node
+ <AsyncFunctionDef.func l.2 at 0x7f23b2e416d8>
+ >>> node.body[0]
+ <AsyncFor l.3 at 0x7f23b2e417b8>
+ """
+
+
+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, node_classes.Tuple):
+ _rec_get_names(arg.elts, names)
+ else:
+ names.append(arg.name)
+ return names
+
+
+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():
+ baseobj_name = baseobj.qname()
+ if baseobj_name in seen:
+ continue
+
+ seen.add(baseobj_name)
+ if isinstance(baseobj, bases.Instance):
+ # not abstract
+ return False
+ if baseobj is util.Uninferable:
+ continue
+ if baseobj is klass:
+ continue
+ if not isinstance(baseobj, ClassDef):
+ continue
+ if baseobj._type == "metaclass":
+ return True
+ if _is_metaclass(baseobj, seen):
+ return True
+ except exceptions.InferenceError:
+ continue
+ return False
+
+
+def _class_type(klass, ancestors=None):
+ """return a ClassDef node type to differ metaclass and exception
+ from 'regular' classes
+ """
+ # XXX we have to store ancestors in case we have an ancestor loop
+ if klass._type is not None:
+ return klass._type
+ if _is_metaclass(klass):
+ klass._type = "metaclass"
+ elif klass.name.endswith("Exception"):
+ klass._type = "exception"
+ else:
+ if ancestors is None:
+ ancestors = set()
+ klass_name = klass.qname()
+ if klass_name in ancestors:
+ # XXX we are in loop ancestors, and have found no type
+ klass._type = "class"
+ return "class"
+ ancestors.add(klass_name)
+ 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 get_wrapping_class(node):
+ """Get the class that wraps the given node.
+
+ We consider that a class wraps a node if the class
+ is a parent for the said node.
+
+ :returns: The class that wraps the given node
+ :rtype: ClassDef or None
+ """
+
+ klass = node.frame()
+ while klass is not None and not isinstance(klass, ClassDef):
+ if klass.parent is None:
+ klass = None
+ else:
+ klass = klass.parent.frame()
+ return klass
+
+
+class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement):
+ """Class representing an :class:`ast.ClassDef` node.
+
+ >>> node = astroid.extract_node('''
+ class Thing:
+ def my_meth(self, arg):
+ return arg + self.offset
+ ''')
+ >>> node
+ <ClassDef.Thing l.2 at 0x7f23b2e9e748>
+ """
+
+ # 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
+ """The decorators that are applied to this class.
+
+ :type: Decorators or None
+ """
+ special_attributes = objectmodel.ClassModel()
+ """The names of special attributes that this class has.
+
+ :type: objectmodel.ClassModel
+ """
+
+ _type = None
+ _metaclass_hack = False
+ hide = False
+ type = property(
+ _class_type,
+ doc=(
+ "The class type for this node.\n\n"
+ "Possible values are: class, metaclass, exception.\n\n"
+ ":type: str"
+ ),
+ )
+ _other_fields = ("name", "doc")
+ _other_other_fields = ("locals", "_newstyle")
+ _newstyle = None
+
+ def __init__(self, name=None, doc=None, lineno=None, col_offset=None, parent=None):
+ """
+ :param name: The name of the class.
+ :type name: str or None
+
+ :param doc: The function's docstring.
+ :type doc: str or None
+
+ :param lineno: The line that this node appears on in the source code.
+ :type lineno: int or None
+
+ :param col_offset: The column that this node appears on in the
+ source code.
+ :type col_offset: int or None
+
+ :param parent: The parent node in the syntax tree.
+ :type parent: NodeNG or None
+ """
+ self.instance_attrs = {}
+ self.locals = {}
+ """A map of the name of a local variable to the node defining it.
+
+ :type: dict(str, NodeNG)
+ """
+
+ self.keywords = []
+ """The keywords given to the class definition.
+
+ This is usually for :pep:`3115` style metaclass declaration.
+
+ :type: list(Keyword) or None
+ """
+
+ self.bases = []
+ """What the class inherits from.
+
+ :type: list(NodeNG)
+ """
+
+ self.body = []
+ """The contents of the class body.
+
+ :type: list(NodeNG)
+ """
+
+ self.name = name
+ """The name of the class.
+
+ :type name: str or None
+ """
+
+ self.doc = doc
+ """The class' docstring.
+
+ :type doc: str or None
+ """
+
+ super(ClassDef, self).__init__(lineno, col_offset, parent)
+ if parent is not None:
+ parent.frame().set_local(name, self)
+
+ for local_name, node in self.implicit_locals():
+ self.add_local_node(node, local_name)
+
+ def implicit_parameters(self):
+ return 1
+
+ def implicit_locals(self):
+ """Get implicitly defined class definition locals.
+
+ :returns: the the name and Const pair for each local
+ :rtype: tuple(tuple(str, node_classes.Const), ...)
+ """
+ locals_ = (("__module__", self.special_attributes.attr___module__),)
+ # __qualname__ is defined in PEP3155
+ locals_ += (("__qualname__", self.special_attributes.attr___qualname__),)
+ return locals_
+
+ # pylint: disable=redefined-outer-name
+ def postinit(
+ self, bases, body, decorators, newstyle=None, metaclass=None, keywords=None
+ ):
+ """Do some setup after initialisation.
+
+ :param bases: What the class inherits from.
+ :type bases: list(NodeNG)
+
+ :param body: The contents of the class body.
+ :type body: list(NodeNG)
+
+ :param decorators: The decorators that are applied to this class.
+ :type decorators: Decorators or None
+
+ :param newstyle: Whether this is a new style class or not.
+ :type newstyle: bool or None
+
+ :param metaclass: The metaclass of this class.
+ :type metaclass: NodeNG or None
+
+ :param keywords: The keywords given to the class definition.
+ :type keywords: list(Keyword) or None
+ """
+ self.keywords = keywords
+ self.bases = bases
+ self.body = body
+ self.decorators = decorators
+ if newstyle is not None:
+ self._newstyle = newstyle
+ if metaclass is not None:
+ self._metaclass = metaclass
+
+ def _newstyle_impl(self, context=None):
+ if context is None:
+ context = contextmod.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.declared_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, ClassDef):
+ self._newstyle = klass._newstyle_impl(context)
+ if self._newstyle is None:
+ self._newstyle = False
+ return self._newstyle
+
+ _newstyle = None
+ newstyle = property(
+ _newstyle_impl,
+ doc=("Whether this is a new style class or not\n\n" ":type: bool or None"),
+ )
+
+ @decorators_mod.cachedproperty
+ def blockstart_tolineno(self):
+ """The line on which the beginning of this block ends.
+
+ :type: int
+ """
+ if self.bases:
+ return self.bases[-1].tolineno
+
+ return self.fromlineno
+
+ def block_range(self, lineno):
+ """Get a range from the given line number to where this node ends.
+
+ :param lineno: Unused.
+ :type lineno: int
+
+ :returns: The range of line numbers that this node belongs to,
+ :rtype: tuple(int, int)
+ """
+ return self.fromlineno, self.tolineno
+
+ def pytype(self):
+ """Get the name of the type that this node represents.
+
+ :returns: The name of the type.
+ :rtype: str
+ """
+ if self.newstyle:
+ return "%s.type" % BUILTINS
+ return "%s.classobj" % BUILTINS
+
+ def display_type(self):
+ """A human readable type of this node.
+
+ :returns: The type of this node.
+ :rtype: str
+ """
+ return "Class"
+
+ def callable(self):
+ """Whether this node defines something that is callable.
+
+ :returns: True if this defines something that is callable,
+ False otherwise.
+ For a :class:`ClassDef` this is always ``True``.
+ :rtype: bool
+ """
+ return True
+
+ def is_subtype_of(self, type_name, context=None):
+ """Whether this class is a subtype of the given type.
+
+ :param type_name: The name of the type of check against.
+ :type type_name: str
+
+ :returns: True if this class is a subtype of the given type,
+ False otherwise.
+ :rtype: bool
+ """
+ if self.qname() == type_name:
+ return True
+ for anc in self.ancestors(context=context):
+ if anc.qname() == type_name:
+ return True
+ return False
+
+ def _infer_type_call(self, caller, context):
+ name_node = next(caller.args[0].infer(context))
+ if isinstance(name_node, node_classes.Const) and isinstance(
+ name_node.value, str
+ ):
+ name = name_node.value
+ else:
+ return util.Uninferable
+
+ result = ClassDef(name, None)
+
+ # Get the bases of the class.
+ class_bases = next(caller.args[1].infer(context))
+ if isinstance(class_bases, (node_classes.Tuple, node_classes.List)):
+ result.bases = class_bases.itered()
+ else:
+ # There is currently no AST node that can represent an 'unknown'
+ # node (Uninferable is not an AST node), therefore we simply return Uninferable here
+ # although we know at least the name of the class.
+ return util.Uninferable
+
+ # Get the members of the class
+ try:
+ members = next(caller.args[2].infer(context))
+ except exceptions.InferenceError:
+ members = None
+
+ if members and isinstance(members, node_classes.Dict):
+ for attr, value in members.items:
+ if isinstance(attr, node_classes.Const) and isinstance(attr.value, str):
+ result.locals[attr.value] = [value]
+
+ result.parent = caller.parent
+ return result
+
+ def infer_call_result(self, caller, context=None):
+ """infer what a class is returning when called"""
+ if (
+ self.is_subtype_of("%s.type" % (BUILTINS,), context)
+ and len(caller.args) == 3
+ ):
+ result = self._infer_type_call(caller, context)
+ yield result
+ return
+
+ dunder_call = None
+ try:
+ metaclass = self.metaclass(context=context)
+ if metaclass is not None:
+ dunder_call = next(metaclass.igetattr("__call__", context))
+ except exceptions.AttributeInferenceError:
+ pass
+
+ if dunder_call and dunder_call.qname() != "builtins.type.__call__":
+ # Call type.__call__ if not set metaclass
+ # (since type is the default metaclass)
+ context = contextmod.bind_context_to_node(context, self)
+ yield from dunder_call.infer_call_result(caller, context)
+ else:
+ if any(cls.name in EXCEPTION_BASE_CLASSES for cls in self.mro()):
+ # Subclasses of exceptions can be exception instances
+ yield objects.ExceptionInstance(self)
+ else:
+ yield bases.Instance(self)
+
+ def scope_lookup(self, node, name, offset=0):
+ """Lookup where the given name is assigned.
+
+ :param node: The node to look for assignments up to.
+ Any assignments after the given node are ignored.
+ :type node: NodeNG
+
+ :param name: The name to find assignments for.
+ :type name: str
+
+ :param offset: The line offset to filter statements up to.
+ :type offset: int
+
+ :returns: This scope node and the list of assignments associated to the
+ given name according to the scope where it has been found (locals,
+ globals or builtin).
+ :rtype: tuple(str, list(NodeNG))
+ """
+ # If the name looks like a builtin name, just try to look
+ # into the upper scope of this class. We might have a
+ # decorator that it's poorly named after a builtin object
+ # inside this class.
+ lookup_upper_frame = (
+ isinstance(node.parent, node_classes.Decorators)
+ and name in MANAGER.builtins_module
+ )
+ if (
+ any(node == base or base.parent_of(node) for base in self.bases)
+ or lookup_upper_frame
+ ):
+ # Handle the case where we have either a name
+ # in the bases of a class, which exists before
+ # the actual definition or the case where we have
+ # a Getattr node, with that name.
+ #
+ # name = ...
+ # class A(name):
+ # def name(self): ...
+ #
+ # import name
+ # class A(name.Name):
+ # def name(self): ...
+
+ 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)
+
+ @property
+ def basenames(self):
+ """The names of the parent classes
+
+ Names are given in the order they appear in the class definition.
+
+ :type: list(str)
+ """
+ return [bnode.as_string() for bnode in self.bases]
+
+ def ancestors(self, recurs=True, context=None):
+ """Iterate over the base classes in prefixed depth first order.
+
+ :param recurs: Whether to recurse or return direct ancestors only.
+ :type recurs: bool
+
+ :returns: The base classes
+ :rtype: iterable(NodeNG)
+ """
+ # FIXME: should be possible to choose the resolution order
+ # FIXME: inference make infinite loops possible here
+ yielded = {self}
+ if context is None:
+ context = contextmod.InferenceContext()
+ 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, ClassDef):
+ if isinstance(baseobj, bases.Instance):
+ baseobj = baseobj._proxied
+ else:
+ continue
+ if not baseobj.hide:
+ if baseobj in yielded:
+ continue
+ yielded.add(baseobj)
+ yield baseobj
+ if not recurs:
+ continue
+ for grandpa in baseobj.ancestors(recurs=True, context=context):
+ if grandpa is self:
+ # This class is the ancestor of itself.
+ break
+ if grandpa in yielded:
+ continue
+ yielded.add(grandpa)
+ yield grandpa
+ except exceptions.InferenceError:
+ continue
+
+ def local_attr_ancestors(self, name, context=None):
+ """Iterate over the parents that define the given name.
+
+ :param name: The name to find definitions for.
+ :type name: str
+
+ :returns: The parents that define the given name.
+ :rtype: iterable(NodeNG)
+ """
+ # Look up in the mro if we can. This will result in the
+ # attribute being looked up just as Python does it.
+ try:
+ ancestors = self.mro(context)[1:]
+ except exceptions.MroError:
+ # Fallback to use ancestors, we can't determine
+ # a sane MRO.
+ ancestors = self.ancestors(context=context)
+ for astroid in ancestors:
+ if name in astroid:
+ yield astroid
+
+ def instance_attr_ancestors(self, name, context=None):
+ """Iterate over the parents that define the given name as an attribute.
+
+ :param name: The name to find definitions for.
+ :type name: str
+
+ :returns: The parents that define the given name as
+ an instance attribute.
+ :rtype: iterable(NodeNG)
+ """
+ for astroid in self.ancestors(context=context):
+ if name in astroid.instance_attrs:
+ yield astroid
+
+ def has_base(self, node):
+ """Whether this class directly inherits from the given node.
+
+ :param node: The node to check for.
+ :type node: NodeNG
+
+ :returns: True if this class directly inherits from the given node.
+ :rtype: bool
+ """
+ return node in self.bases
+
+ def local_attr(self, name, context=None):
+ """Get the list of assign nodes associated to the given name.
+
+ Assignments are looked for in both this class and in parents.
+
+ :returns: The list of assignments to the given name.
+ :rtype: list(NodeNG)
+
+ :raises AttributeInferenceError: If no attribute with this name
+ can be found in this class or parent classes.
+ """
+ result = []
+ if name in self.locals:
+ result = self.locals[name]
+ else:
+ class_node = next(self.local_attr_ancestors(name, context), None)
+ if class_node:
+ result = class_node.locals[name]
+ result = [n for n in result if not isinstance(n, node_classes.DelAttr)]
+ if result:
+ return result
+ raise exceptions.AttributeInferenceError(
+ target=self, attribute=name, context=context
+ )
+
+ def instance_attr(self, name, context=None):
+ """Get the list of nodes associated to the given attribute name.
+
+ Assignments are looked for in both this class and in parents.
+
+ :returns: The list of assignments to the given name.
+ :rtype: list(NodeNG)
+
+ :raises AttributeInferenceError: If no attribute with this name
+ can be found in this class or 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]
+ values = [n for n in values if not isinstance(n, node_classes.DelAttr)]
+ if values:
+ return values
+ raise exceptions.AttributeInferenceError(
+ target=self, attribute=name, context=context
+ )
+
+ def instantiate_class(self):
+ """Get an :class:`Instance` of the :class:`ClassDef` node.
+
+ :returns: An :class:`Instance` of the :class:`ClassDef` node,
+ or self if this is not possible.
+ :rtype: Instance or ClassDef
+ """
+ return bases.Instance(self)
+
+ def getattr(self, name, context=None, class_context=True):
+ """Get an attribute from this class, using Python's attribute semantic.
+
+ This method doesn't look in the :attr:`instance_attrs` dictionary
+ since it is done by an :class:`Instance` proxy at inference time.
+ It may return an :class:`Uninferable` object if
+ the attribute has not been
+ found, but a ``__getattr__`` or ``__getattribute__`` method is defined.
+ If ``class_context`` is given, then it is considered that the
+ attribute is accessed from a class context,
+ e.g. ClassDef.attribute, otherwise it might have been accessed
+ from an instance as well. If ``class_context`` is used in that
+ case, then a lookup in the implicit metaclass and the explicit
+ metaclass will be done.
+
+ :param name: The attribute to look for.
+ :type name: str
+
+ :param class_context: Whether the attribute can be accessed statically.
+ :type class_context: bool
+
+ :returns: The attribute.
+ :rtype: list(NodeNG)
+
+ :raises AttributeInferenceError: If the attribute cannot be inferred.
+ """
+ values = self.locals.get(name, [])
+ if name in self.special_attributes and class_context and not values:
+ result = [self.special_attributes.lookup(name)]
+ if name == "__bases__":
+ # Need special treatment, since they are mutable
+ # and we need to return all the values.
+ result += values
+ return result
+
+ # 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 class_context:
+ values += self._metaclass_lookup_attribute(name, context)
+
+ if not values:
+ raise exceptions.AttributeInferenceError(
+ target=self, attribute=name, context=context
+ )
+
+ # Look for AnnAssigns, which are not attributes in the purest sense.
+ for value in values:
+ if isinstance(value, node_classes.AssignName):
+ stmt = value.statement()
+ if isinstance(stmt, node_classes.AnnAssign) and stmt.value is None:
+ raise exceptions.AttributeInferenceError(
+ target=self, attribute=name, context=context
+ )
+ return values
+
+ def _metaclass_lookup_attribute(self, name, context):
+ """Search the given name in the implicit and the explicit metaclass."""
+ attrs = set()
+ implicit_meta = self.implicit_metaclass()
+ metaclass = self.metaclass()
+ for cls in {implicit_meta, metaclass}:
+ if cls and cls != self and isinstance(cls, ClassDef):
+ cls_attributes = self._get_attribute_from_metaclass(cls, name, context)
+ attrs.update(set(cls_attributes))
+ return attrs
+
+ def _get_attribute_from_metaclass(self, cls, name, context):
+ try:
+ attrs = cls.getattr(name, context=context, class_context=True)
+ except exceptions.AttributeInferenceError:
+ return
+
+ for attr in bases._infer_stmts(attrs, context, frame=cls):
+ if not isinstance(attr, FunctionDef):
+ yield attr
+ continue
+
+ if bases._is_property(attr):
+ yield from attr.infer_call_result(self, context)
+ continue
+ if attr.type == "classmethod":
+ # If the method is a classmethod, then it will
+ # be bound to the metaclass, not to the class
+ # from where the attribute is retrieved.
+ # get_wrapping_class could return None, so just
+ # default to the current class.
+ frame = get_wrapping_class(attr) or self
+ yield bases.BoundMethod(attr, frame)
+ elif attr.type == "staticmethod":
+ yield attr
+ else:
+ yield bases.BoundMethod(attr, self)
+
+ def igetattr(self, name, context=None, class_context=True):
+ """Infer the possible values of the given variable.
+
+ :param name: The name of the variable to infer.
+ :type name: str
+
+ :returns: The inferred possible values.
+ :rtype: iterable(NodeNG or Uninferable)
+ """
+ # set lookup name since this is necessary to infer on import nodes for
+ # instance
+ context = contextmod.copy_context(context)
+ context.lookupname = name
+ try:
+ attr = self.getattr(name, context, class_context=class_context)[0]
+ for inferred in bases._infer_stmts([attr], context, frame=self):
+ # yield Uninferable object instead of descriptors when necessary
+ if not isinstance(inferred, node_classes.Const) and isinstance(
+ inferred, bases.Instance
+ ):
+ try:
+ inferred._proxied.getattr("__get__", context)
+ except exceptions.AttributeInferenceError:
+ yield inferred
+ else:
+ yield util.Uninferable
+ else:
+ yield function_to_method(inferred, self)
+ except exceptions.AttributeInferenceError as error:
+ if not name.startswith("__") and self.has_dynamic_getattr(context):
+ # class handle some dynamic attributes, return a Uninferable object
+ yield util.Uninferable
+ else:
+ raise exceptions.InferenceError(
+ error.message, target=self, attribute=name, context=context
+ )
+
+ def has_dynamic_getattr(self, context=None):
+ """Check if the class has a custom __getattr__ or __getattribute__.
+
+ If any such method is found and it is not from
+ builtins, nor from an extension module, then the function
+ will return True.
+
+ :returns: True if the class has a custom
+ __getattr__ or __getattribute__, False otherwise.
+ :rtype: bool
+ """
+
+ def _valid_getattr(node):
+ root = node.root()
+ return root.name != BUILTINS and getattr(root, "pure_python", None)
+
+ try:
+ return _valid_getattr(self.getattr("__getattr__", context)[0])
+ except exceptions.AttributeInferenceError:
+ # if self.newstyle: XXX cause an infinite recursion error
+ try:
+ getattribute = self.getattr("__getattribute__", context)[0]
+ return _valid_getattr(getattribute)
+ except exceptions.AttributeInferenceError:
+ pass
+ return False
+
+ def getitem(self, index, context=None):
+ """Return the inference of a subscript.
+
+ This is basically looking up the method in the metaclass and calling it.
+
+ :returns: The inferred value of a subscript to this class.
+ :rtype: NodeNG
+
+ :raises AstroidTypeError: If this class does not define a
+ ``__getitem__`` method.
+ """
+ try:
+ methods = dunder_lookup.lookup(self, "__getitem__")
+ except exceptions.AttributeInferenceError as exc:
+ raise exceptions.AstroidTypeError(node=self, context=context) from exc
+
+ method = methods[0]
+
+ # Create a new callcontext for providing index as an argument.
+ new_context = contextmod.bind_context_to_node(context, self)
+ new_context.callcontext = contextmod.CallContext(args=[index])
+
+ try:
+ return next(method.infer_call_result(self, new_context))
+ except exceptions.InferenceError:
+ return util.Uninferable
+
+ def methods(self):
+ """Iterate over all of the method defined in this class and its parents.
+
+ :returns: The methods defined on the class.
+ :rtype: iterable(FunctionDef)
+ """
+ done = {}
+ for astroid in itertools.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):
+ """Iterate over all of the method defined in this class only.
+
+ :returns: The methods defined on the class.
+ :rtype: iterable(FunctionDef)
+ """
+ for member in self.values():
+ if isinstance(member, FunctionDef):
+ yield member
+
+ def implicit_metaclass(self):
+ """Get the implicit metaclass of the current class.
+
+ For newstyle classes, this will return an instance of builtins.type.
+ For oldstyle classes, it will simply return None, since there's
+ no implicit metaclass there.
+
+ :returns: The metaclass.
+ :rtype: builtins.type or None
+ """
+ if self.newstyle:
+ return builtin_lookup("type")[1][0]
+ return None
+
+ _metaclass = None
+
+ def declared_metaclass(self, context=None):
+ """Return the explicit declared metaclass for the current class.
+
+ An explicit declared 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.
+
+ :returns: The metaclass of this class,
+ or None if one could not be found.
+ :rtype: NodeNG or None
+ """
+ for base in self.bases:
+ try:
+ for baseobj in base.infer(context=context):
+ if isinstance(baseobj, ClassDef) and baseobj.hide:
+ self._metaclass = baseobj._metaclass
+ self._metaclass_hack = True
+ break
+ except exceptions.InferenceError:
+ pass
+
+ if self._metaclass:
+ # Expects this from Py3k TreeRebuilder
+ try:
+ return next(
+ node
+ for node in self._metaclass.infer(context=context)
+ if node is not util.Uninferable
+ )
+ except (exceptions.InferenceError, StopIteration):
+ return None
+
+ return None
+
+ def _find_metaclass(self, seen=None, context=None):
+ if seen is None:
+ seen = set()
+ seen.add(self)
+
+ klass = self.declared_metaclass(context=context)
+ if klass is None:
+ for parent in self.ancestors(context=context):
+ if parent not in seen:
+ klass = parent._find_metaclass(seen)
+ if klass is not None:
+ break
+ return klass
+
+ def metaclass(self, context=None):
+ """Get 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.
+
+ :returns: The metaclass of this class.
+ :rtype: NodeNG or None
+ """
+ return self._find_metaclass(context=context)
+
+ 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:
+ return None
+ for slots in self.igetattr("__slots__"):
+ # check if __slots__ is a valid type
+ for meth in ITER_METHODS:
+ try:
+ slots.getattr(meth)
+ break
+ except exceptions.AttributeInferenceError:
+ continue
+ else:
+ continue
+
+ if isinstance(slots, node_classes.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, node_classes.Dict):
+ values = [item[0] for item in slots.items]
+ else:
+ values = slots.itered()
+ if values is util.Uninferable:
+ continue
+ if not values:
+ # Stop the iteration, because the class
+ # has an empty list of slots.
+ return values
+
+ for elt in values:
+ try:
+ for inferred in elt.infer():
+ if inferred is util.Uninferable:
+ continue
+ if not isinstance(
+ inferred, node_classes.Const
+ ) or not isinstance(inferred.value, str):
+ continue
+ if not inferred.value:
+ continue
+ yield inferred
+ except exceptions.InferenceError:
+ continue
+
+ return None
+
+ def _slots(self):
+ if not self.newstyle:
+ raise NotImplementedError(
+ "The concept of slots is undefined for old-style classes."
+ )
+
+ slots = self._islots()
+ try:
+ first = next(slots)
+ except StopIteration as exc:
+ # The class doesn't have a __slots__ definition or empty slots.
+ if exc.args and exc.args[0] not in ("", None):
+ return exc.args[0]
+ return None
+ return [first] + list(slots)
+
+ # Cached, because inferring them all the time is expensive
+ @decorators_mod.cached
+ def slots(self):
+ """Get all the slots for this node.
+
+ :returns: The names of slots for this class.
+ If the class doesn't define any slot, through the ``__slots__``
+ variable, then this function will return a None.
+ Also, it will return None in the case the slots were not inferred.
+ :rtype: list(str) or None
+ """
+
+ def grouped_slots():
+ # Not interested in object, since it can't have slots.
+ for cls in self.mro()[:-1]:
+ try:
+ cls_slots = cls._slots()
+ except NotImplementedError:
+ continue
+ if cls_slots is not None:
+ yield from cls_slots
+ else:
+ yield None
+
+ if not self.newstyle:
+ raise NotImplementedError(
+ "The concept of slots is undefined for old-style classes."
+ )
+
+ slots = list(grouped_slots())
+ if not all(slot is not None for slot in slots):
+ return None
+
+ return sorted(slots, key=lambda item: item.value)
+
+ def _inferred_bases(self, context=None):
+ # Similar with .ancestors, but the difference is when one base is inferred,
+ # only the first object is wanted. That's because
+ # we aren't interested in superclasses, as in the following
+ # example:
+ #
+ # class SomeSuperClass(object): pass
+ # class SomeClass(SomeSuperClass): pass
+ # class Test(SomeClass): pass
+ #
+ # Inferring SomeClass from the Test's bases will give
+ # us both SomeClass and SomeSuperClass, but we are interested
+ # only in SomeClass.
+
+ if context is None:
+ context = contextmod.InferenceContext()
+ if not self.bases and self.qname() != "builtins.object":
+ yield builtin_lookup("object")[1][0]
+ return
+
+ for stmt in self.bases:
+ try:
+ baseobj = next(stmt.infer(context=context))
+ except exceptions.InferenceError:
+ continue
+ if isinstance(baseobj, bases.Instance):
+ baseobj = baseobj._proxied
+ if not isinstance(baseobj, ClassDef):
+ continue
+ if not baseobj.hide:
+ yield baseobj
+ else:
+ yield from baseobj.bases
+
+ def _compute_mro(self, context=None):
+ inferred_bases = list(self._inferred_bases(context=context))
+ bases_mro = []
+ for base in inferred_bases:
+ if base is self:
+ continue
+
+ try:
+ mro = base._compute_mro(context=context)
+ bases_mro.append(mro)
+ except NotImplementedError:
+ # Some classes have in their ancestors both newstyle and
+ # old style classes. For these we can't retrieve the .mro,
+ # although in Python it's possible, since the class we are
+ # currently working is in fact new style.
+ # So, we fallback to ancestors here.
+ ancestors = list(base.ancestors(context=context))
+ bases_mro.append(ancestors)
+
+ unmerged_mro = [[self]] + bases_mro + [inferred_bases]
+ unmerged_mro = list(clean_duplicates_mro(unmerged_mro, self, context))
+ return _c3_merge(unmerged_mro, self, context)
+
+ def mro(self, context=None) -> List["ClassDef"]:
+ """Get the method resolution order, using C3 linearization.
+
+ :returns: The list of ancestors, sorted by the mro.
+ :rtype: list(NodeNG)
+ :raises DuplicateBasesError: Duplicate bases in the same class base
+ :raises InconsistentMroError: A class' MRO is inconsistent
+ """
+ return self._compute_mro(context=context)
+
+ def bool_value(self):
+ """Determine the boolean value of this node.
+
+ :returns: The boolean value of this node.
+ For a :class:`ClassDef` this is always ``True``.
+ :rtype: bool
+ """
+ return True
+
+ def get_children(self):
+ if self.decorators is not None:
+ yield self.decorators
+
+ yield from self.bases
+ yield from self.body
+
+ @decorators_mod.cached
+ def _get_assign_nodes(self):
+ children_assign_nodes = (
+ child_node._get_assign_nodes() for child_node in self.body
+ )
+ return list(itertools.chain.from_iterable(children_assign_nodes))