1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
4
5"""
6This module contains the classes for "scoped" node, i.e. which are opening a
7new local scope in the language definition : Module, ClassDef, FunctionDef (and
8Lambda, GeneratorExp, DictComp and SetComp to some extent).
9"""
10
11from __future__ import annotations
12
13import io
14import itertools
15import os
16import sys
17from collections.abc import Generator, Iterable, Iterator, Sequence
18from functools import cached_property, lru_cache
19from typing import TYPE_CHECKING, Any, ClassVar, Literal, NoReturn
20
21from astroid import bases, protocols, util
22from astroid.context import (
23 CallContext,
24 InferenceContext,
25 bind_context_to_node,
26 copy_context,
27)
28from astroid.exceptions import (
29 AstroidBuildingError,
30 AstroidTypeError,
31 AttributeInferenceError,
32 DuplicateBasesError,
33 InconsistentMroError,
34 InferenceError,
35 MroError,
36 ParentMissingError,
37 StatementMissing,
38 TooManyLevelsError,
39)
40from astroid.interpreter.dunder_lookup import lookup
41from astroid.interpreter.objectmodel import ClassModel, FunctionModel, ModuleModel
42from astroid.manager import AstroidManager
43from astroid.nodes import _base_nodes, node_classes
44from astroid.nodes.scoped_nodes.mixin import ComprehensionScope, LocalsDictNodeNG
45from astroid.nodes.scoped_nodes.utils import builtin_lookup
46from astroid.nodes.utils import Position
47from astroid.typing import (
48 InferBinaryOp,
49 InferenceErrorInfo,
50 InferenceResult,
51 SuccessfulInferenceResult,
52)
53
54if sys.version_info >= (3, 11):
55 from typing import Self
56else:
57 from typing_extensions import Self
58
59if TYPE_CHECKING:
60 from astroid import nodes, objects
61 from astroid.nodes import Arguments, Const, NodeNG
62 from astroid.nodes._base_nodes import LookupMixIn
63
64
65ITER_METHODS = ("__iter__", "__getitem__")
66EXCEPTION_BASE_CLASSES = frozenset({"Exception", "BaseException"})
67BUILTIN_DESCRIPTORS = frozenset(
68 {"classmethod", "staticmethod", "builtins.classmethod", "builtins.staticmethod"}
69)
70
71
72def _c3_merge(sequences, cls, context):
73 """Merges MROs in *sequences* to a single MRO using the C3 algorithm.
74
75 Adapted from http://www.python.org/download/releases/2.3/mro/.
76
77 """
78 result = []
79 while True:
80 sequences = [s for s in sequences if s] # purge empty sequences
81 if not sequences:
82 return result
83 for s1 in sequences: # find merge candidates among seq heads
84 candidate = s1[0]
85 for s2 in sequences:
86 if candidate in s2[1:]:
87 candidate = None
88 break # reject the current head, it appears later
89 else:
90 break
91 if not candidate:
92 # Show all the remaining bases, which were considered as
93 # candidates for the next mro sequence.
94 raise InconsistentMroError(
95 message="Cannot create a consistent method resolution order "
96 "for MROs {mros} of class {cls!r}.",
97 mros=sequences,
98 cls=cls,
99 context=context,
100 )
101
102 result.append(candidate)
103 # remove the chosen candidate
104 for seq in sequences:
105 if seq[0] == candidate:
106 del seq[0]
107 return None
108
109
110def clean_typing_generic_mro(sequences: list[list[ClassDef]]) -> None:
111 """A class can inherit from typing.Generic directly, as base,
112 and as base of bases. The merged MRO must however only contain the last entry.
113 To prepare for _c3_merge, remove some typing.Generic entries from
114 sequences if multiple are present.
115
116 This method will check if Generic is in inferred_bases and also
117 part of bases_mro. If true, remove it from inferred_bases
118 as well as its entry the bases_mro.
119
120 Format sequences: [[self]] + bases_mro + [inferred_bases]
121 """
122 bases_mro = sequences[1:-1]
123 inferred_bases = sequences[-1]
124 # Check if Generic is part of inferred_bases
125 for i, base in enumerate(inferred_bases):
126 if base.qname() == "typing.Generic":
127 position_in_inferred_bases = i
128 break
129 else:
130 return
131 # Check if also part of bases_mro
132 # Ignore entry for typing.Generic
133 for i, seq in enumerate(bases_mro):
134 if i == position_in_inferred_bases:
135 continue
136 if any(base.qname() == "typing.Generic" for base in seq):
137 break
138 else:
139 return
140 # Found multiple Generics in mro, remove entry from inferred_bases
141 # and the corresponding one from bases_mro
142 inferred_bases.pop(position_in_inferred_bases)
143 bases_mro.pop(position_in_inferred_bases)
144
145
146def clean_duplicates_mro(
147 sequences: list[list[ClassDef]],
148 cls: ClassDef,
149 context: InferenceContext | None,
150) -> list[list[ClassDef]]:
151 for sequence in sequences:
152 seen = set()
153 for node in sequence:
154 lineno_and_qname = (node.lineno, node.qname())
155 if lineno_and_qname in seen:
156 raise DuplicateBasesError(
157 message="Duplicates found in MROs {mros} for {cls!r}.",
158 mros=sequences,
159 cls=cls,
160 context=context,
161 )
162 seen.add(lineno_and_qname)
163 return sequences
164
165
166def function_to_method(n, klass):
167 if isinstance(n, FunctionDef):
168 if n.type == "classmethod":
169 return bases.BoundMethod(n, klass)
170 if n.type == "property":
171 return n
172 if n.type != "staticmethod":
173 return bases.UnboundMethod(n)
174 return n
175
176
177def _infer_last(
178 arg: SuccessfulInferenceResult, context: InferenceContext
179) -> InferenceResult:
180 res = util.Uninferable
181 for b in arg.infer(context=context.clone()):
182 res = b
183 return res
184
185
186class Module(LocalsDictNodeNG):
187 """Class representing an :class:`ast.Module` node.
188
189 >>> import astroid
190 >>> node = astroid.extract_node('import astroid')
191 >>> node
192 <Import l.1 at 0x7f23b2e4e5c0>
193 >>> node.parent
194 <Module l.0 at 0x7f23b2e4eda0>
195 """
196
197 _astroid_fields = ("doc_node", "body")
198
199 doc_node: Const | None
200 """The doc node associated with this node."""
201
202 # attributes below are set by the builder module or by raw factories
203
204 file_bytes: str | bytes | None = None
205 """The string/bytes that this ast was built from."""
206
207 file_encoding: str | None = None
208 """The encoding of the source file.
209
210 This is used to get unicode out of a source file.
211 Python 2 only.
212 """
213
214 special_attributes = ModuleModel()
215 """The names of special attributes that this module has."""
216
217 # names of module attributes available through the global scope
218 scope_attrs: ClassVar[set[str]] = {
219 "__name__",
220 "__doc__",
221 "__file__",
222 "__path__",
223 "__package__",
224 }
225 """The names of module attributes available through the global scope."""
226
227 _other_fields = (
228 "name",
229 "file",
230 "path",
231 "package",
232 "pure_python",
233 "future_imports",
234 )
235 _other_other_fields = ("locals", "globals")
236
237 def __init__(
238 self,
239 name: str,
240 file: str | None = None,
241 path: Sequence[str] | None = None,
242 package: bool = False,
243 pure_python: bool = True,
244 ) -> None:
245 self.name = name
246 """The name of the module."""
247
248 self.file = file
249 """The path to the file that this ast has been extracted from.
250
251 This will be ``None`` when the representation has been built from a
252 built-in module.
253 """
254
255 self.path = path
256
257 self.package = package
258 """Whether the node represents a package or a module."""
259
260 self.pure_python = pure_python
261 """Whether the ast was built from source."""
262
263 self.globals: dict[str, list[InferenceResult]]
264 """A map of the name of a global variable to the node defining the global."""
265
266 self.locals = self.globals = {}
267 """A map of the name of a local variable to the node defining the local."""
268
269 self.body: list[node_classes.NodeNG] = []
270 """The contents of the module."""
271
272 self.future_imports: set[str] = set()
273 """The imports from ``__future__``."""
274
275 super().__init__(
276 lineno=0, parent=None, col_offset=0, end_lineno=None, end_col_offset=None
277 )
278
279 # pylint: enable=redefined-builtin
280
281 def postinit(
282 self, body: list[node_classes.NodeNG], *, doc_node: Const | None = None
283 ):
284 self.body = body
285 self.doc_node = doc_node
286
287 def _get_stream(self):
288 if self.file_bytes is not None:
289 return io.BytesIO(self.file_bytes)
290 if self.file is not None:
291 # pylint: disable=consider-using-with
292 stream = open(self.file, "rb")
293 return stream
294 return None
295
296 def stream(self):
297 """Get a stream to the underlying file or bytes.
298
299 :type: file or io.BytesIO or None
300 """
301 return self._get_stream()
302
303 def block_range(self, lineno: int) -> tuple[int, int]:
304 """Get a range from where this node starts to where this node ends.
305
306 :param lineno: Unused.
307
308 :returns: The range of line numbers that this node belongs to.
309 """
310 return self.fromlineno, self.tolineno
311
312 def scope_lookup(
313 self, node: LookupMixIn, name: str, offset: int = 0
314 ) -> tuple[LocalsDictNodeNG, list[node_classes.NodeNG]]:
315 """Lookup where the given variable is assigned.
316
317 :param node: The node to look for assignments up to.
318 Any assignments after the given node are ignored.
319
320 :param name: The name of the variable to find assignments for.
321
322 :param offset: The line offset to filter statements up to.
323
324 :returns: This scope node and the list of assignments associated to the
325 given name according to the scope where it has been found (locals,
326 globals or builtin).
327 """
328 if name in self.scope_attrs and name not in self.locals:
329 try:
330 return self, self.getattr(name)
331 except AttributeInferenceError:
332 return self, []
333 return self._scope_lookup(node, name, offset)
334
335 def pytype(self) -> Literal["builtins.module"]:
336 """Get the name of the type that this node represents.
337
338 :returns: The name of the type.
339 """
340 return "builtins.module"
341
342 def display_type(self) -> str:
343 """A human readable type of this node.
344
345 :returns: The type of this node.
346 :rtype: str
347 """
348 return "Module"
349
350 def getattr(
351 self, name, context: InferenceContext | None = None, ignore_locals=False
352 ):
353 if not name:
354 raise AttributeInferenceError(target=self, attribute=name, context=context)
355
356 result = []
357 name_in_locals = name in self.locals
358
359 if name in self.special_attributes and not ignore_locals and not name_in_locals:
360 result = [self.special_attributes.lookup(name)]
361 if name == "__name__":
362 main_const = node_classes.const_factory("__main__")
363 main_const.parent = AstroidManager().builtins_module
364 result.append(main_const)
365 elif not ignore_locals and name_in_locals:
366 result = self.locals[name]
367 elif self.package:
368 try:
369 result = [self.import_module(name, relative_only=True)]
370 except (AstroidBuildingError, SyntaxError) as exc:
371 raise AttributeInferenceError(
372 target=self, attribute=name, context=context
373 ) from exc
374 result = [n for n in result if not isinstance(n, node_classes.DelName)]
375 if result:
376 return result
377 raise AttributeInferenceError(target=self, attribute=name, context=context)
378
379 def igetattr(
380 self, name: str, context: InferenceContext | None = None
381 ) -> Iterator[InferenceResult]:
382 """Infer the possible values of the given variable.
383
384 :param name: The name of the variable to infer.
385
386 :returns: The inferred possible values.
387 """
388 # set lookup name since this is necessary to infer on import nodes for
389 # instance
390 context = copy_context(context)
391 context.lookupname = name
392 try:
393 return bases._infer_stmts(self.getattr(name, context), context, frame=self)
394 except AttributeInferenceError as error:
395 raise InferenceError(
396 str(error), target=self, attribute=name, context=context
397 ) from error
398
399 def fully_defined(self) -> bool:
400 """Check if this module has been build from a .py file.
401
402 If so, the module contains a complete representation,
403 including the code.
404
405 :returns: Whether the module has been built from a .py file.
406 """
407 return self.file is not None and self.file.endswith(".py")
408
409 def statement(self) -> NoReturn:
410 """The first parent node, including self, marked as statement node.
411
412 When called on a :class:`Module` this raises a StatementMissing.
413 """
414 raise StatementMissing(target=self)
415
416 def previous_sibling(self):
417 """The previous sibling statement.
418
419 :returns: The previous sibling statement node.
420 :rtype: NodeNG or None
421 """
422
423 def next_sibling(self):
424 """The next sibling statement node.
425
426 :returns: The next sibling statement node.
427 :rtype: NodeNG or None
428 """
429
430 _absolute_import_activated = True
431
432 def absolute_import_activated(self) -> bool:
433 """Whether :pep:`328` absolute import behaviour has been enabled.
434
435 :returns: Whether :pep:`328` has been enabled.
436 """
437 return self._absolute_import_activated
438
439 def import_module(
440 self,
441 modname: str,
442 relative_only: bool = False,
443 level: int | None = None,
444 use_cache: bool = True,
445 ) -> Module:
446 """Get the ast for a given module as if imported from this module.
447
448 :param modname: The name of the module to "import".
449
450 :param relative_only: Whether to only consider relative imports.
451
452 :param level: The level of relative import.
453
454 :param use_cache: Whether to use the astroid_cache of modules.
455
456 :returns: The imported module ast.
457 """
458 if relative_only and level is None:
459 level = 0
460 absmodname = self.relative_to_absolute_name(modname, level)
461
462 try:
463 return AstroidManager().ast_from_module_name(
464 absmodname, use_cache=use_cache
465 )
466 except AstroidBuildingError:
467 # we only want to import a sub module or package of this module,
468 # skip here
469 if relative_only:
470 raise
471 # Don't repeat the same operation, e.g. for missing modules
472 # like "_winapi" or "nt" on POSIX systems.
473 if modname == absmodname:
474 raise
475 return AstroidManager().ast_from_module_name(modname, use_cache=use_cache)
476
477 def relative_to_absolute_name(self, modname: str, level: int | None) -> str:
478 """Get the absolute module name for a relative import.
479
480 The relative import can be implicit or explicit.
481
482 :param modname: The module name to convert.
483
484 :param level: The level of relative import.
485
486 :returns: The absolute module name.
487
488 :raises TooManyLevelsError: When the relative import refers to a
489 module too far above this one.
490 """
491 # XXX this returns non sens when called on an absolute import
492 # like 'pylint.checkers.astroid.utils'
493 # XXX doesn't return absolute name if self.name isn't absolute name
494 if self.absolute_import_activated() and level is None:
495 return modname
496 if level:
497 if self.package:
498 level = level - 1
499 package_name = self.name.rsplit(".", level)[0]
500 elif (
501 self.path
502 and not os.path.exists(os.path.dirname(self.path[0]) + "/__init__.py")
503 and os.path.exists(
504 os.path.dirname(self.path[0]) + "/" + modname.split(".")[0]
505 )
506 ):
507 level = level - 1
508 package_name = ""
509 else:
510 package_name = self.name.rsplit(".", level)[0]
511 if level and self.name.count(".") < level:
512 raise TooManyLevelsError(level=level, name=self.name)
513
514 elif self.package:
515 package_name = self.name
516 else:
517 package_name = self.name.rsplit(".", 1)[0]
518
519 if package_name:
520 if not modname:
521 return package_name
522 return f"{package_name}.{modname}"
523 return modname
524
525 def wildcard_import_names(self):
526 """The list of imported names when this module is 'wildcard imported'.
527
528 It doesn't include the '__builtins__' name which is added by the
529 current CPython implementation of wildcard imports.
530
531 :returns: The list of imported names.
532 :rtype: list(str)
533 """
534 # We separate the different steps of lookup in try/excepts
535 # to avoid catching too many Exceptions
536 default = [name for name in self.keys() if not name.startswith("_")]
537 try:
538 all_values = self["__all__"]
539 except KeyError:
540 return default
541
542 try:
543 explicit = next(all_values.assigned_stmts())
544 except (InferenceError, StopIteration):
545 return default
546 except AttributeError:
547 # not an assignment node
548 # XXX infer?
549 return default
550
551 # Try our best to detect the exported name.
552 inferred = []
553 try:
554 explicit = next(explicit.infer())
555 except (InferenceError, StopIteration):
556 return default
557 if not isinstance(explicit, (node_classes.Tuple, node_classes.List)):
558 return default
559
560 def str_const(node) -> bool:
561 return isinstance(node, node_classes.Const) and isinstance(node.value, str)
562
563 for node in explicit.elts:
564 if str_const(node):
565 inferred.append(node.value)
566 else:
567 try:
568 inferred_node = next(node.infer())
569 except (InferenceError, StopIteration):
570 continue
571 if str_const(inferred_node):
572 inferred.append(inferred_node.value)
573 return inferred
574
575 def public_names(self):
576 """The list of the names that are publicly available in this module.
577
578 :returns: The list of public names.
579 :rtype: list(str)
580 """
581 return [name for name in self.keys() if not name.startswith("_")]
582
583 def bool_value(self, context: InferenceContext | None = None) -> bool:
584 """Determine the boolean value of this node.
585
586 :returns: The boolean value of this node.
587 For a :class:`Module` this is always ``True``.
588 """
589 return True
590
591 def get_children(self):
592 yield from self.body
593
594 def frame(self, *, future: Literal[None, True] = None) -> Self:
595 """The node's frame node.
596
597 A frame node is a :class:`Module`, :class:`FunctionDef`,
598 :class:`ClassDef` or :class:`Lambda`.
599
600 :returns: The node itself.
601 """
602 return self
603
604 def _infer(
605 self, context: InferenceContext | None = None, **kwargs: Any
606 ) -> Generator[Module]:
607 yield self
608
609
610class __SyntheticRoot(Module):
611 def __init__(self):
612 super().__init__("__astroid_synthetic", pure_python=False)
613
614
615SYNTHETIC_ROOT = __SyntheticRoot()
616
617
618class GeneratorExp(ComprehensionScope):
619 """Class representing an :class:`ast.GeneratorExp` node.
620
621 >>> import astroid
622 >>> node = astroid.extract_node('(thing for thing in things if thing)')
623 >>> node
624 <GeneratorExp l.1 at 0x7f23b2e4e400>
625 """
626
627 _astroid_fields = ("elt", "generators")
628 _other_other_fields = ("locals",)
629 elt: NodeNG
630 """The element that forms the output of the expression."""
631
632 def __init__(
633 self,
634 lineno: int,
635 col_offset: int,
636 parent: NodeNG,
637 *,
638 end_lineno: int | None,
639 end_col_offset: int | None,
640 ) -> None:
641 self.locals = {}
642 """A map of the name of a local variable to the node defining the local."""
643
644 self.generators: list[nodes.Comprehension] = []
645 """The generators that are looped through."""
646
647 super().__init__(
648 lineno=lineno,
649 col_offset=col_offset,
650 end_lineno=end_lineno,
651 end_col_offset=end_col_offset,
652 parent=parent,
653 )
654
655 def postinit(self, elt: NodeNG, generators: list[nodes.Comprehension]) -> None:
656 self.elt = elt
657 self.generators = generators
658
659 def bool_value(self, context: InferenceContext | None = None) -> Literal[True]:
660 """Determine the boolean value of this node.
661
662 :returns: The boolean value of this node.
663 For a :class:`GeneratorExp` this is always ``True``.
664 """
665 return True
666
667 def get_children(self):
668 yield self.elt
669
670 yield from self.generators
671
672
673class DictComp(ComprehensionScope):
674 """Class representing an :class:`ast.DictComp` node.
675
676 >>> import astroid
677 >>> node = astroid.extract_node('{k:v for k, v in things if k > v}')
678 >>> node
679 <DictComp l.1 at 0x7f23b2e41d68>
680 """
681
682 _astroid_fields = ("key", "value", "generators")
683 _other_other_fields = ("locals",)
684 key: NodeNG
685 """What produces the keys."""
686
687 value: NodeNG
688 """What produces the values."""
689
690 def __init__(
691 self,
692 lineno: int,
693 col_offset: int,
694 parent: NodeNG,
695 *,
696 end_lineno: int | None,
697 end_col_offset: int | None,
698 ) -> None:
699 self.locals = {}
700 """A map of the name of a local variable to the node defining the local."""
701
702 super().__init__(
703 lineno=lineno,
704 col_offset=col_offset,
705 end_lineno=end_lineno,
706 end_col_offset=end_col_offset,
707 parent=parent,
708 )
709
710 def postinit(
711 self, key: NodeNG, value: NodeNG, generators: list[nodes.Comprehension]
712 ) -> None:
713 self.key = key
714 self.value = value
715 self.generators = generators
716
717 def bool_value(self, context: InferenceContext | None = None):
718 """Determine the boolean value of this node.
719
720 :returns: The boolean value of this node.
721 For a :class:`DictComp` this is always :class:`Uninferable`.
722 :rtype: Uninferable
723 """
724 return util.Uninferable
725
726 def get_children(self):
727 yield self.key
728 yield self.value
729
730 yield from self.generators
731
732
733class SetComp(ComprehensionScope):
734 """Class representing an :class:`ast.SetComp` node.
735
736 >>> import astroid
737 >>> node = astroid.extract_node('{thing for thing in things if thing}')
738 >>> node
739 <SetComp l.1 at 0x7f23b2e41898>
740 """
741
742 _astroid_fields = ("elt", "generators")
743 _other_other_fields = ("locals",)
744 elt: NodeNG
745 """The element that forms the output of the expression."""
746
747 def __init__(
748 self,
749 lineno: int,
750 col_offset: int,
751 parent: NodeNG,
752 *,
753 end_lineno: int | None,
754 end_col_offset: int | None,
755 ) -> None:
756 self.locals = {}
757 """A map of the name of a local variable to the node defining the local."""
758
759 self.generators: list[nodes.Comprehension] = []
760 """The generators that are looped through."""
761
762 super().__init__(
763 lineno=lineno,
764 col_offset=col_offset,
765 end_lineno=end_lineno,
766 end_col_offset=end_col_offset,
767 parent=parent,
768 )
769
770 def postinit(self, elt: NodeNG, generators: list[nodes.Comprehension]) -> None:
771 self.elt = elt
772 self.generators = generators
773
774 def bool_value(self, context: InferenceContext | None = None):
775 """Determine the boolean value of this node.
776
777 :returns: The boolean value of this node.
778 For a :class:`SetComp` this is always :class:`Uninferable`.
779 :rtype: Uninferable
780 """
781 return util.Uninferable
782
783 def get_children(self):
784 yield self.elt
785
786 yield from self.generators
787
788
789class ListComp(ComprehensionScope):
790 """Class representing an :class:`ast.ListComp` node.
791
792 >>> import astroid
793 >>> node = astroid.extract_node('[thing for thing in things if thing]')
794 >>> node
795 <ListComp l.1 at 0x7f23b2e418d0>
796 """
797
798 _astroid_fields = ("elt", "generators")
799 _other_other_fields = ("locals",)
800
801 elt: NodeNG
802 """The element that forms the output of the expression."""
803
804 def __init__(
805 self,
806 lineno: int,
807 col_offset: int,
808 parent: NodeNG,
809 *,
810 end_lineno: int | None,
811 end_col_offset: int | None,
812 ) -> None:
813 self.locals = {}
814 """A map of the name of a local variable to the node defining it."""
815
816 self.generators: list[nodes.Comprehension] = []
817 """The generators that are looped through."""
818
819 super().__init__(
820 lineno=lineno,
821 col_offset=col_offset,
822 end_lineno=end_lineno,
823 end_col_offset=end_col_offset,
824 parent=parent,
825 )
826
827 def postinit(self, elt: NodeNG, generators: list[nodes.Comprehension]):
828 self.elt = elt
829 self.generators = generators
830
831 def bool_value(self, context: InferenceContext | None = None):
832 """Determine the boolean value of this node.
833
834 :returns: The boolean value of this node.
835 For a :class:`ListComp` this is always :class:`Uninferable`.
836 :rtype: Uninferable
837 """
838 return util.Uninferable
839
840 def get_children(self):
841 yield self.elt
842
843 yield from self.generators
844
845
846def _infer_decorator_callchain(node):
847 """Detect decorator call chaining and see if the end result is a
848 static or a classmethod.
849 """
850 if not isinstance(node, FunctionDef):
851 return None
852 if not node.parent:
853 return None
854 try:
855 result = next(node.infer_call_result(node.parent), None)
856 except InferenceError:
857 return None
858 if isinstance(result, bases.Instance):
859 result = result._proxied
860 if isinstance(result, ClassDef):
861 if result.is_subtype_of("builtins.classmethod"):
862 return "classmethod"
863 if result.is_subtype_of("builtins.staticmethod"):
864 return "staticmethod"
865 if isinstance(result, FunctionDef):
866 if not result.decorators:
867 return None
868 # Determine if this function is decorated with one of the builtin descriptors we want.
869 for decorator in result.decorators.nodes:
870 if isinstance(decorator, node_classes.Name):
871 if decorator.name in BUILTIN_DESCRIPTORS:
872 return decorator.name
873 if (
874 isinstance(decorator, node_classes.Attribute)
875 and isinstance(decorator.expr, node_classes.Name)
876 and decorator.expr.name == "builtins"
877 and decorator.attrname in BUILTIN_DESCRIPTORS
878 ):
879 return decorator.attrname
880 return None
881
882
883class Lambda(_base_nodes.FilterStmtsBaseNode, LocalsDictNodeNG):
884 """Class representing an :class:`ast.Lambda` node.
885
886 >>> import astroid
887 >>> node = astroid.extract_node('lambda arg: arg + 1')
888 >>> node
889 <Lambda.<lambda> l.1 at 0x7f23b2e41518>
890 """
891
892 _astroid_fields: ClassVar[tuple[str, ...]] = ("args", "body")
893 _other_other_fields: ClassVar[tuple[str, ...]] = ("locals",)
894 name = "<lambda>"
895 is_lambda = True
896 special_attributes = FunctionModel()
897 """The names of special attributes that this function has."""
898
899 args: Arguments
900 """The arguments that the function takes."""
901
902 body: NodeNG
903 """The contents of the function body."""
904
905 def implicit_parameters(self) -> Literal[0]:
906 return 0
907
908 @property
909 def type(self) -> Literal["method", "function"]:
910 """Whether this is a method or function.
911
912 :returns: 'method' if this is a method, 'function' otherwise.
913 """
914 if self.args.arguments and self.args.arguments[0].name == "self":
915 if self.parent and isinstance(self.parent.scope(), ClassDef):
916 return "method"
917 return "function"
918
919 def __init__(
920 self,
921 lineno: int,
922 col_offset: int,
923 parent: NodeNG,
924 *,
925 end_lineno: int | None,
926 end_col_offset: int | None,
927 ):
928 self.locals = {}
929 """A map of the name of a local variable to the node defining it."""
930
931 self.instance_attrs: dict[str, list[NodeNG]] = {}
932
933 super().__init__(
934 lineno=lineno,
935 col_offset=col_offset,
936 end_lineno=end_lineno,
937 end_col_offset=end_col_offset,
938 parent=parent,
939 )
940
941 def postinit(self, args: Arguments, body: NodeNG) -> None:
942 self.args = args
943 self.body = body
944
945 def pytype(self) -> Literal["builtins.instancemethod", "builtins.function"]:
946 """Get the name of the type that this node represents.
947
948 :returns: The name of the type.
949 """
950 if "method" in self.type:
951 return "builtins.instancemethod"
952 return "builtins.function"
953
954 def display_type(self) -> str:
955 """A human readable type of this node.
956
957 :returns: The type of this node.
958 :rtype: str
959 """
960 if "method" in self.type:
961 return "Method"
962 return "Function"
963
964 def callable(self) -> Literal[True]:
965 """Whether this node defines something that is callable.
966
967 :returns: Whether this defines something that is callable
968 For a :class:`Lambda` this is always ``True``.
969 """
970 return True
971
972 def argnames(self) -> list[str]:
973 """Get the names of each of the arguments, including that
974 of the collections of variable-length arguments ("args", "kwargs",
975 etc.), as well as positional-only and keyword-only arguments.
976
977 :returns: The names of the arguments.
978 :rtype: list(str)
979 """
980 if self.args.arguments: # maybe None with builtin functions
981 names = [elt.name for elt in self.args.arguments]
982 else:
983 names = []
984
985 return names
986
987 def infer_call_result(
988 self,
989 caller: SuccessfulInferenceResult | None,
990 context: InferenceContext | None = None,
991 ) -> Iterator[InferenceResult]:
992 """Infer what the function returns when called."""
993 return self.body.infer(context)
994
995 def scope_lookup(
996 self, node: LookupMixIn, name: str, offset: int = 0
997 ) -> tuple[LocalsDictNodeNG, list[NodeNG]]:
998 """Lookup where the given names is assigned.
999
1000 :param node: The node to look for assignments up to.
1001 Any assignments after the given node are ignored.
1002
1003 :param name: The name to find assignments for.
1004
1005 :param offset: The line offset to filter statements up to.
1006
1007 :returns: This scope node and the list of assignments associated to the
1008 given name according to the scope where it has been found (locals,
1009 globals or builtin).
1010 """
1011 if (self.args.defaults and node in self.args.defaults) or (
1012 self.args.kw_defaults and node in self.args.kw_defaults
1013 ):
1014 if not self.parent:
1015 raise ParentMissingError(target=self)
1016 frame = self.parent.frame()
1017 # line offset to avoid that def func(f=func) resolve the default
1018 # value to the defined function
1019 offset = -1
1020 else:
1021 # check this is not used in function decorators
1022 frame = self
1023 return frame._scope_lookup(node, name, offset)
1024
1025 def bool_value(self, context: InferenceContext | None = None) -> Literal[True]:
1026 """Determine the boolean value of this node.
1027
1028 :returns: The boolean value of this node.
1029 For a :class:`Lambda` this is always ``True``.
1030 """
1031 return True
1032
1033 def get_children(self):
1034 yield self.args
1035 yield self.body
1036
1037 def frame(self, *, future: Literal[None, True] = None) -> Self:
1038 """The node's frame node.
1039
1040 A frame node is a :class:`Module`, :class:`FunctionDef`,
1041 :class:`ClassDef` or :class:`Lambda`.
1042
1043 :returns: The node itself.
1044 """
1045 return self
1046
1047 def getattr(
1048 self, name: str, context: InferenceContext | None = None
1049 ) -> list[NodeNG]:
1050 if not name:
1051 raise AttributeInferenceError(target=self, attribute=name, context=context)
1052
1053 found_attrs = []
1054 if name in self.instance_attrs:
1055 found_attrs = self.instance_attrs[name]
1056 if name in self.special_attributes:
1057 found_attrs.append(self.special_attributes.lookup(name))
1058 if found_attrs:
1059 return found_attrs
1060 raise AttributeInferenceError(target=self, attribute=name)
1061
1062 def _infer(
1063 self, context: InferenceContext | None = None, **kwargs: Any
1064 ) -> Generator[Lambda]:
1065 yield self
1066
1067 def _get_yield_nodes_skip_functions(self):
1068 """A Lambda node can contain a Yield node in the body."""
1069 yield from self.body._get_yield_nodes_skip_functions()
1070
1071
1072class FunctionDef(
1073 _base_nodes.MultiLineBlockNode,
1074 _base_nodes.FilterStmtsBaseNode,
1075 _base_nodes.Statement,
1076 LocalsDictNodeNG,
1077):
1078 """Class representing an :class:`ast.FunctionDef`.
1079
1080 >>> import astroid
1081 >>> node = astroid.extract_node('''
1082 ... def my_func(arg):
1083 ... return arg + 1
1084 ... ''')
1085 >>> node
1086 <FunctionDef.my_func l.2 at 0x7f23b2e71e10>
1087 """
1088
1089 _astroid_fields = (
1090 "decorators",
1091 "args",
1092 "returns",
1093 "type_params",
1094 "doc_node",
1095 "body",
1096 )
1097 _multi_line_block_fields = ("body",)
1098 returns = None
1099
1100 decorators: node_classes.Decorators | None
1101 """The decorators that are applied to this method or function."""
1102
1103 doc_node: Const | None
1104 """The doc node associated with this node."""
1105
1106 args: Arguments
1107 """The arguments that the function takes."""
1108
1109 is_function = True
1110 """Whether this node indicates a function.
1111
1112 For a :class:`FunctionDef` this is always ``True``.
1113
1114 :type: bool
1115 """
1116 type_annotation = None
1117 """If present, this will contain the type annotation passed by a type comment
1118
1119 :type: NodeNG or None
1120 """
1121 type_comment_args = None
1122 """
1123 If present, this will contain the type annotation for arguments
1124 passed by a type comment
1125 """
1126 type_comment_returns = None
1127 """If present, this will contain the return type annotation, passed by a type comment"""
1128 # attributes below are set by the builder module or by raw factories
1129 _other_fields = ("name", "position")
1130 _other_other_fields = (
1131 "locals",
1132 "_type",
1133 "type_comment_returns",
1134 "type_comment_args",
1135 )
1136 _type = None
1137
1138 name = "<functiondef>"
1139
1140 special_attributes = FunctionModel()
1141 """The names of special attributes that this function has."""
1142
1143 def __init__(
1144 self,
1145 name: str,
1146 lineno: int,
1147 col_offset: int,
1148 parent: NodeNG,
1149 *,
1150 end_lineno: int | None,
1151 end_col_offset: int | None,
1152 ) -> None:
1153 self.name = name
1154 """The name of the function."""
1155
1156 self.locals = {}
1157 """A map of the name of a local variable to the node defining it."""
1158
1159 self.body: list[NodeNG] = []
1160 """The contents of the function body."""
1161
1162 self.type_params: list[nodes.TypeVar | nodes.ParamSpec | nodes.TypeVarTuple] = (
1163 []
1164 )
1165 """PEP 695 (Python 3.12+) type params, e.g. first 'T' in def func[T]() -> T: ..."""
1166
1167 self.instance_attrs: dict[str, list[NodeNG]] = {}
1168
1169 super().__init__(
1170 lineno=lineno,
1171 col_offset=col_offset,
1172 end_lineno=end_lineno,
1173 end_col_offset=end_col_offset,
1174 parent=parent,
1175 )
1176
1177 def postinit(
1178 self,
1179 args: Arguments,
1180 body: list[NodeNG],
1181 decorators: node_classes.Decorators | None = None,
1182 returns=None,
1183 type_comment_returns=None,
1184 type_comment_args=None,
1185 *,
1186 position: Position | None = None,
1187 doc_node: Const | None = None,
1188 type_params: (
1189 list[nodes.TypeVar | nodes.ParamSpec | nodes.TypeVarTuple] | None
1190 ) = None,
1191 ):
1192 """Do some setup after initialisation.
1193
1194 :param args: The arguments that the function takes.
1195
1196 :param body: The contents of the function body.
1197
1198 :param decorators: The decorators that are applied to this
1199 method or function.
1200 :params type_comment_returns:
1201 The return type annotation passed via a type comment.
1202 :params type_comment_args:
1203 The args type annotation passed via a type comment.
1204 :params position:
1205 Position of function keyword(s) and name.
1206 :param doc_node:
1207 The doc node associated with this node.
1208 :param type_params:
1209 The type_params associated with this node.
1210 """
1211 self.args = args
1212 self.body = body
1213 self.decorators = decorators
1214 self.returns = returns
1215 self.type_comment_returns = type_comment_returns
1216 self.type_comment_args = type_comment_args
1217 self.position = position
1218 self.doc_node = doc_node
1219 self.type_params = type_params or []
1220
1221 @cached_property
1222 def extra_decorators(self) -> list[node_classes.Call]:
1223 """The extra decorators that this function can have.
1224
1225 Additional decorators are considered when they are used as
1226 assignments, as in ``method = staticmethod(method)``.
1227 The property will return all the callables that are used for
1228 decoration.
1229 """
1230 if not (self.parent and isinstance(frame := self.parent.frame(), ClassDef)):
1231 return []
1232
1233 decorators: list[node_classes.Call] = []
1234 for assign in frame._assign_nodes_in_scope:
1235 if isinstance(assign.value, node_classes.Call) and isinstance(
1236 assign.value.func, node_classes.Name
1237 ):
1238 for assign_node in assign.targets:
1239 if not isinstance(assign_node, node_classes.AssignName):
1240 # Support only `name = callable(name)`
1241 continue
1242
1243 if assign_node.name != self.name:
1244 # Interested only in the assignment nodes that
1245 # decorates the current method.
1246 continue
1247 try:
1248 meth = frame[self.name]
1249 except KeyError:
1250 continue
1251 else:
1252 # Must be a function and in the same frame as the
1253 # original method.
1254 if (
1255 isinstance(meth, FunctionDef)
1256 and assign_node.frame() == frame
1257 ):
1258 decorators.append(assign.value)
1259 return decorators
1260
1261 def pytype(self) -> Literal["builtins.instancemethod", "builtins.function"]:
1262 """Get the name of the type that this node represents.
1263
1264 :returns: The name of the type.
1265 """
1266 if "method" in self.type:
1267 return "builtins.instancemethod"
1268 return "builtins.function"
1269
1270 def display_type(self) -> str:
1271 """A human readable type of this node.
1272
1273 :returns: The type of this node.
1274 :rtype: str
1275 """
1276 if "method" in self.type:
1277 return "Method"
1278 return "Function"
1279
1280 def callable(self) -> Literal[True]:
1281 return True
1282
1283 def argnames(self) -> list[str]:
1284 """Get the names of each of the arguments, including that
1285 of the collections of variable-length arguments ("args", "kwargs",
1286 etc.), as well as positional-only and keyword-only arguments.
1287
1288 :returns: The names of the arguments.
1289 :rtype: list(str)
1290 """
1291 if self.args.arguments: # maybe None with builtin functions
1292 names = [elt.name for elt in self.args.arguments]
1293 else:
1294 names = []
1295
1296 return names
1297
1298 def getattr(
1299 self, name: str, context: InferenceContext | None = None
1300 ) -> list[NodeNG]:
1301 if not name:
1302 raise AttributeInferenceError(target=self, attribute=name, context=context)
1303
1304 found_attrs = []
1305 if name in self.instance_attrs:
1306 found_attrs = self.instance_attrs[name]
1307 if name in self.special_attributes:
1308 found_attrs.append(self.special_attributes.lookup(name))
1309 if found_attrs:
1310 return found_attrs
1311 raise AttributeInferenceError(target=self, attribute=name)
1312
1313 @cached_property
1314 def type(self) -> str: # pylint: disable=too-many-return-statements # noqa: C901
1315 """The function type for this node.
1316
1317 Possible values are: method, function, staticmethod, classmethod.
1318 """
1319 for decorator in self.extra_decorators:
1320 if decorator.func.name in BUILTIN_DESCRIPTORS:
1321 return decorator.func.name
1322
1323 if not self.parent:
1324 raise ParentMissingError(target=self)
1325
1326 frame = self.parent.frame()
1327 type_name = "function"
1328 if isinstance(frame, ClassDef):
1329 if self.name == "__new__":
1330 return "classmethod"
1331 if self.name == "__init_subclass__":
1332 return "classmethod"
1333 if self.name == "__class_getitem__":
1334 return "classmethod"
1335
1336 type_name = "method"
1337
1338 if not self.decorators:
1339 return type_name
1340
1341 for node in self.decorators.nodes:
1342 if isinstance(node, node_classes.Name):
1343 if node.name in BUILTIN_DESCRIPTORS:
1344 return node.name
1345 if (
1346 isinstance(node, node_classes.Attribute)
1347 and isinstance(node.expr, node_classes.Name)
1348 and node.expr.name == "builtins"
1349 and node.attrname in BUILTIN_DESCRIPTORS
1350 ):
1351 return node.attrname
1352
1353 if isinstance(node, node_classes.Call):
1354 # Handle the following case:
1355 # @some_decorator(arg1, arg2)
1356 # def func(...)
1357 #
1358 try:
1359 current = next(node.func.infer())
1360 except (InferenceError, StopIteration):
1361 continue
1362 _type = _infer_decorator_callchain(current)
1363 if _type is not None:
1364 return _type
1365
1366 try:
1367 for inferred in node.infer():
1368 # Check to see if this returns a static or a class method.
1369 _type = _infer_decorator_callchain(inferred)
1370 if _type is not None:
1371 return _type
1372
1373 if not isinstance(inferred, ClassDef):
1374 continue
1375 for ancestor in inferred.ancestors():
1376 if not isinstance(ancestor, ClassDef):
1377 continue
1378 if ancestor.is_subtype_of("builtins.classmethod"):
1379 return "classmethod"
1380 if ancestor.is_subtype_of("builtins.staticmethod"):
1381 return "staticmethod"
1382 except InferenceError:
1383 pass
1384 return type_name
1385
1386 @cached_property
1387 def fromlineno(self) -> int:
1388 """The first line that this node appears on in the source code.
1389
1390 Can also return 0 if the line can not be determined.
1391 """
1392 # lineno is the line number of the first decorator, we want the def
1393 # statement lineno. Similar to 'ClassDef.fromlineno'
1394 lineno = self.lineno or 0
1395 if self.decorators is not None:
1396 lineno += sum(
1397 node.tolineno - (node.lineno or 0) + 1 for node in self.decorators.nodes
1398 )
1399
1400 return lineno or 0
1401
1402 @cached_property
1403 def blockstart_tolineno(self):
1404 """The line on which the beginning of this block ends.
1405
1406 :type: int
1407 """
1408 if self.returns:
1409 return self.returns.tolineno
1410 return self.args.tolineno
1411
1412 def implicit_parameters(self) -> Literal[0, 1]:
1413 return 1 if self.is_bound() else 0
1414
1415 def block_range(self, lineno: int) -> tuple[int, int]:
1416 """Get a range from the given line number to where this node ends.
1417
1418 :param lineno: Unused.
1419
1420 :returns: The range of line numbers that this node belongs to,
1421 """
1422 return self.fromlineno, self.tolineno
1423
1424 def igetattr(
1425 self, name: str, context: InferenceContext | None = None
1426 ) -> Iterator[InferenceResult]:
1427 """Inferred getattr, which returns an iterator of inferred statements."""
1428 try:
1429 return bases._infer_stmts(self.getattr(name, context), context, frame=self)
1430 except AttributeInferenceError as error:
1431 raise InferenceError(
1432 str(error), target=self, attribute=name, context=context
1433 ) from error
1434
1435 def is_method(self) -> bool:
1436 """Check if this function node represents a method.
1437
1438 :returns: Whether this is a method.
1439 """
1440 # check we are defined in a ClassDef, because this is usually expected
1441 # (e.g. pylint...) when is_method() return True
1442 return (
1443 self.type != "function"
1444 and self.parent is not None
1445 and isinstance(self.parent.frame(), ClassDef)
1446 )
1447
1448 def decoratornames(self, context: InferenceContext | None = None) -> set[str]:
1449 """Get the qualified names of each of the decorators on this function.
1450
1451 :param context:
1452 An inference context that can be passed to inference functions
1453 :returns: The names of the decorators.
1454 """
1455 result = set()
1456 decoratornodes = []
1457 if self.decorators is not None:
1458 decoratornodes += self.decorators.nodes
1459 decoratornodes += self.extra_decorators
1460 for decnode in decoratornodes:
1461 try:
1462 for infnode in decnode.infer(context=context):
1463 result.add(infnode.qname())
1464 except InferenceError:
1465 continue
1466 return result
1467
1468 def is_bound(self) -> bool:
1469 """Check if the function is bound to an instance or class.
1470
1471 :returns: Whether the function is bound to an instance or class.
1472 """
1473 return self.type in {"method", "classmethod"}
1474
1475 def is_abstract(self, pass_is_abstract=True, any_raise_is_abstract=False) -> bool:
1476 """Check if the method is abstract.
1477
1478 A method is considered abstract if any of the following is true:
1479 * The only statement is 'raise NotImplementedError'
1480 * The only statement is 'raise <SomeException>' and any_raise_is_abstract is True
1481 * The only statement is 'pass' and pass_is_abstract is True
1482 * The method is annotated with abc.astractproperty/abc.abstractmethod
1483
1484 :returns: Whether the method is abstract.
1485 """
1486 if self.decorators:
1487 for node in self.decorators.nodes:
1488 try:
1489 inferred = next(node.infer())
1490 except (InferenceError, StopIteration):
1491 continue
1492 if inferred and inferred.qname() in {
1493 "abc.abstractproperty",
1494 "abc.abstractmethod",
1495 }:
1496 return True
1497
1498 for child_node in self.body:
1499 if isinstance(child_node, node_classes.Raise):
1500 if any_raise_is_abstract:
1501 return True
1502 if child_node.raises_not_implemented():
1503 return True
1504 return pass_is_abstract and isinstance(child_node, node_classes.Pass)
1505 # empty function is the same as function with a single "pass" statement
1506 if pass_is_abstract:
1507 return True
1508
1509 return False
1510
1511 def is_generator(self) -> bool:
1512 """Check if this is a generator function.
1513
1514 :returns: Whether this is a generator function.
1515 """
1516 yields_without_lambdas = set(self._get_yield_nodes_skip_lambdas())
1517 yields_without_functions = set(self._get_yield_nodes_skip_functions())
1518 # Want an intersecting member that is neither in a lambda nor a function
1519 return bool(yields_without_lambdas & yields_without_functions)
1520
1521 def _infer(
1522 self, context: InferenceContext | None = None, **kwargs: Any
1523 ) -> Generator[objects.Property | FunctionDef, None, InferenceErrorInfo]:
1524 from astroid import objects # pylint: disable=import-outside-toplevel
1525
1526 if not (self.decorators and bases._is_property(self)):
1527 yield self
1528 return InferenceErrorInfo(node=self, context=context)
1529
1530 if not self.parent:
1531 raise ParentMissingError(target=self)
1532 prop_func = objects.Property(
1533 function=self,
1534 name=self.name,
1535 lineno=self.lineno,
1536 parent=self.parent,
1537 col_offset=self.col_offset,
1538 )
1539 prop_func.postinit(body=[], args=self.args, doc_node=self.doc_node)
1540 yield prop_func
1541 return InferenceErrorInfo(node=self, context=context)
1542
1543 def infer_yield_result(self, context: InferenceContext | None = None):
1544 """Infer what the function yields when called
1545
1546 :returns: What the function yields
1547 :rtype: iterable(NodeNG or Uninferable) or None
1548 """
1549 for yield_ in self.nodes_of_class(node_classes.Yield):
1550 if yield_.value is None:
1551 yield node_classes.Const(None, parent=yield_, lineno=yield_.lineno)
1552 elif yield_.scope() == self:
1553 yield from yield_.value.infer(context=context)
1554
1555 def infer_call_result(
1556 self,
1557 caller: SuccessfulInferenceResult | None,
1558 context: InferenceContext | None = None,
1559 ) -> Iterator[InferenceResult]:
1560 """Infer what the function returns when called."""
1561 if context is None:
1562 context = InferenceContext()
1563 if self.is_generator():
1564 if isinstance(self, AsyncFunctionDef):
1565 generator_cls: type[bases.Generator] = bases.AsyncGenerator
1566 else:
1567 generator_cls = bases.Generator
1568 result = generator_cls(self, generator_initial_context=context)
1569 yield result
1570 return
1571 # This is really a gigantic hack to work around metaclass generators
1572 # that return transient class-generating functions. Pylint's AST structure
1573 # cannot handle a base class object that is only used for calling __new__,
1574 # but does not contribute to the inheritance structure itself. We inject
1575 # a fake class into the hierarchy here for several well-known metaclass
1576 # generators, and filter it out later.
1577 if (
1578 self.name == "with_metaclass"
1579 and caller is not None
1580 and self.args.args
1581 and len(self.args.args) == 1
1582 and self.args.vararg is not None
1583 ):
1584 if isinstance(caller.args, node_classes.Arguments):
1585 assert caller.args.args is not None
1586 metaclass = next(caller.args.args[0].infer(context), None)
1587 elif isinstance(caller.args, list):
1588 metaclass = next(caller.args[0].infer(context), None)
1589 else:
1590 raise TypeError( # pragma: no cover
1591 f"caller.args was neither Arguments nor list; got {type(caller.args)}"
1592 )
1593 if isinstance(metaclass, ClassDef):
1594 class_bases = [_infer_last(x, context) for x in caller.args[1:]]
1595 new_class = ClassDef(
1596 name="temporary_class",
1597 lineno=0,
1598 col_offset=0,
1599 end_lineno=0,
1600 end_col_offset=0,
1601 parent=SYNTHETIC_ROOT,
1602 )
1603 new_class.hide = True
1604 new_class.postinit(
1605 bases=[
1606 base
1607 for base in class_bases
1608 if not isinstance(base, util.UninferableBase)
1609 ],
1610 body=[],
1611 decorators=None,
1612 metaclass=metaclass,
1613 )
1614 yield new_class
1615 return
1616 returns = self._get_return_nodes_skip_functions()
1617
1618 first_return = next(returns, None)
1619 if not first_return:
1620 if self.body:
1621 if self.is_abstract(pass_is_abstract=True, any_raise_is_abstract=True):
1622 yield util.Uninferable
1623 else:
1624 yield node_classes.Const(None)
1625 return
1626
1627 # Builtin dunder methods have empty bodies, return Uninferable.
1628 if (
1629 len(self.body) == 0
1630 and self.name.startswith("__")
1631 and self.name.endswith("__")
1632 and self.root().qname() == "builtins"
1633 ):
1634 yield util.Uninferable
1635 return
1636
1637 raise InferenceError("The function does not have any return statements")
1638
1639 for returnnode in itertools.chain((first_return,), returns):
1640 if returnnode.value is None:
1641 yield node_classes.Const(None)
1642 else:
1643 try:
1644 yield from returnnode.value.infer(context)
1645 except InferenceError:
1646 yield util.Uninferable
1647
1648 def bool_value(self, context: InferenceContext | None = None) -> bool:
1649 """Determine the boolean value of this node.
1650
1651 :returns: The boolean value of this node.
1652 For a :class:`FunctionDef` this is always ``True``.
1653 """
1654 return True
1655
1656 def get_children(self):
1657 if self.decorators is not None:
1658 yield self.decorators
1659
1660 yield self.args
1661
1662 if self.returns is not None:
1663 yield self.returns
1664 yield from self.type_params
1665
1666 yield from self.body
1667
1668 def scope_lookup(
1669 self, node: LookupMixIn, name: str, offset: int = 0
1670 ) -> tuple[LocalsDictNodeNG, list[nodes.NodeNG]]:
1671 """Lookup where the given name is assigned."""
1672 if name == "__class__":
1673 # __class__ is an implicit closure reference created by the compiler
1674 # if any methods in a class body refer to either __class__ or super.
1675 # In our case, we want to be able to look it up in the current scope
1676 # when `__class__` is being used.
1677 if self.parent and isinstance(frame := self.parent.frame(), ClassDef):
1678 return self, [frame]
1679
1680 if (self.args.defaults and node in self.args.defaults) or (
1681 self.args.kw_defaults and node in self.args.kw_defaults
1682 ):
1683 if not self.parent:
1684 raise ParentMissingError(target=self)
1685 frame = self.parent.frame()
1686 # line offset to avoid that def func(f=func) resolve the default
1687 # value to the defined function
1688 offset = -1
1689 else:
1690 # check this is not used in function decorators
1691 frame = self
1692 return frame._scope_lookup(node, name, offset)
1693
1694 def frame(self, *, future: Literal[None, True] = None) -> Self:
1695 """The node's frame node.
1696
1697 A frame node is a :class:`Module`, :class:`FunctionDef`,
1698 :class:`ClassDef` or :class:`Lambda`.
1699
1700 :returns: The node itself.
1701 """
1702 return self
1703
1704
1705class AsyncFunctionDef(FunctionDef):
1706 """Class representing an :class:`ast.FunctionDef` node.
1707
1708 A :class:`AsyncFunctionDef` is an asynchronous function
1709 created with the `async` keyword.
1710
1711 >>> import astroid
1712 >>> node = astroid.extract_node('''
1713 async def func(things):
1714 async for thing in things:
1715 print(thing)
1716 ''')
1717 >>> node
1718 <AsyncFunctionDef.func l.2 at 0x7f23b2e416d8>
1719 >>> node.body[0]
1720 <AsyncFor l.3 at 0x7f23b2e417b8>
1721 """
1722
1723
1724def _is_metaclass(
1725 klass: ClassDef,
1726 seen: set[str] | None = None,
1727 context: InferenceContext | None = None,
1728) -> bool:
1729 """Return if the given class can be
1730 used as a metaclass.
1731 """
1732 if klass.name == "type":
1733 return True
1734 if seen is None:
1735 seen = set()
1736 for base in klass.bases:
1737 try:
1738 for baseobj in base.infer(context=context):
1739 baseobj_name = baseobj.qname()
1740 if baseobj_name in seen:
1741 continue
1742
1743 seen.add(baseobj_name)
1744 if isinstance(baseobj, bases.Instance):
1745 # not abstract
1746 return False
1747 if baseobj is klass:
1748 continue
1749 if not isinstance(baseobj, ClassDef):
1750 continue
1751 if baseobj._type == "metaclass":
1752 return True
1753 if _is_metaclass(baseobj, seen, context=context):
1754 return True
1755 except InferenceError:
1756 continue
1757 return False
1758
1759
1760def _class_type(
1761 klass: ClassDef,
1762 ancestors: set[str] | None = None,
1763 context: InferenceContext | None = None,
1764) -> Literal["class", "exception", "metaclass"]:
1765 """return a ClassDef node type to differ metaclass and exception
1766 from 'regular' classes
1767 """
1768 # XXX we have to store ancestors in case we have an ancestor loop
1769 if klass._type is not None:
1770 return klass._type
1771 if _is_metaclass(klass, context=context):
1772 klass._type = "metaclass"
1773 elif klass.name.endswith("Exception"):
1774 klass._type = "exception"
1775 else:
1776 if ancestors is None:
1777 ancestors = set()
1778 klass_name = klass.qname()
1779 if klass_name in ancestors:
1780 # XXX we are in loop ancestors, and have found no type
1781 klass._type = "class"
1782 return "class"
1783 ancestors.add(klass_name)
1784 for base in klass.ancestors(recurs=False):
1785 name = _class_type(base, ancestors)
1786 if name != "class":
1787 if name == "metaclass" and klass._type != "metaclass":
1788 # don't propagate it if the current class
1789 # can't be a metaclass
1790 continue
1791 klass._type = base.type
1792 break
1793 if klass._type is None:
1794 klass._type = "class"
1795 return klass._type
1796
1797
1798def get_wrapping_class(node):
1799 """Get the class that wraps the given node.
1800
1801 We consider that a class wraps a node if the class
1802 is a parent for the said node.
1803
1804 :returns: The class that wraps the given node
1805 :rtype: ClassDef or None
1806 """
1807
1808 klass = node.frame()
1809 while klass is not None and not isinstance(klass, ClassDef):
1810 if klass.parent is None:
1811 klass = None
1812 else:
1813 klass = klass.parent.frame()
1814 return klass
1815
1816
1817class ClassDef(
1818 _base_nodes.FilterStmtsBaseNode, LocalsDictNodeNG, _base_nodes.Statement
1819):
1820 """Class representing an :class:`ast.ClassDef` node.
1821
1822 >>> import astroid
1823 >>> node = astroid.extract_node('''
1824 class Thing:
1825 def my_meth(self, arg):
1826 return arg + self.offset
1827 ''')
1828 >>> node
1829 <ClassDef.Thing l.2 at 0x7f23b2e9e748>
1830 """
1831
1832 # some of the attributes below are set by the builder module or
1833 # by a raw factories
1834
1835 # a dictionary of class instances attributes
1836 _astroid_fields = (
1837 "decorators",
1838 "bases",
1839 "keywords",
1840 "doc_node",
1841 "body",
1842 "type_params",
1843 ) # name
1844
1845 decorators = None
1846 """The decorators that are applied to this class.
1847
1848 :type: Decorators or None
1849 """
1850 special_attributes = ClassModel()
1851 """The names of special attributes that this class has.
1852
1853 :type: objectmodel.ClassModel
1854 """
1855
1856 _type: Literal["class", "exception", "metaclass"] | None = None
1857 _metaclass: NodeNG | None = None
1858 _metaclass_hack = False
1859 hide = False
1860 type = property(
1861 _class_type,
1862 doc=(
1863 "The class type for this node.\n\n"
1864 "Possible values are: class, metaclass, exception.\n\n"
1865 ":type: str"
1866 ),
1867 )
1868 _other_fields = ("name", "is_dataclass", "position")
1869 _other_other_fields = "locals"
1870
1871 def __init__(
1872 self,
1873 name: str,
1874 lineno: int,
1875 col_offset: int,
1876 parent: NodeNG,
1877 *,
1878 end_lineno: int | None,
1879 end_col_offset: int | None,
1880 ) -> None:
1881 self.instance_attrs: dict[str, NodeNG] = {}
1882 self.locals = {}
1883 """A map of the name of a local variable to the node defining it."""
1884
1885 self.keywords: list[node_classes.Keyword] = []
1886 """The keywords given to the class definition.
1887
1888 This is usually for :pep:`3115` style metaclass declaration.
1889 """
1890
1891 self.bases: list[SuccessfulInferenceResult] = []
1892 """What the class inherits from."""
1893
1894 self.body: list[NodeNG] = []
1895 """The contents of the class body."""
1896
1897 self.name = name
1898 """The name of the class."""
1899
1900 self.decorators = None
1901 """The decorators that are applied to this class."""
1902
1903 self.doc_node: Const | None = None
1904 """The doc node associated with this node."""
1905
1906 self.is_dataclass: bool = False
1907 """Whether this class is a dataclass."""
1908
1909 self.type_params: list[nodes.TypeVar | nodes.ParamSpec | nodes.TypeVarTuple] = (
1910 []
1911 )
1912 """PEP 695 (Python 3.12+) type params, e.g. class MyClass[T]: ..."""
1913
1914 super().__init__(
1915 lineno=lineno,
1916 col_offset=col_offset,
1917 end_lineno=end_lineno,
1918 end_col_offset=end_col_offset,
1919 parent=parent,
1920 )
1921 for local_name, node in self.implicit_locals():
1922 self.add_local_node(node, local_name)
1923
1924 infer_binary_op: ClassVar[InferBinaryOp[ClassDef]] = (
1925 protocols.instance_class_infer_binary_op
1926 )
1927
1928 def implicit_parameters(self) -> Literal[1]:
1929 return 1
1930
1931 def implicit_locals(self):
1932 """Get implicitly defined class definition locals.
1933
1934 :returns: the the name and Const pair for each local
1935 :rtype: tuple(tuple(str, node_classes.Const), ...)
1936 """
1937 locals_ = (("__module__", self.special_attributes.attr___module__),)
1938 # __qualname__ is defined in PEP3155
1939 locals_ += (
1940 ("__qualname__", self.special_attributes.attr___qualname__),
1941 ("__annotations__", self.special_attributes.attr___annotations__),
1942 )
1943 return locals_
1944
1945 # pylint: disable=redefined-outer-name
1946 def postinit(
1947 self,
1948 bases: list[SuccessfulInferenceResult],
1949 body: list[NodeNG],
1950 decorators: node_classes.Decorators | None,
1951 newstyle: bool | None = None,
1952 metaclass: NodeNG | None = None,
1953 keywords: list[node_classes.Keyword] | None = None,
1954 *,
1955 position: Position | None = None,
1956 doc_node: Const | None = None,
1957 type_params: (
1958 list[nodes.TypeVar | nodes.ParamSpec | nodes.TypeVarTuple] | None
1959 ) = None,
1960 ) -> None:
1961 if keywords is not None:
1962 self.keywords = keywords
1963 self.bases = bases
1964 self.body = body
1965 self.decorators = decorators
1966 self._metaclass = metaclass
1967 self.position = position
1968 self.doc_node = doc_node
1969 self.type_params = type_params or []
1970
1971 @cached_property
1972 def blockstart_tolineno(self):
1973 """The line on which the beginning of this block ends.
1974
1975 :type: int
1976 """
1977 if self.bases:
1978 return self.bases[-1].tolineno
1979
1980 return self.fromlineno
1981
1982 def block_range(self, lineno: int) -> tuple[int, int]:
1983 """Get a range from the given line number to where this node ends.
1984
1985 :param lineno: Unused.
1986
1987 :returns: The range of line numbers that this node belongs to,
1988 """
1989 return self.fromlineno, self.tolineno
1990
1991 def pytype(self) -> Literal["builtins.type"]:
1992 """Get the name of the type that this node represents.
1993
1994 :returns: The name of the type.
1995 """
1996 return "builtins.type"
1997
1998 def display_type(self) -> str:
1999 """A human readable type of this node.
2000
2001 :returns: The type of this node.
2002 :rtype: str
2003 """
2004 return "Class"
2005
2006 def callable(self) -> bool:
2007 """Whether this node defines something that is callable.
2008
2009 :returns: Whether this defines something that is callable.
2010 For a :class:`ClassDef` this is always ``True``.
2011 """
2012 return True
2013
2014 def is_subtype_of(self, type_name, context: InferenceContext | None = None) -> bool:
2015 """Whether this class is a subtype of the given type.
2016
2017 :param type_name: The name of the type of check against.
2018 :type type_name: str
2019
2020 :returns: Whether this class is a subtype of the given type.
2021 """
2022 if self.qname() == type_name:
2023 return True
2024
2025 return any(anc.qname() == type_name for anc in self.ancestors(context=context))
2026
2027 def _infer_type_call(self, caller, context):
2028 try:
2029 name_node = next(caller.args[0].infer(context))
2030 except StopIteration as e:
2031 raise InferenceError(node=caller.args[0], context=context) from e
2032 if isinstance(name_node, node_classes.Const) and isinstance(
2033 name_node.value, str
2034 ):
2035 name = name_node.value
2036 else:
2037 return util.Uninferable
2038
2039 result = ClassDef(
2040 name,
2041 lineno=0,
2042 col_offset=0,
2043 end_lineno=0,
2044 end_col_offset=0,
2045 parent=caller.parent,
2046 )
2047
2048 # Get the bases of the class.
2049 try:
2050 class_bases = next(caller.args[1].infer(context))
2051 except StopIteration as e:
2052 raise InferenceError(node=caller.args[1], context=context) from e
2053 if isinstance(class_bases, (node_classes.Tuple, node_classes.List)):
2054 bases = []
2055 for base in class_bases.itered():
2056 inferred = next(base.infer(context=context), None)
2057 if inferred:
2058 bases.append(
2059 node_classes.EvaluatedObject(original=base, value=inferred)
2060 )
2061 result.bases = bases
2062 else:
2063 # There is currently no AST node that can represent an 'unknown'
2064 # node (Uninferable is not an AST node), therefore we simply return Uninferable here
2065 # although we know at least the name of the class.
2066 return util.Uninferable
2067
2068 # Get the members of the class
2069 try:
2070 members = next(caller.args[2].infer(context))
2071 except (InferenceError, StopIteration):
2072 members = None
2073
2074 if members and isinstance(members, node_classes.Dict):
2075 for attr, value in members.items:
2076 if isinstance(attr, node_classes.Const) and isinstance(attr.value, str):
2077 result.locals[attr.value] = [value]
2078
2079 return result
2080
2081 def infer_call_result(
2082 self,
2083 caller: SuccessfulInferenceResult | None,
2084 context: InferenceContext | None = None,
2085 ) -> Iterator[InferenceResult]:
2086 """infer what a class is returning when called"""
2087 if self.is_subtype_of("builtins.type", context) and len(caller.args) == 3:
2088 result = self._infer_type_call(caller, context)
2089 yield result
2090 return
2091
2092 dunder_call = None
2093 try:
2094 metaclass = self.metaclass(context=context)
2095 if metaclass is not None:
2096 # Only get __call__ if it's defined locally for the metaclass.
2097 # Otherwise we will find ObjectModel.__call__ which will
2098 # return an instance of the metaclass. Instantiating the class is
2099 # handled later.
2100 if "__call__" in metaclass.locals:
2101 dunder_call = next(metaclass.igetattr("__call__", context))
2102 except (AttributeInferenceError, StopIteration):
2103 pass
2104
2105 if dunder_call and dunder_call.qname() != "builtins.type.__call__":
2106 # Call type.__call__ if not set metaclass
2107 # (since type is the default metaclass)
2108 context = bind_context_to_node(context, self)
2109 # ``infer_call_result`` may be called through the public API without
2110 # a call context (it defaults to None); only annotate the callee
2111 # when there is a call context to annotate.
2112 if context.callcontext:
2113 context.callcontext.callee = dunder_call
2114 yield from dunder_call.infer_call_result(caller, context)
2115 else:
2116 yield self.instantiate_class()
2117
2118 def scope_lookup(
2119 self, node: LookupMixIn, name: str, offset: int = 0
2120 ) -> tuple[LocalsDictNodeNG, list[nodes.NodeNG]]:
2121 """Lookup where the given name is assigned.
2122
2123 :param node: The node to look for assignments up to.
2124 Any assignments after the given node are ignored.
2125
2126 :param name: The name to find assignments for.
2127
2128 :param offset: The line offset to filter statements up to.
2129
2130 :returns: This scope node and the list of assignments associated to the
2131 given name according to the scope where it has been found (locals,
2132 globals or builtin).
2133 """
2134 # If the name looks like a builtin name, just try to look
2135 # into the upper scope of this class. We might have a
2136 # decorator that it's poorly named after a builtin object
2137 # inside this class.
2138 lookup_upper_frame = (
2139 isinstance(node.parent, node_classes.Decorators)
2140 and name in AstroidManager().builtins_module
2141 )
2142 if (
2143 any(
2144 node == base or (base.parent_of(node) and not self.type_params)
2145 for base in self.bases
2146 )
2147 or lookup_upper_frame
2148 ):
2149 # Handle the case where we have either a name
2150 # in the bases of a class, which exists before
2151 # the actual definition or the case where we have
2152 # a Getattr node, with that name.
2153 #
2154 # name = ...
2155 # class A(name):
2156 # def name(self): ...
2157 #
2158 # import name
2159 # class A(name.Name):
2160 # def name(self): ...
2161 if not self.parent:
2162 raise ParentMissingError(target=self)
2163 frame = self.parent.frame()
2164 # line offset to avoid that class A(A) resolve the ancestor to
2165 # the defined class
2166 offset = -1
2167 else:
2168 frame = self
2169 return frame._scope_lookup(node, name, offset)
2170
2171 @property
2172 def basenames(self):
2173 """The names of the parent classes
2174
2175 Names are given in the order they appear in the class definition.
2176
2177 :type: list(str)
2178 """
2179 return [bnode.as_string() for bnode in self.bases]
2180
2181 def ancestors(
2182 self, recurs: bool = True, context: InferenceContext | None = None
2183 ) -> Generator[ClassDef]:
2184 """Iterate over the base classes in prefixed depth first order.
2185
2186 :param recurs: Whether to recurse or return direct ancestors only.
2187
2188 :returns: The base classes
2189 """
2190 # FIXME: should be possible to choose the resolution order
2191 # FIXME: inference make infinite loops possible here
2192 yielded = {self}
2193 if context is None:
2194 context = InferenceContext()
2195 if not self.bases and self.qname() != "builtins.object":
2196 # This should always be a ClassDef (which we don't assert for)
2197 yield builtin_lookup("object")[1][0] # type: ignore[misc]
2198 return
2199
2200 for stmt in self.bases:
2201 with context.restore_path():
2202 try:
2203 for baseobj in stmt.infer(context):
2204 if not isinstance(baseobj, ClassDef):
2205 if isinstance(baseobj, bases.Instance):
2206 baseobj = baseobj._proxied
2207 else:
2208 continue
2209 if not baseobj.hide:
2210 if baseobj in yielded:
2211 continue
2212 yielded.add(baseobj)
2213 yield baseobj
2214 if not recurs:
2215 continue
2216 for grandpa in baseobj.ancestors(recurs=True, context=context):
2217 if grandpa is self:
2218 # This class is the ancestor of itself.
2219 break
2220 if grandpa in yielded:
2221 continue
2222 yielded.add(grandpa)
2223 yield grandpa
2224 except InferenceError:
2225 continue
2226
2227 def local_attr_ancestors(self, name, context: InferenceContext | None = None):
2228 """Iterate over the parents that define the given name.
2229
2230 :param name: The name to find definitions for.
2231 :type name: str
2232
2233 :returns: The parents that define the given name.
2234 :rtype: iterable(NodeNG)
2235 """
2236 # Look up in the mro if we can. This will result in the
2237 # attribute being looked up just as Python does it.
2238 try:
2239 ancestors: Iterable[ClassDef] = self.mro(context)[1:]
2240 except MroError:
2241 # Fallback to use ancestors, we can't determine
2242 # a sane MRO.
2243 ancestors = self.ancestors(context=context)
2244 for astroid in ancestors:
2245 if name in astroid:
2246 yield astroid
2247
2248 def instance_attr_ancestors(self, name, context: InferenceContext | None = None):
2249 """Iterate over the parents that define the given name as an attribute.
2250
2251 :param name: The name to find definitions for.
2252 :type name: str
2253
2254 :returns: The parents that define the given name as
2255 an instance attribute.
2256 :rtype: iterable(NodeNG)
2257 """
2258 for astroid in self.ancestors(context=context):
2259 if name in astroid.instance_attrs:
2260 yield astroid
2261
2262 def has_base(self, node) -> bool:
2263 """Whether this class directly inherits from the given node.
2264
2265 :param node: The node to check for.
2266 :type node: NodeNG
2267
2268 :returns: Whether this class directly inherits from the given node.
2269 """
2270 return node in self.bases
2271
2272 def local_attr(self, name, context: InferenceContext | None = None):
2273 """Get the list of assign nodes associated to the given name.
2274
2275 Assignments are looked for in both this class and in parents.
2276
2277 :returns: The list of assignments to the given name.
2278 :rtype: list(NodeNG)
2279
2280 :raises AttributeInferenceError: If no attribute with this name
2281 can be found in this class or parent classes.
2282 """
2283 result = []
2284 if name in self.locals:
2285 result = self.locals[name]
2286 else:
2287 class_node = next(self.local_attr_ancestors(name, context), None)
2288 if class_node:
2289 result = class_node.locals[name]
2290 result = [n for n in result if not isinstance(n, node_classes.DelAttr)]
2291 if result:
2292 return result
2293 raise AttributeInferenceError(target=self, attribute=name, context=context)
2294
2295 def instance_attr(self, name, context: InferenceContext | None = None):
2296 """Get the list of nodes associated to the given attribute name.
2297
2298 Assignments are looked for in both this class and in parents.
2299
2300 :returns: The list of assignments to the given name.
2301 :rtype: list(NodeNG)
2302
2303 :raises AttributeInferenceError: If no attribute with this name
2304 can be found in this class or parent classes.
2305 """
2306 # Return a copy, so we don't modify self.instance_attrs,
2307 # which could lead to infinite loop.
2308 values = list(self.instance_attrs.get(name, []))
2309 # get all values from parents
2310 for class_node in self.instance_attr_ancestors(name, context):
2311 values += class_node.instance_attrs[name]
2312 values = [n for n in values if not isinstance(n, node_classes.DelAttr)]
2313 if values:
2314 return values
2315 raise AttributeInferenceError(target=self, attribute=name, context=context)
2316
2317 def instantiate_class(self) -> bases.Instance:
2318 """Get an :class:`Instance` of the :class:`ClassDef` node.
2319
2320 :returns: An :class:`Instance` of the :class:`ClassDef` node
2321 """
2322 from astroid import objects # pylint: disable=import-outside-toplevel
2323
2324 try:
2325 if any(cls.name in EXCEPTION_BASE_CLASSES for cls in self.mro()):
2326 # Subclasses of exceptions can be exception instances
2327 return objects.ExceptionInstance(self)
2328 except MroError:
2329 pass
2330 return bases.Instance(self)
2331
2332 def getattr(
2333 self,
2334 name: str,
2335 context: InferenceContext | None = None,
2336 class_context: bool = True,
2337 ) -> list[InferenceResult]:
2338 """Get an attribute from this class, using Python's attribute semantic.
2339
2340 This method doesn't look in the :attr:`instance_attrs` dictionary
2341 since it is done by an :class:`Instance` proxy at inference time.
2342 It may return an :class:`Uninferable` object if
2343 the attribute has not been
2344 found, but a ``__getattr__`` or ``__getattribute__`` method is defined.
2345 If ``class_context`` is given, then it is considered that the
2346 attribute is accessed from a class context,
2347 e.g. ClassDef.attribute, otherwise it might have been accessed
2348 from an instance as well. If ``class_context`` is used in that
2349 case, then a lookup in the implicit metaclass and the explicit
2350 metaclass will be done.
2351
2352 :param name: The attribute to look for.
2353
2354 :param class_context: Whether the attribute can be accessed statically.
2355
2356 :returns: The attribute.
2357
2358 :raises AttributeInferenceError: If the attribute cannot be inferred.
2359 """
2360 if not name:
2361 raise AttributeInferenceError(target=self, attribute=name, context=context)
2362
2363 # don't modify the list in self.locals!
2364 values: list[InferenceResult] = list(self.locals.get(name, []))
2365 for classnode in self.ancestors(recurs=True, context=context):
2366 values += classnode.locals.get(name, [])
2367
2368 if name in self.special_attributes and class_context and not values:
2369 special_attr = self.special_attributes.lookup(name)
2370 if not isinstance(special_attr, node_classes.Unknown):
2371 result = [special_attr]
2372 return result
2373
2374 if class_context:
2375 values += self._metaclass_lookup_attribute(name, context)
2376
2377 result: list[InferenceResult] = []
2378 for value in values:
2379 if isinstance(value, node_classes.AssignName):
2380 stmt = value.statement()
2381 # Ignore AnnAssigns without value, which are not attributes in the purest sense.
2382 if isinstance(stmt, node_classes.AnnAssign) and stmt.value is None:
2383 continue
2384 result.append(value)
2385
2386 if not result:
2387 raise AttributeInferenceError(target=self, attribute=name, context=context)
2388
2389 return result
2390
2391 @lru_cache(maxsize=1024) # noqa
2392 def _metaclass_lookup_attribute(self, name, context):
2393 """Search the given name in the implicit and the explicit metaclass."""
2394 attrs = set()
2395 implicit_meta = self.implicit_metaclass()
2396 context = copy_context(context)
2397 metaclass = self.metaclass(context=context)
2398 for cls in (implicit_meta, metaclass):
2399 if cls and cls != self and isinstance(cls, ClassDef):
2400 cls_attributes = self._get_attribute_from_metaclass(cls, name, context)
2401 attrs.update(cls_attributes)
2402 return attrs
2403
2404 def _get_attribute_from_metaclass(self, cls, name, context):
2405 from astroid import objects # pylint: disable=import-outside-toplevel
2406
2407 try:
2408 attrs = cls.getattr(name, context=context, class_context=True)
2409 except AttributeInferenceError:
2410 return
2411
2412 for attr in bases._infer_stmts(attrs, context, frame=cls):
2413 if not isinstance(attr, FunctionDef):
2414 yield attr
2415 continue
2416
2417 if isinstance(attr, objects.Property):
2418 yield attr
2419 continue
2420 if attr.type == "classmethod":
2421 # If the method is a classmethod, then it will
2422 # be bound to the metaclass, not to the class
2423 # from where the attribute is retrieved.
2424 # get_wrapping_class could return None, so just
2425 # default to the current class.
2426 frame = get_wrapping_class(attr) or self
2427 yield bases.BoundMethod(attr, frame)
2428 elif attr.type == "staticmethod":
2429 yield attr
2430 else:
2431 yield bases.BoundMethod(attr, self)
2432
2433 def igetattr(
2434 self,
2435 name: str,
2436 context: InferenceContext | None = None,
2437 class_context: bool = True,
2438 ) -> Iterator[InferenceResult]:
2439 """Infer the possible values of the given variable.
2440
2441 :param name: The name of the variable to infer.
2442
2443 :returns: The inferred possible values.
2444 """
2445 from astroid import objects # pylint: disable=import-outside-toplevel
2446
2447 # set lookup name since this is necessary to infer on import nodes for
2448 # instance
2449 context = copy_context(context)
2450 context.lookupname = name
2451
2452 metaclass = self.metaclass(context=context)
2453 try:
2454 attributes = self.getattr(name, context, class_context=class_context)
2455 # If we have more than one attribute, make sure that those starting from
2456 # the second one are from the same scope. This is to account for modifications
2457 # to the attribute happening *after* the attribute's definition (e.g. AugAssigns on lists)
2458 if len(attributes) > 1:
2459 first_attr, attributes = attributes[0], attributes[1:]
2460 first_scope = first_attr.parent.scope()
2461 attributes = [first_attr] + [
2462 attr
2463 for attr in attributes
2464 if attr.parent and attr.parent.scope() == first_scope
2465 ]
2466 functions = [attr for attr in attributes if isinstance(attr, FunctionDef)]
2467 setter = None
2468 for function in functions:
2469 dec_names = function.decoratornames(context=context)
2470 for dec_name in dec_names:
2471 if dec_name is util.Uninferable:
2472 continue
2473 if dec_name.split(".")[-1] == "setter":
2474 setter = function
2475 if setter:
2476 break
2477 if functions:
2478 # Prefer only the last function, unless a property is involved.
2479 last_function = functions[-1]
2480 attributes = [
2481 a
2482 for a in attributes
2483 if a not in functions or a is last_function or bases._is_property(a)
2484 ]
2485
2486 for inferred in bases._infer_stmts(attributes, context, frame=self):
2487 # yield Uninferable object instead of descriptors when necessary
2488 if not isinstance(inferred, node_classes.Const) and isinstance(
2489 inferred, bases.Instance
2490 ):
2491 try:
2492 inferred._proxied.getattr("__get__", context)
2493 except AttributeInferenceError:
2494 yield inferred
2495 else:
2496 yield util.Uninferable
2497 elif isinstance(inferred, objects.Property):
2498 function = inferred.function
2499 if not class_context:
2500 if not context.callcontext and not setter:
2501 context.callcontext = CallContext(
2502 args=function.args.arguments, callee=function
2503 )
2504 # Through an instance so we can solve the property
2505 yield from function.infer_call_result(
2506 caller=self, context=context
2507 )
2508 # If we're in a class context, we need to determine if the property
2509 # was defined in the metaclass (a derived class must be a subclass of
2510 # the metaclass of all its bases), in which case we can resolve the
2511 # property. If not, i.e. the property is defined in some base class
2512 # instead, then we return the property object
2513 elif metaclass and function.parent.scope() is metaclass:
2514 # Resolve a property as long as it is not accessed through
2515 # the class itself.
2516 yield from function.infer_call_result(
2517 caller=self, context=context
2518 )
2519 else:
2520 yield inferred
2521 else:
2522 yield function_to_method(inferred, self)
2523 except AttributeInferenceError as error:
2524 if not name.startswith("__") and self.has_dynamic_getattr(context):
2525 # class handle some dynamic attributes, return a Uninferable object
2526 yield util.Uninferable
2527 else:
2528 raise InferenceError(
2529 str(error), target=self, attribute=name, context=context
2530 ) from error
2531
2532 def has_dynamic_getattr(self, context: InferenceContext | None = None) -> bool:
2533 """Check if the class has a custom __getattr__ or __getattribute__.
2534
2535 If any such method is found and it is not from
2536 builtins, nor from an extension module, then the function
2537 will return True.
2538
2539 :returns: Whether the class has a custom __getattr__ or __getattribute__.
2540 """
2541
2542 def _valid_getattr(node):
2543 root = node.root()
2544 return root.name != "builtins" and getattr(root, "pure_python", None)
2545
2546 try:
2547 return _valid_getattr(self.getattr("__getattr__", context)[0])
2548 except AttributeInferenceError:
2549 try:
2550 getattribute = self.getattr("__getattribute__", context)[0]
2551 return _valid_getattr(getattribute)
2552 except AttributeInferenceError:
2553 pass
2554 return False
2555
2556 def getitem(self, index, context: InferenceContext | None = None):
2557 """Return the inference of a subscript.
2558
2559 This is basically looking up the method in the metaclass and calling it.
2560
2561 :returns: The inferred value of a subscript to this class.
2562 :rtype: NodeNG
2563
2564 :raises AstroidTypeError: If this class does not define a
2565 ``__getitem__`` method.
2566 """
2567 try:
2568 methods = lookup(self, "__getitem__", context=context)
2569 except AttributeInferenceError as exc:
2570 if isinstance(self, ClassDef):
2571 # subscripting a class definition may be
2572 # achieved thanks to __class_getitem__ method
2573 # which is a classmethod defined in the class
2574 # that supports subscript and not in the metaclass
2575 try:
2576 methods = self.getattr("__class_getitem__")
2577 # Here it is assumed that the __class_getitem__ node is
2578 # a FunctionDef. One possible improvement would be to deal
2579 # with more generic inference.
2580 except AttributeInferenceError:
2581 raise AstroidTypeError(node=self, context=context) from exc
2582 else:
2583 raise AstroidTypeError(node=self, context=context) from exc
2584
2585 method = methods[0]
2586
2587 # Create a new callcontext for providing index as an argument.
2588 new_context = bind_context_to_node(context, self)
2589 new_context.callcontext = CallContext(args=[index], callee=method)
2590
2591 try:
2592 return next(method.infer_call_result(self, new_context), util.Uninferable)
2593 except AttributeError as exc:
2594 # Starting with python3.9, builtin types list, dict etc...
2595 # are subscriptable thanks to __class_getitem___ classmethod.
2596 # However in such case the method is bound to an EmptyNode and
2597 # EmptyNode doesn't have infer_call_result method yielding to
2598 # AttributeError
2599 if (
2600 isinstance(method, node_classes.EmptyNode)
2601 and self.pytype() == "builtins.type"
2602 ):
2603 return self
2604 # ``__class_getitem__`` may resolve to a non-callable node (e.g. an
2605 # ``AssignName`` or an ``Import``), which has no
2606 # ``infer_call_result`` method.
2607 if not method.callable():
2608 raise AstroidTypeError(node=self, context=context) from exc
2609 raise
2610 except InferenceError:
2611 return util.Uninferable
2612
2613 def methods(self):
2614 """Iterate over all of the method defined in this class and its parents.
2615
2616 :returns: The methods defined on the class.
2617 :rtype: iterable(FunctionDef)
2618 """
2619 done = {}
2620 for astroid in itertools.chain(iter((self,)), self.ancestors()):
2621 for meth in astroid.mymethods():
2622 if meth.name in done:
2623 continue
2624 done[meth.name] = None
2625 yield meth
2626
2627 def mymethods(self):
2628 """Iterate over all of the method defined in this class only.
2629
2630 :returns: The methods defined on the class.
2631 :rtype: iterable(FunctionDef)
2632 """
2633 for member in self.values():
2634 if isinstance(member, FunctionDef):
2635 yield member
2636
2637 def implicit_metaclass(self):
2638 """Get the implicit metaclass of the current class.
2639
2640 This will return an instance of builtins.type.
2641
2642 :returns: The metaclass.
2643 :rtype: builtins.type
2644 """
2645 return builtin_lookup("type")[1][0]
2646
2647 def declared_metaclass(
2648 self, context: InferenceContext | None = None
2649 ) -> SuccessfulInferenceResult | None:
2650 """Return the explicit declared metaclass for the current class.
2651
2652 An explicit declared metaclass is defined
2653 either by passing the ``metaclass`` keyword argument
2654 in the class definition line (Python 3) or (Python 2) by
2655 having a ``__metaclass__`` class attribute, or if there are
2656 no explicit bases but there is a global ``__metaclass__`` variable.
2657
2658 :returns: The metaclass of this class,
2659 or None if one could not be found.
2660 """
2661 for base in self.bases:
2662 try:
2663 for baseobj in base.infer(context=context):
2664 if isinstance(baseobj, ClassDef) and baseobj.hide:
2665 self._metaclass = baseobj._metaclass
2666 self._metaclass_hack = True
2667 break
2668 except InferenceError:
2669 pass
2670
2671 if self._metaclass:
2672 # Expects this from Py3k TreeRebuilder
2673 try:
2674 return next(
2675 node
2676 for node in self._metaclass.infer(context=context)
2677 if not isinstance(node, util.UninferableBase)
2678 )
2679 except (InferenceError, StopIteration):
2680 return None
2681
2682 return None
2683
2684 def _find_metaclass(
2685 self, seen: set[ClassDef] | None = None, context: InferenceContext | None = None
2686 ) -> SuccessfulInferenceResult | None:
2687 if seen is None:
2688 seen = set()
2689 seen.add(self)
2690
2691 klass = self.declared_metaclass(context=context)
2692 if klass is None:
2693 for parent in self.ancestors(context=context):
2694 if parent not in seen:
2695 klass = parent._find_metaclass(seen)
2696 if klass is not None:
2697 break
2698 return klass
2699
2700 def metaclass(
2701 self, context: InferenceContext | None = None
2702 ) -> SuccessfulInferenceResult | None:
2703 """Get the metaclass of this class.
2704
2705 If this class does not define explicitly a metaclass,
2706 then the first defined metaclass in ancestors will be used
2707 instead.
2708
2709 :returns: The metaclass of this class.
2710 """
2711 return self._find_metaclass(context=context)
2712
2713 def has_metaclass_hack(self) -> bool:
2714 return self._metaclass_hack
2715
2716 def _islots(self):
2717 """Return an iterator with the inferred slots."""
2718 if "__slots__" not in self.locals:
2719 return None
2720 try:
2721 slots_attributes = list(self.igetattr("__slots__"))
2722 except InferenceError:
2723 # ``__slots__`` is present in ``locals`` but cannot be inferred,
2724 # e.g. an annotation-only ``__slots__: ...`` with no assigned value.
2725 return None
2726 for slots in slots_attributes:
2727 # check if __slots__ is a valid type
2728 for meth in ITER_METHODS:
2729 try:
2730 slots.getattr(meth)
2731 break
2732 except AttributeInferenceError:
2733 continue
2734 else:
2735 continue
2736
2737 if isinstance(slots, node_classes.Const):
2738 # a string. Ignore the following checks,
2739 # but yield the node, only if it has a value
2740 if slots.value:
2741 yield slots
2742 continue
2743 if not hasattr(slots, "itered"):
2744 # we can't obtain the values, maybe a .deque?
2745 continue
2746
2747 if isinstance(slots, node_classes.Dict):
2748 values = [item[0] for item in slots.items]
2749 else:
2750 values = slots.itered()
2751 if isinstance(values, util.UninferableBase):
2752 continue
2753 if not values:
2754 # Stop the iteration, because the class
2755 # has an empty list of slots.
2756 return values
2757
2758 for elt in values:
2759 try:
2760 for inferred in elt.infer():
2761 if not (
2762 isinstance(inferred, node_classes.Const)
2763 and isinstance(inferred.value, str)
2764 ):
2765 continue
2766 if not inferred.value:
2767 continue
2768 yield inferred
2769 except InferenceError:
2770 continue
2771
2772 return None
2773
2774 def _slots(self):
2775
2776 slots = self._islots()
2777 try:
2778 first = next(slots)
2779 except StopIteration as exc:
2780 # The class doesn't have a __slots__ definition or empty slots.
2781 if exc.args and exc.args[0] not in ("", None):
2782 return exc.args[0]
2783 return None
2784 return [first, *slots]
2785
2786 # Cached, because inferring them all the time is expensive
2787 @cached_property
2788 def _all_slots(self):
2789 """Get all the slots for this node.
2790
2791 :returns: The names of slots for this class.
2792 If the class doesn't define any slot, through the ``__slots__``
2793 variable, then this function will return a None.
2794 Also, it will return None in the case the slots were not inferred.
2795 :rtype: list(str) or None
2796 """
2797
2798 def grouped_slots(
2799 mro: list[ClassDef],
2800 ) -> Iterator[node_classes.NodeNG | None]:
2801 for cls in mro:
2802 # Not interested in object, since it can't have slots.
2803 if cls.qname() == "builtins.object":
2804 continue
2805 try:
2806 cls_slots = cls._slots()
2807 except NotImplementedError:
2808 continue
2809 if cls_slots is not None:
2810 yield from cls_slots
2811 else:
2812 yield None
2813
2814 try:
2815 mro = self.mro()
2816 except MroError as e:
2817 raise NotImplementedError(
2818 "Cannot get slots while parsing mro fails."
2819 ) from e
2820
2821 slots = list(grouped_slots(mro))
2822 if not all(slot is not None for slot in slots):
2823 return None
2824
2825 return sorted(set(slots), key=lambda item: item.value)
2826
2827 def slots(self):
2828 return self._all_slots
2829
2830 def _inferred_bases(
2831 self,
2832 context: InferenceContext | None = None,
2833 *,
2834 base_classes: frozenset[ClassDef] = frozenset(),
2835 ):
2836 # Similar with .ancestors, but the difference is when one base is inferred,
2837 # only the first object is wanted. That's because
2838 # we aren't interested in superclasses, as in the following
2839 # example:
2840 #
2841 # class SomeSuperClass(object): pass
2842 # class SomeClass(SomeSuperClass): pass
2843 # class Test(SomeClass): pass
2844 #
2845 # Inferring SomeClass from the Test's bases will give
2846 # us both SomeClass and SomeSuperClass, but we are interested
2847 # only in SomeClass.
2848
2849 if context is None:
2850 context = InferenceContext()
2851 if not self.bases and self.qname() != "builtins.object":
2852 yield builtin_lookup("object")[1][0]
2853 return
2854
2855 for stmt in self.bases:
2856 try:
2857 baseobj = _infer_last(stmt, context)
2858 except InferenceError:
2859 continue
2860 if isinstance(baseobj, bases.Instance):
2861 baseobj = baseobj._proxied
2862 if not isinstance(baseobj, ClassDef):
2863 continue
2864 if baseobj is self or baseobj in base_classes:
2865 # Circular base due to name rebinding (e.g. pdb.Pdb = CustomPdb
2866 # where CustomPdb inherits from pdb.Pdb). Fall back to the
2867 # first non-circular inferred value from the base expression.
2868 baseobj = self._resolve_circular_base(stmt, context)
2869 if baseobj is None:
2870 continue
2871 if not baseobj.hide:
2872 yield baseobj
2873 else:
2874 yield from baseobj.bases
2875
2876 def _resolve_circular_base(
2877 self,
2878 stmt: nodes.NodeNG,
2879 context: InferenceContext | None,
2880 ) -> ClassDef | None:
2881 """Resolve a circular base reference by finding the original class.
2882
2883 When a name is rebound to a subclass (e.g. ``pdb.Pdb = CustomPdb``),
2884 ``_infer_last`` follows the rebinding and returns the subclass itself.
2885 This method iterates through all inferred values to find the first
2886 non-circular ClassDef.
2887 """
2888 inf_context = copy_context(context)
2889 try:
2890 for inferred in stmt.infer(context=inf_context):
2891 if isinstance(inferred, bases.Instance):
2892 inferred = inferred._proxied
2893 if isinstance(inferred, ClassDef) and inferred is not self:
2894 return inferred
2895 except InferenceError:
2896 pass
2897 return None
2898
2899 def _compute_mro(
2900 self,
2901 context: InferenceContext,
2902 *,
2903 base_chain: frozenset[ClassDef] = frozenset(),
2904 ):
2905 if self.qname() == "builtins.object":
2906 return [self]
2907
2908 inferred_bases = list(
2909 self._inferred_bases(context=context, base_classes=base_chain)
2910 )
2911 bases_mro = []
2912 base_chain |= {self}
2913 for base in inferred_bases:
2914 if base in base_chain:
2915 continue
2916
2917 mro = base._compute_mro(context=context, base_chain=base_chain)
2918 bases_mro.append(mro)
2919
2920 unmerged_mro: list[list[ClassDef]] = [[self], *bases_mro, inferred_bases]
2921 unmerged_mro = clean_duplicates_mro(unmerged_mro, self, context)
2922 clean_typing_generic_mro(unmerged_mro)
2923 return _c3_merge(unmerged_mro, self, context)
2924
2925 def mro(self, context: InferenceContext | None = None) -> list[ClassDef]:
2926 """Get the method resolution order, using C3 linearization.
2927
2928 :returns: The list of ancestors, sorted by the mro.
2929 :rtype: list(NodeNG)
2930 :raises DuplicateBasesError: Duplicate bases in the same class base
2931 :raises InconsistentMroError: A class' MRO is inconsistent
2932 """
2933 return self._compute_mro(context=context)
2934
2935 def bool_value(self, context: InferenceContext | None = None) -> Literal[True]:
2936 """Determine the boolean value of this node.
2937
2938 :returns: The boolean value of this node.
2939 For a :class:`ClassDef` this is always ``True``.
2940 """
2941 return True
2942
2943 def get_children(self):
2944 if self.decorators is not None:
2945 yield self.decorators
2946
2947 yield from self.bases
2948 if self.keywords is not None:
2949 yield from self.keywords
2950 yield from self.type_params
2951
2952 yield from self.body
2953
2954 @cached_property
2955 def _assign_nodes_in_scope(self):
2956 children_assign_nodes = (
2957 child_node._assign_nodes_in_scope for child_node in self.body
2958 )
2959 return list(itertools.chain.from_iterable(children_assign_nodes))
2960
2961 def frame(self, *, future: Literal[None, True] = None) -> Self:
2962 """The node's frame node.
2963
2964 A frame node is a :class:`Module`, :class:`FunctionDef`,
2965 :class:`ClassDef` or :class:`Lambda`.
2966
2967 :returns: The node itself.
2968 """
2969 return self
2970
2971 def _infer(
2972 self, context: InferenceContext | None = None, **kwargs: Any
2973 ) -> Generator[ClassDef]:
2974 yield self