mixin.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  2. # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
  3. # Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
  4. """This module contains mixin classes for scoped nodes."""
  5. from __future__ import annotations
  6. from typing import TYPE_CHECKING, TypeVar, overload
  7. from astroid.filter_statements import _filter_stmts
  8. from astroid.nodes import node_classes, scoped_nodes
  9. from astroid.nodes.scoped_nodes.utils import builtin_lookup
  10. from astroid.typing import SuccessfulInferenceResult
  11. if TYPE_CHECKING:
  12. from astroid import nodes
  13. _T = TypeVar("_T")
  14. class LocalsDictNodeNG(node_classes.LookupMixIn):
  15. """this class provides locals handling common to Module, FunctionDef
  16. and ClassDef nodes, including a dict like interface for direct access
  17. to locals information
  18. """
  19. # attributes below are set by the builder module or by raw factories
  20. locals: dict[str, list[SuccessfulInferenceResult]] = {}
  21. """A map of the name of a local variable to the node defining the local."""
  22. def qname(self) -> str:
  23. """Get the 'qualified' name of the node.
  24. For example: module.name, module.class.name ...
  25. :returns: The qualified name.
  26. :rtype: str
  27. """
  28. # pylint: disable=no-member; github.com/pycqa/astroid/issues/278
  29. if self.parent is None:
  30. return self.name
  31. return f"{self.parent.frame(future=True).qname()}.{self.name}"
  32. def scope(self: _T) -> _T:
  33. """The first parent node defining a new scope.
  34. :returns: The first parent scope node.
  35. :rtype: Module or FunctionDef or ClassDef or Lambda or GenExpr
  36. """
  37. return self
  38. def scope_lookup(self, node, name: str, offset: int = 0):
  39. """Lookup where the given variable is assigned.
  40. :param node: The node to look for assignments up to.
  41. Any assignments after the given node are ignored.
  42. :type node: NodeNG
  43. :param name: The name of the variable to find assignments for.
  44. :param offset: The line offset to filter statements up to.
  45. :returns: This scope node and the list of assignments associated to the
  46. given name according to the scope where it has been found (locals,
  47. globals or builtin).
  48. :rtype: tuple(str, list(NodeNG))
  49. """
  50. raise NotImplementedError
  51. def _scope_lookup(self, node, name, offset=0):
  52. """XXX method for interfacing the scope lookup"""
  53. try:
  54. stmts = _filter_stmts(node, self.locals[name], self, offset)
  55. except KeyError:
  56. stmts = ()
  57. if stmts:
  58. return self, stmts
  59. # Handle nested scopes: since class names do not extend to nested
  60. # scopes (e.g., methods), we find the next enclosing non-class scope
  61. pscope = self.parent and self.parent.scope()
  62. while pscope is not None:
  63. if not isinstance(pscope, scoped_nodes.ClassDef):
  64. return pscope.scope_lookup(node, name)
  65. pscope = pscope.parent and pscope.parent.scope()
  66. # self is at the top level of a module, or is enclosed only by ClassDefs
  67. return builtin_lookup(name)
  68. def set_local(self, name: str, stmt: nodes.NodeNG) -> None:
  69. """Define that the given name is declared in the given statement node.
  70. .. seealso:: :meth:`scope`
  71. :param name: The name that is being defined.
  72. :param stmt: The statement that defines the given name.
  73. """
  74. # assert not stmt in self.locals.get(name, ()), (self, stmt)
  75. self.locals.setdefault(name, []).append(stmt)
  76. __setitem__ = set_local
  77. def _append_node(self, child: nodes.NodeNG) -> None:
  78. """append a child, linking it in the tree"""
  79. # pylint: disable=no-member; depending by the class
  80. # which uses the current class as a mixin or base class.
  81. # It's rewritten in 2.0, so it makes no sense for now
  82. # to spend development time on it.
  83. self.body.append(child) # type: ignore[attr-defined]
  84. child.parent = self
  85. @overload
  86. def add_local_node(
  87. self, child_node: nodes.ClassDef, name: str | None = ...
  88. ) -> None:
  89. ...
  90. @overload
  91. def add_local_node(self, child_node: nodes.NodeNG, name: str) -> None:
  92. ...
  93. def add_local_node(self, child_node: nodes.NodeNG, name: str | None = None) -> None:
  94. """Append a child that should alter the locals of this scope node.
  95. :param child_node: The child node that will alter locals.
  96. :param name: The name of the local that will be altered by
  97. the given child node.
  98. """
  99. if name != "__class__":
  100. # add __class__ node as a child will cause infinite recursion later!
  101. self._append_node(child_node)
  102. self.set_local(name or child_node.name, child_node) # type: ignore[attr-defined]
  103. def __getitem__(self, item: str) -> SuccessfulInferenceResult:
  104. """The first node the defines the given local.
  105. :param item: The name of the locally defined object.
  106. :raises KeyError: If the name is not defined.
  107. """
  108. return self.locals[item][0]
  109. def __iter__(self):
  110. """Iterate over the names of locals defined in this scoped node.
  111. :returns: The names of the defined locals.
  112. :rtype: iterable(str)
  113. """
  114. return iter(self.keys())
  115. def keys(self):
  116. """The names of locals defined in this scoped node.
  117. :returns: The names of the defined locals.
  118. :rtype: list(str)
  119. """
  120. return list(self.locals.keys())
  121. def values(self):
  122. """The nodes that define the locals in this scoped node.
  123. :returns: The nodes that define locals.
  124. :rtype: list(NodeNG)
  125. """
  126. # pylint: disable=consider-using-dict-items
  127. # It look like this class override items/keys/values,
  128. # probably not worth the headache
  129. return [self[key] for key in self.keys()]
  130. def items(self):
  131. """Get the names of the locals and the node that defines the local.
  132. :returns: The names of locals and their associated node.
  133. :rtype: list(tuple(str, NodeNG))
  134. """
  135. return list(zip(self.keys(), self.values()))
  136. def __contains__(self, name) -> bool:
  137. """Check if a local is defined in this scope.
  138. :param name: The name of the local to check for.
  139. :type name: str
  140. :returns: Whether this node has a local of the given name,
  141. """
  142. return name in self.locals
  143. class ComprehensionScope(LocalsDictNodeNG):
  144. """Scoping for different types of comprehensions."""
  145. scope_lookup = LocalsDictNodeNG._scope_lookup
  146. generators: list[nodes.Comprehension]
  147. """The generators that are looped through."""