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"""Module for some node classes. More nodes in scoped_nodes.py"""
6
7from __future__ import annotations
8
9import abc
10import ast
11import itertools
12import operator
13import sys
14import typing
15import warnings
16from collections.abc import Callable, Generator, Iterable, Iterator, Mapping
17from functools import cached_property
18from typing import TYPE_CHECKING, Any, ClassVar, Literal, Union
19
20from astroid import decorators, protocols, util
21from astroid.bases import Instance, _infer_stmts
22from astroid.const import _EMPTY_OBJECT_MARKER, PY314_PLUS, Context
23from astroid.context import CallContext, InferenceContext, copy_context
24from astroid.exceptions import (
25 AstroidBuildingError,
26 AstroidError,
27 AstroidIndexError,
28 AstroidTypeError,
29 AstroidValueError,
30 AttributeInferenceError,
31 InferenceError,
32 NameInferenceError,
33 NoDefault,
34 ParentMissingError,
35 _NonDeducibleTypeHierarchy,
36)
37from astroid.interpreter import dunder_lookup
38from astroid.manager import AstroidManager
39from astroid.nodes import _base_nodes
40from astroid.nodes.const import OP_PRECEDENCE
41from astroid.nodes.node_ng import NodeNG
42from astroid.nodes.scoped_nodes import SYNTHETIC_ROOT
43from astroid.typing import (
44 ConstFactoryResult,
45 InferenceErrorInfo,
46 InferenceResult,
47 SuccessfulInferenceResult,
48)
49
50if sys.version_info >= (3, 11):
51 from typing import Self
52else:
53 from typing_extensions import Self
54
55if TYPE_CHECKING:
56 from astroid import nodes
57 from astroid.nodes import LocalsDictNodeNG
58 from astroid.nodes.node_ng import FrameType
59
60
61def _is_const(value) -> bool:
62 return isinstance(value, tuple(CONST_CLS))
63
64
65_NodesT = typing.TypeVar("_NodesT", bound=NodeNG)
66_BadOpMessageT = typing.TypeVar("_BadOpMessageT", bound=util.BadOperationMessage)
67
68# pylint: disable-next=consider-alternative-union-syntax
69AssignedStmtsPossibleNode = Union["List", "Tuple", "AssignName", "AssignAttr", None]
70AssignedStmtsCall = Callable[
71 [
72 _NodesT,
73 AssignedStmtsPossibleNode,
74 InferenceContext | None,
75 list[int] | None,
76 ],
77 Any,
78]
79InferBinaryOperation = Callable[
80 [_NodesT, InferenceContext | None],
81 Generator[InferenceResult | _BadOpMessageT],
82]
83InferLHS = Callable[
84 [_NodesT, InferenceContext | None],
85 Generator[InferenceResult, None, InferenceErrorInfo | None],
86]
87InferUnaryOp = Callable[[_NodesT, str], ConstFactoryResult]
88
89
90@decorators.raise_if_nothing_inferred
91def unpack_infer(stmt, context: InferenceContext | None = None):
92 """recursively generate nodes inferred by the given statement.
93 If the inferred value is a list or a tuple, recurse on the elements
94 """
95 if isinstance(stmt, (List, Tuple)):
96 for elt in stmt.elts:
97 if elt is util.Uninferable:
98 yield elt
99 continue
100 yield from unpack_infer(elt, context)
101 return {"node": stmt, "context": context}
102 # if inferred is a final node, return it and stop
103 inferred = next(stmt.infer(context), util.Uninferable)
104 if inferred is stmt:
105 yield inferred
106 return {"node": stmt, "context": context}
107 # else, infer recursively, except Uninferable object that should be returned as is
108 for inferred in stmt.infer(context):
109 if isinstance(inferred, util.UninferableBase):
110 yield inferred
111 else:
112 yield from unpack_infer(inferred, context)
113
114 return {"node": stmt, "context": context}
115
116
117def are_exclusive(stmt1, stmt2, exceptions: list[str] | None = None) -> bool:
118 """return true if the two given statements are mutually exclusive
119
120 `exceptions` may be a list of exception names. If specified, discard If
121 branches and check one of the statement is in an exception handler catching
122 one of the given exceptions.
123
124 algorithm :
125 1) index stmt1's parents
126 2) climb among stmt2's parents until we find a common parent
127 3) if the common parent is a If or Try statement, look if nodes are
128 in exclusive branches
129 """
130 # index stmt1's parents
131 stmt1_parents = {}
132 children = {}
133 previous = stmt1
134 for node in stmt1.node_ancestors():
135 stmt1_parents[node] = 1
136 children[node] = previous
137 previous = node
138 # climb among stmt2's parents until we find a common parent
139 previous = stmt2
140 for node in stmt2.node_ancestors():
141 if node in stmt1_parents:
142 # if the common parent is a If or Try statement, look if
143 # nodes are in exclusive branches
144 if isinstance(node, If) and exceptions is None:
145 c2attr, c2node = node.locate_child(previous)
146 c1attr, c1node = node.locate_child(children[node])
147 if "test" in (c1attr, c2attr):
148 # If any node is `If.test`, then it must be inclusive with
149 # the other node (`If.body` and `If.orelse`)
150 return False
151 if c1attr != c2attr:
152 # different `If` branches (`If.body` and `If.orelse`)
153 return True
154 elif isinstance(node, Try):
155 c2attr, c2node = node.locate_child(previous)
156 c1attr, c1node = node.locate_child(children[node])
157 if c1node is not c2node:
158 first_in_body_caught_by_handlers = (
159 c2attr == "handlers"
160 and c1attr == "body"
161 and previous.catch(exceptions)
162 )
163 second_in_body_caught_by_handlers = (
164 c2attr == "body"
165 and c1attr == "handlers"
166 and children[node].catch(exceptions)
167 )
168 first_in_else_other_in_handlers = (
169 c2attr == "handlers" and c1attr == "orelse"
170 )
171 second_in_else_other_in_handlers = (
172 c2attr == "orelse" and c1attr == "handlers"
173 )
174 if any(
175 (
176 first_in_body_caught_by_handlers,
177 second_in_body_caught_by_handlers,
178 first_in_else_other_in_handlers,
179 second_in_else_other_in_handlers,
180 )
181 ):
182 return True
183 elif c2attr == "handlers" and c1attr == "handlers":
184 return previous is not children[node]
185 return False
186 previous = node
187 return False
188
189
190# getitem() helpers.
191
192_SLICE_SENTINEL = object()
193
194
195def _slice_value(index, context: InferenceContext | None = None):
196 """Get the value of the given slice index."""
197
198 if isinstance(index, Const):
199 if isinstance(index.value, (int, type(None))):
200 return index.value
201 elif index is None:
202 return None
203 else:
204 # Try to infer what the index actually is.
205 # Since we can't return all the possible values,
206 # we'll stop at the first possible value.
207 try:
208 inferred = next(index.infer(context=context))
209 except (InferenceError, StopIteration):
210 pass
211 else:
212 if isinstance(inferred, Const):
213 if isinstance(inferred.value, (int, type(None))):
214 return inferred.value
215
216 # Use a sentinel, because None can be a valid
217 # value that this function can return,
218 # as it is the case for unspecified bounds.
219 return _SLICE_SENTINEL
220
221
222def _infer_slice(node, context: InferenceContext | None = None):
223 lower = _slice_value(node.lower, context)
224 upper = _slice_value(node.upper, context)
225 step = _slice_value(node.step, context)
226 if all(elem is not _SLICE_SENTINEL for elem in (lower, upper, step)):
227 return slice(lower, upper, step)
228
229 raise AstroidTypeError(
230 message="Could not infer slice used in subscript",
231 node=node,
232 index=node.parent,
233 context=context,
234 )
235
236
237def _container_getitem(instance, elts, index, context: InferenceContext | None = None):
238 """Get a slice or an item, using the given *index*, for the given sequence."""
239 try:
240 if isinstance(index, Slice):
241 index_slice = _infer_slice(index, context=context)
242 new_cls = instance.__class__()
243 new_cls.elts = elts[index_slice]
244 new_cls.parent = instance.parent
245 return new_cls
246 if isinstance(index, Const):
247 return elts[index.value]
248 except ValueError as exc:
249 raise AstroidValueError(
250 message="Slice {index!r} cannot index container",
251 node=instance,
252 index=index,
253 context=context,
254 ) from exc
255 except IndexError as exc:
256 raise AstroidIndexError(
257 message="Index {index!s} out of range",
258 node=instance,
259 index=index,
260 context=context,
261 ) from exc
262 except TypeError as exc:
263 raise AstroidTypeError(
264 message="Type error {error!r}", node=instance, index=index, context=context
265 ) from exc
266
267 raise AstroidTypeError(f"Could not use {index} as subscript index")
268
269
270class BaseContainer(_base_nodes.ParentAssignNode, Instance, metaclass=abc.ABCMeta):
271 """Base class for Set, FrozenSet, Tuple and List."""
272
273 _astroid_fields = ("elts",)
274
275 def __init__(
276 self,
277 lineno: int | None,
278 col_offset: int | None,
279 parent: NodeNG | None,
280 *,
281 end_lineno: int | None,
282 end_col_offset: int | None,
283 ) -> None:
284 self.elts: list[SuccessfulInferenceResult] = []
285 """The elements in the node."""
286
287 super().__init__(
288 lineno=lineno,
289 col_offset=col_offset,
290 end_lineno=end_lineno,
291 end_col_offset=end_col_offset,
292 parent=parent,
293 )
294
295 def postinit(self, elts: list[SuccessfulInferenceResult]) -> None:
296 self.elts = elts
297
298 @classmethod
299 def from_elements(cls, elts: Iterable[Any]) -> Self:
300 """Create a node of this type from the given list of elements.
301
302 :param elts: The list of elements that the node should contain.
303
304 :returns: A new node containing the given elements.
305 """
306 node = cls(
307 lineno=None,
308 col_offset=None,
309 parent=None,
310 end_lineno=None,
311 end_col_offset=None,
312 )
313 node.elts = [const_factory(e) if _is_const(e) else e for e in elts]
314 return node
315
316 def itered(self):
317 """An iterator over the elements this node contains.
318
319 :returns: The contents of this node.
320 :rtype: iterable(NodeNG)
321 """
322 return self.elts
323
324 def bool_value(self, context: InferenceContext | None = None) -> bool:
325 """Determine the boolean value of this node.
326
327 :returns: The boolean value of this node.
328 """
329 return bool(self.elts)
330
331 @abc.abstractmethod
332 def pytype(self) -> str:
333 """Get the name of the type that this node represents.
334
335 :returns: The name of the type.
336 """
337
338 def get_children(self):
339 yield from self.elts
340
341 @decorators.raise_if_nothing_inferred
342 def _infer(
343 self,
344 context: InferenceContext | None = None,
345 **kwargs: Any,
346 ) -> Iterator[Self]:
347 has_starred_named_expr = any(
348 isinstance(e, (Starred, NamedExpr)) for e in self.elts
349 )
350 if has_starred_named_expr:
351 values = self._infer_sequence_helper(context)
352 new_seq = type(self)(
353 lineno=self.lineno,
354 col_offset=self.col_offset,
355 parent=self.parent,
356 end_lineno=self.end_lineno,
357 end_col_offset=self.end_col_offset,
358 )
359 new_seq.postinit(values)
360
361 yield new_seq
362 else:
363 yield self
364
365 def _infer_sequence_helper(
366 self, context: InferenceContext | None = None
367 ) -> list[SuccessfulInferenceResult]:
368 """Infer all values based on BaseContainer.elts."""
369 values = []
370
371 for elt in self.elts:
372 if isinstance(elt, Starred):
373 starred = util.safe_infer(elt.value, context)
374 if not starred:
375 raise InferenceError(node=self, context=context)
376 if isinstance(starred, TypeVarTuple):
377 # TypeVarTuple unpacking (*Ts) represents a variadic
378 # type parameter, not an iterable to expand.
379 values.append(elt)
380 elif not hasattr(starred, "elts"):
381 raise InferenceError(node=self, context=context)
382 else:
383 # TODO: fresh context?
384 values.extend(starred._infer_sequence_helper(context))
385 elif isinstance(elt, NamedExpr):
386 value = util.safe_infer(elt.value, context)
387 if not value:
388 raise InferenceError(node=self, context=context)
389 values.append(value)
390 else:
391 values.append(elt)
392 return values
393
394
395# Name classes
396
397
398class AssignName(
399 _base_nodes.NoChildrenNode,
400 _base_nodes.LookupMixIn,
401 _base_nodes.ParentAssignNode,
402):
403 """Variation of :class:`ast.Assign` representing assignment to a name.
404
405 An :class:`AssignName` is the name of something that is assigned to.
406 This includes variables defined in a function signature or in a loop.
407
408 >>> import astroid
409 >>> node = astroid.extract_node('variable = range(10)')
410 >>> node
411 <Assign l.1 at 0x7effe1db8550>
412 >>> list(node.get_children())
413 [<AssignName.variable l.1 at 0x7effe1db8748>, <Call l.1 at 0x7effe1db8630>]
414 >>> list(node.get_children())[0].as_string()
415 'variable'
416 """
417
418 _other_fields = ("name",)
419
420 def __init__(
421 self,
422 name: str,
423 lineno: int,
424 col_offset: int,
425 parent: NodeNG,
426 *,
427 end_lineno: int | None,
428 end_col_offset: int | None,
429 ) -> None:
430 self.name = name
431 """The name that is assigned to."""
432
433 super().__init__(
434 lineno=lineno,
435 col_offset=col_offset,
436 end_lineno=end_lineno,
437 end_col_offset=end_col_offset,
438 parent=parent,
439 )
440
441 assigned_stmts = protocols.assend_assigned_stmts
442 """Returns the assigned statement (non inferred) according to the assignment type.
443 See astroid/protocols.py for actual implementation.
444 """
445
446 @decorators.raise_if_nothing_inferred
447 @decorators.path_wrapper
448 def _infer(
449 self, context: InferenceContext | None = None, **kwargs: Any
450 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
451 """Infer an AssignName: need to inspect the RHS part of the
452 assign node.
453 """
454 if isinstance(self.parent, AugAssign):
455 return self.parent.infer(context)
456
457 stmts = list(self.assigned_stmts(context=context))
458 return _infer_stmts(stmts, context)
459
460 @decorators.raise_if_nothing_inferred
461 def infer_lhs(
462 self, context: InferenceContext | None = None, **kwargs: Any
463 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
464 """Infer a Name: use name lookup rules.
465
466 Same implementation as Name._infer."""
467 # pylint: disable=import-outside-toplevel
468 from astroid.constraint import get_constraints
469 from astroid.helpers import _higher_function_scope
470
471 frame, stmts = self.lookup(self.name)
472 if not stmts:
473 # Try to see if the name is enclosed in a nested function
474 # and use the higher (first function) scope for searching.
475 parent_function = _higher_function_scope(self.scope())
476 if parent_function:
477 _, stmts = parent_function.lookup(self.name)
478
479 if not stmts:
480 raise NameInferenceError(
481 name=self.name, scope=self.scope(), context=context
482 )
483 context = copy_context(context)
484 context.lookupname = self.name
485 context.constraints[self.name] = get_constraints(self, frame)
486
487 return _infer_stmts(stmts, context, frame)
488
489
490class DelName(
491 _base_nodes.NoChildrenNode, _base_nodes.LookupMixIn, _base_nodes.ParentAssignNode
492):
493 """Variation of :class:`ast.Delete` representing deletion of a name.
494
495 A :class:`DelName` is the name of something that is deleted.
496
497 >>> import astroid
498 >>> node = astroid.extract_node("del variable #@")
499 >>> list(node.get_children())
500 [<DelName.variable l.1 at 0x7effe1da4d30>]
501 >>> list(node.get_children())[0].as_string()
502 'variable'
503 """
504
505 _other_fields = ("name",)
506
507 def __init__(
508 self,
509 name: str,
510 lineno: int,
511 col_offset: int,
512 parent: NodeNG,
513 *,
514 end_lineno: int | None,
515 end_col_offset: int | None,
516 ) -> None:
517 self.name = name
518 """The name that is being deleted."""
519
520 super().__init__(
521 lineno=lineno,
522 col_offset=col_offset,
523 end_lineno=end_lineno,
524 end_col_offset=end_col_offset,
525 parent=parent,
526 )
527
528
529class Name(_base_nodes.LookupMixIn, _base_nodes.NoChildrenNode):
530 """Class representing an :class:`ast.Name` node.
531
532 A :class:`Name` node is something that is named, but not covered by
533 :class:`AssignName` or :class:`DelName`.
534
535 >>> import astroid
536 >>> node = astroid.extract_node('range(10)')
537 >>> node
538 <Call l.1 at 0x7effe1db8710>
539 >>> list(node.get_children())
540 [<Name.range l.1 at 0x7effe1db86a0>, <Const.int l.1 at 0x7effe1db8518>]
541 >>> list(node.get_children())[0].as_string()
542 'range'
543 """
544
545 _other_fields = ("name",)
546
547 def __init__(
548 self,
549 name: str,
550 lineno: int,
551 col_offset: int,
552 parent: NodeNG,
553 *,
554 end_lineno: int | None,
555 end_col_offset: int | None,
556 ) -> None:
557 self.name = name
558 """The name that this node refers to."""
559
560 super().__init__(
561 lineno=lineno,
562 col_offset=col_offset,
563 end_lineno=end_lineno,
564 end_col_offset=end_col_offset,
565 parent=parent,
566 )
567
568 def _get_name_nodes(self):
569 yield self
570
571 for child_node in self.get_children():
572 yield from child_node._get_name_nodes()
573
574 @decorators.raise_if_nothing_inferred
575 @decorators.path_wrapper
576 def _infer(
577 self, context: InferenceContext | None = None, **kwargs: Any
578 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
579 """Infer a Name: use name lookup rules
580
581 Same implementation as AssignName._infer_lhs."""
582 # pylint: disable=import-outside-toplevel
583 from astroid.constraint import get_constraints
584 from astroid.helpers import _higher_function_scope
585
586 frame, stmts = self.lookup(self.name)
587 if not stmts:
588 # Try to see if the name is enclosed in a nested function
589 # and use the higher (first function) scope for searching.
590 parent_function = _higher_function_scope(self.scope())
591 if parent_function:
592 _, stmts = parent_function.lookup(self.name)
593
594 if not stmts:
595 raise NameInferenceError(
596 name=self.name, scope=self.scope(), context=context
597 )
598 context = copy_context(context)
599 context.lookupname = self.name
600 context.constraints[self.name] = get_constraints(self, frame)
601
602 return _infer_stmts(stmts, context, frame)
603
604
605class Arguments(
606 _base_nodes.AssignTypeNode
607): # pylint: disable=too-many-instance-attributes
608 """Class representing an :class:`ast.arguments` node.
609
610 An :class:`Arguments` node represents that arguments in a
611 function definition.
612
613 >>> import astroid
614 >>> node = astroid.extract_node('def foo(bar): pass')
615 >>> node
616 <FunctionDef.foo l.1 at 0x7effe1db8198>
617 >>> node.args
618 <Arguments l.1 at 0x7effe1db82e8>
619 """
620
621 # Python 3.4+ uses a different approach regarding annotations,
622 # each argument is a new class, _ast.arg, which exposes an
623 # 'annotation' attribute. In astroid though, arguments are exposed
624 # as is in the Arguments node and the only way to expose annotations
625 # is by using something similar with Python 3.3:
626 # - we expose 'varargannotation' and 'kwargannotation' of annotations
627 # of varargs and kwargs.
628 # - we expose 'annotation', a list with annotations for
629 # for each normal argument. If an argument doesn't have an
630 # annotation, its value will be None.
631 _astroid_fields = (
632 "args",
633 "defaults",
634 "kwonlyargs",
635 "posonlyargs",
636 "posonlyargs_annotations",
637 "kw_defaults",
638 "annotations",
639 "varargannotation",
640 "kwargannotation",
641 "kwonlyargs_annotations",
642 "type_comment_args",
643 "type_comment_kwonlyargs",
644 "type_comment_posonlyargs",
645 )
646
647 _other_fields = ("vararg", "kwarg")
648
649 args: list[AssignName] | None
650 """The names of the required arguments.
651
652 Can be None if the associated function does not have a retrievable
653 signature and the arguments are therefore unknown.
654 This can happen with (builtin) functions implemented in C that have
655 incomplete signature information.
656 """
657
658 defaults: list[NodeNG] | None
659 """The default values for arguments that can be passed positionally."""
660
661 kwonlyargs: list[AssignName]
662 """The keyword arguments that cannot be passed positionally."""
663
664 posonlyargs: list[AssignName]
665 """The arguments that can only be passed positionally."""
666
667 kw_defaults: list[NodeNG | None] | None
668 """The default values for keyword arguments that cannot be passed positionally."""
669
670 annotations: list[NodeNG | None]
671 """The type annotations of arguments that can be passed positionally."""
672
673 posonlyargs_annotations: list[NodeNG | None]
674 """The type annotations of arguments that can only be passed positionally."""
675
676 kwonlyargs_annotations: list[NodeNG | None]
677 """The type annotations of arguments that cannot be passed positionally."""
678
679 type_comment_args: list[NodeNG | None]
680 """The type annotation, passed by a type comment, of each argument.
681
682 If an argument does not have a type comment,
683 the value for that argument will be None.
684 """
685
686 type_comment_kwonlyargs: list[NodeNG | None]
687 """The type annotation, passed by a type comment, of each keyword only argument.
688
689 If an argument does not have a type comment,
690 the value for that argument will be None.
691 """
692
693 type_comment_posonlyargs: list[NodeNG | None]
694 """The type annotation, passed by a type comment, of each positional argument.
695
696 If an argument does not have a type comment,
697 the value for that argument will be None.
698 """
699
700 varargannotation: NodeNG | None
701 """The type annotation for the variable length arguments."""
702
703 kwargannotation: NodeNG | None
704 """The type annotation for the variable length keyword arguments."""
705
706 vararg_node: AssignName | None
707 """The node for variable length arguments"""
708
709 kwarg_node: AssignName | None
710 """The node for variable keyword arguments"""
711
712 def __init__(
713 self,
714 vararg: str | None,
715 kwarg: str | None,
716 parent: NodeNG,
717 vararg_node: AssignName | None = None,
718 kwarg_node: AssignName | None = None,
719 ) -> None:
720 """Almost all attributes can be None for living objects where introspection failed."""
721 super().__init__(
722 parent=parent,
723 lineno=None,
724 col_offset=None,
725 end_lineno=None,
726 end_col_offset=None,
727 )
728
729 self.vararg = vararg
730 """The name of the variable length arguments."""
731
732 self.kwarg = kwarg
733 """The name of the variable length keyword arguments."""
734
735 self.vararg_node = vararg_node
736 self.kwarg_node = kwarg_node
737
738 # pylint: disable=too-many-arguments, too-many-positional-arguments
739 def postinit(
740 self,
741 args: list[AssignName] | None,
742 defaults: list[NodeNG] | None,
743 kwonlyargs: list[AssignName],
744 kw_defaults: list[NodeNG | None] | None,
745 annotations: list[NodeNG | None],
746 posonlyargs: list[AssignName],
747 kwonlyargs_annotations: list[NodeNG | None],
748 posonlyargs_annotations: list[NodeNG | None],
749 varargannotation: NodeNG | None = None,
750 kwargannotation: NodeNG | None = None,
751 type_comment_args: list[NodeNG | None] | None = None,
752 type_comment_kwonlyargs: list[NodeNG | None] | None = None,
753 type_comment_posonlyargs: list[NodeNG | None] | None = None,
754 ) -> None:
755 self.args = args
756 self.defaults = defaults
757 self.kwonlyargs = kwonlyargs
758 self.posonlyargs = posonlyargs
759 self.kw_defaults = kw_defaults
760 self.annotations = annotations
761 self.kwonlyargs_annotations = kwonlyargs_annotations
762 self.posonlyargs_annotations = posonlyargs_annotations
763
764 # Parameters that got added later and need a default
765 self.varargannotation = varargannotation
766 self.kwargannotation = kwargannotation
767 if type_comment_args is None:
768 type_comment_args = []
769 self.type_comment_args = type_comment_args
770 if type_comment_kwonlyargs is None:
771 type_comment_kwonlyargs = []
772 self.type_comment_kwonlyargs = type_comment_kwonlyargs
773 if type_comment_posonlyargs is None:
774 type_comment_posonlyargs = []
775 self.type_comment_posonlyargs = type_comment_posonlyargs
776
777 assigned_stmts = protocols.arguments_assigned_stmts
778 """Returns the assigned statement (non inferred) according to the assignment type.
779 See astroid/protocols.py for actual implementation.
780 """
781
782 def _infer_name(self, frame, name):
783 if self.parent is frame:
784 return name
785 return None
786
787 @cached_property
788 def fromlineno(self) -> int:
789 """The first line that this node appears on in the source code.
790
791 Can also return 0 if the line can not be determined.
792 """
793 lineno = super().fromlineno
794 return max(lineno, self.parent.fromlineno or 0)
795
796 @cached_property
797 def arguments(self):
798 """Get all the arguments for this node. This includes:
799 * Positional only arguments
800 * Positional arguments
801 * Keyword arguments
802 * Variable arguments (.e.g *args)
803 * Variable keyword arguments (e.g **kwargs)
804 """
805 retval = list(itertools.chain((self.posonlyargs or ()), (self.args or ())))
806 if self.vararg_node:
807 retval.append(self.vararg_node)
808 retval += self.kwonlyargs or ()
809 if self.kwarg_node:
810 retval.append(self.kwarg_node)
811
812 return retval
813
814 def format_args(self, *, skippable_names: set[str] | None = None) -> str:
815 """Get the arguments formatted as string.
816
817 :returns: The formatted arguments.
818 :rtype: str
819 """
820 result = []
821 positional_only_defaults = []
822 positional_or_keyword_defaults = self.defaults
823 if self.defaults:
824 args = self.args or []
825 positional_or_keyword_defaults = self.defaults[-len(args) :]
826 positional_only_defaults = self.defaults[: len(self.defaults) - len(args)]
827
828 if self.posonlyargs:
829 result.append(
830 _format_args(
831 self.posonlyargs,
832 positional_only_defaults,
833 self.posonlyargs_annotations,
834 skippable_names=skippable_names,
835 )
836 )
837 result.append("/")
838 if self.args:
839 result.append(
840 _format_args(
841 self.args,
842 positional_or_keyword_defaults,
843 getattr(self, "annotations", None),
844 skippable_names=skippable_names,
845 )
846 )
847 if self.vararg:
848 result.append(f"*{self.vararg}")
849 if self.kwonlyargs:
850 if not self.vararg:
851 result.append("*")
852 result.append(
853 _format_args(
854 self.kwonlyargs,
855 self.kw_defaults,
856 self.kwonlyargs_annotations,
857 skippable_names=skippable_names,
858 )
859 )
860 if self.kwarg:
861 result.append(f"**{self.kwarg}")
862 return ", ".join(result)
863
864 def _get_arguments_data(
865 self,
866 ) -> tuple[
867 dict[str, tuple[str | None, str | None]],
868 dict[str, tuple[str | None, str | None]],
869 ]:
870 """Get the arguments as dictionary with information about typing and defaults.
871
872 The return tuple contains a dictionary for positional and keyword arguments with their typing
873 and their default value, if any.
874 The method follows a similar order as format_args but instead of formatting into a string it
875 returns the data that is used to do so.
876 """
877 pos_only: dict[str, tuple[str | None, str | None]] = {}
878 kw_only: dict[str, tuple[str | None, str | None]] = {}
879
880 # Setup and match defaults with arguments
881 positional_only_defaults = []
882 positional_or_keyword_defaults = self.defaults
883 if self.defaults:
884 args = self.args or []
885 positional_or_keyword_defaults = self.defaults[-len(args) :]
886 positional_only_defaults = self.defaults[: len(self.defaults) - len(args)]
887
888 for index, posonly in enumerate(self.posonlyargs):
889 annotation, default = self.posonlyargs_annotations[index], None
890 if annotation is not None:
891 annotation = annotation.as_string()
892 if positional_only_defaults:
893 default = positional_only_defaults[index].as_string()
894 pos_only[posonly.name] = (annotation, default)
895
896 for index, arg in enumerate(self.args):
897 annotation, default = self.annotations[index], None
898 if annotation is not None:
899 annotation = annotation.as_string()
900 if positional_or_keyword_defaults:
901 defaults_offset = len(self.args) - len(positional_or_keyword_defaults)
902 default_index = index - defaults_offset
903 if (
904 default_index > -1
905 and positional_or_keyword_defaults[default_index] is not None
906 ):
907 default = positional_or_keyword_defaults[default_index].as_string()
908 pos_only[arg.name] = (annotation, default)
909
910 if self.vararg:
911 annotation = self.varargannotation
912 if annotation is not None:
913 annotation = annotation.as_string()
914 pos_only[self.vararg] = (annotation, None)
915
916 for index, kwarg in enumerate(self.kwonlyargs):
917 annotation = self.kwonlyargs_annotations[index]
918 if annotation is not None:
919 annotation = annotation.as_string()
920 default = self.kw_defaults[index]
921 if default is not None:
922 default = default.as_string()
923 kw_only[kwarg.name] = (annotation, default)
924
925 if self.kwarg:
926 annotation = self.kwargannotation
927 if annotation is not None:
928 annotation = annotation.as_string()
929 kw_only[self.kwarg] = (annotation, None)
930
931 return pos_only, kw_only
932
933 def default_value(self, argname):
934 """Get the default value for an argument.
935
936 :param argname: The name of the argument to get the default value for.
937 :type argname: str
938
939 :raises NoDefault: If there is no default value defined for the
940 given argument.
941 """
942 args = [
943 arg for arg in self.arguments if arg.name not in [self.vararg, self.kwarg]
944 ]
945
946 index = _find_arg(argname, self.kwonlyargs)[0]
947 if (index is not None) and (len(self.kw_defaults) > index):
948 if self.kw_defaults[index] is not None:
949 return self.kw_defaults[index]
950 raise NoDefault(func=self.parent, name=argname)
951
952 index = _find_arg(argname, args)[0]
953 if index is not None:
954 idx = index - (len(args) - len(self.defaults) - len(self.kw_defaults))
955 if idx >= 0:
956 return self.defaults[idx]
957
958 raise NoDefault(func=self.parent, name=argname)
959
960 def is_argument(self, name) -> bool:
961 """Check if the given name is defined in the arguments.
962
963 :param name: The name to check for.
964 :type name: str
965
966 :returns: Whether the given name is defined in the arguments,
967 """
968 if name == self.vararg:
969 return True
970 if name == self.kwarg:
971 return True
972 return self.find_argname(name)[1] is not None
973
974 def find_argname(self, argname):
975 """Get the index and :class:`AssignName` node for given name.
976
977 :param argname: The name of the argument to search for.
978 :type argname: str
979
980 :returns: The index and node for the argument.
981 :rtype: tuple(str or None, AssignName or None)
982 """
983 if self.arguments:
984 index, argument = _find_arg(argname, self.arguments)
985 if argument:
986 return index, argument
987 return None, None
988
989 def get_children(self):
990 yield from self.posonlyargs or ()
991
992 for elt in self.posonlyargs_annotations:
993 if elt is not None:
994 yield elt
995
996 yield from self.args or ()
997
998 if self.defaults is not None:
999 yield from self.defaults
1000 yield from self.kwonlyargs
1001
1002 for elt in self.kw_defaults or ():
1003 if elt is not None:
1004 yield elt
1005
1006 for elt in self.annotations:
1007 if elt is not None:
1008 yield elt
1009
1010 if self.varargannotation is not None:
1011 yield self.varargannotation
1012
1013 if self.kwargannotation is not None:
1014 yield self.kwargannotation
1015
1016 for elt in self.kwonlyargs_annotations:
1017 if elt is not None:
1018 yield elt
1019
1020 def get_annotations(self) -> Iterator[nodes.NodeNG]:
1021 """Iterate over all annotations nodes."""
1022 for elt in self.posonlyargs_annotations:
1023 if elt is not None:
1024 yield elt
1025 for elt in self.annotations:
1026 if elt is not None:
1027 yield elt
1028 if self.varargannotation is not None:
1029 yield self.varargannotation
1030
1031 for elt in self.kwonlyargs_annotations:
1032 if elt is not None:
1033 yield elt
1034 if self.kwargannotation is not None:
1035 yield self.kwargannotation
1036
1037 @decorators.raise_if_nothing_inferred
1038 def _infer(
1039 self, context: InferenceContext | None = None, **kwargs: Any
1040 ) -> Generator[InferenceResult]:
1041 # pylint: disable-next=import-outside-toplevel
1042 from astroid.protocols import _arguments_infer_argname
1043
1044 if context is None or context.lookupname is None:
1045 raise InferenceError(node=self, context=context)
1046 return _arguments_infer_argname(self, context.lookupname, context)
1047
1048
1049def _find_arg(argname, args):
1050 for i, arg in enumerate(args):
1051 if arg.name == argname:
1052 return i, arg
1053 return None, None
1054
1055
1056def _format_args(
1057 args, defaults=None, annotations=None, skippable_names: set[str] | None = None
1058) -> str:
1059 if skippable_names is None:
1060 skippable_names = set()
1061 values = []
1062 if args is None:
1063 return ""
1064 if annotations is None:
1065 annotations = []
1066 if defaults is not None:
1067 default_offset = len(args) - len(defaults)
1068 else:
1069 default_offset = None
1070 packed = itertools.zip_longest(args, annotations)
1071 for i, (arg, annotation) in enumerate(packed):
1072 if arg.name in skippable_names:
1073 continue
1074 if isinstance(arg, Tuple):
1075 values.append(f"({_format_args(arg.elts)})")
1076 else:
1077 argname = arg.name
1078 default_sep = "="
1079 if annotation is not None:
1080 argname += ": " + annotation.as_string()
1081 default_sep = " = "
1082 values.append(argname)
1083
1084 if default_offset is not None and i >= default_offset:
1085 if defaults[i - default_offset] is not None:
1086 values[-1] += default_sep + defaults[i - default_offset].as_string()
1087 return ", ".join(values)
1088
1089
1090def _infer_attribute(
1091 node: nodes.AssignAttr | nodes.Attribute,
1092 context: InferenceContext | None = None,
1093 **kwargs: Any,
1094) -> Generator[InferenceResult, None, InferenceErrorInfo]:
1095 """Infer an AssignAttr/Attribute node by using getattr on the associated object."""
1096 # pylint: disable=import-outside-toplevel
1097 from astroid.constraint import get_constraints
1098 from astroid.nodes import ClassDef
1099
1100 for owner in node.expr.infer(context):
1101 if isinstance(owner, util.UninferableBase):
1102 yield owner
1103 continue
1104
1105 context = copy_context(context)
1106 old_boundnode = context.boundnode
1107 try:
1108 context.boundnode = owner
1109 if isinstance(owner, (ClassDef, Instance)):
1110 frame = owner if isinstance(owner, ClassDef) else owner._proxied
1111 context.constraints[node.attrname] = get_constraints(node, frame=frame)
1112 if node.attrname == "argv" and owner.name == "sys":
1113 # sys.argv will never be inferable during static analysis
1114 # It's value would be the args passed to the linter itself
1115 yield util.Uninferable
1116 else:
1117 yield from owner.igetattr(node.attrname, context)
1118 except (
1119 AttributeInferenceError,
1120 InferenceError,
1121 AttributeError,
1122 ):
1123 pass
1124 finally:
1125 context.boundnode = old_boundnode
1126 return InferenceErrorInfo(node=node, context=context)
1127
1128
1129class AssignAttr(_base_nodes.LookupMixIn, _base_nodes.ParentAssignNode):
1130 """Variation of :class:`ast.Assign` representing assignment to an attribute.
1131
1132 >>> import astroid
1133 >>> node = astroid.extract_node('self.attribute = range(10)')
1134 >>> node
1135 <Assign l.1 at 0x7effe1d521d0>
1136 >>> list(node.get_children())
1137 [<AssignAttr.attribute l.1 at 0x7effe1d52320>, <Call l.1 at 0x7effe1d522e8>]
1138 >>> list(node.get_children())[0].as_string()
1139 'self.attribute'
1140 """
1141
1142 expr: NodeNG
1143
1144 _astroid_fields = ("expr",)
1145 _other_fields = ("attrname",)
1146
1147 def __init__(
1148 self,
1149 attrname: str,
1150 lineno: int,
1151 col_offset: int,
1152 parent: NodeNG,
1153 *,
1154 end_lineno: int | None,
1155 end_col_offset: int | None,
1156 ) -> None:
1157 self.attrname = attrname
1158 """The name of the attribute being assigned to."""
1159
1160 super().__init__(
1161 lineno=lineno,
1162 col_offset=col_offset,
1163 end_lineno=end_lineno,
1164 end_col_offset=end_col_offset,
1165 parent=parent,
1166 )
1167
1168 def postinit(self, expr: NodeNG) -> None:
1169 self.expr = expr
1170
1171 assigned_stmts = protocols.assend_assigned_stmts
1172 """Returns the assigned statement (non inferred) according to the assignment type.
1173 See astroid/protocols.py for actual implementation.
1174 """
1175
1176 def get_children(self):
1177 yield self.expr
1178
1179 @decorators.raise_if_nothing_inferred
1180 @decorators.path_wrapper
1181 def _infer(
1182 self, context: InferenceContext | None = None, **kwargs: Any
1183 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
1184 """Infer an AssignAttr: need to inspect the RHS part of the
1185 assign node.
1186 """
1187 if isinstance(self.parent, AugAssign):
1188 return self.parent.infer(context)
1189
1190 stmts = list(self.assigned_stmts(context=context))
1191 return _infer_stmts(stmts, context)
1192
1193 @decorators.raise_if_nothing_inferred
1194 @decorators.path_wrapper
1195 def infer_lhs(
1196 self, context: InferenceContext | None = None, **kwargs: Any
1197 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
1198 return _infer_attribute(self, context, **kwargs)
1199
1200
1201class Assert(_base_nodes.Statement):
1202 """Class representing an :class:`ast.Assert` node.
1203
1204 An :class:`Assert` node represents an assert statement.
1205
1206 >>> import astroid
1207 >>> node = astroid.extract_node('assert len(things) == 10, "Not enough things"')
1208 >>> node
1209 <Assert l.1 at 0x7effe1d527b8>
1210 """
1211
1212 _astroid_fields = ("test", "fail")
1213
1214 test: NodeNG
1215 """The test that passes or fails the assertion."""
1216
1217 fail: NodeNG | None
1218 """The message shown when the assertion fails."""
1219
1220 def postinit(self, test: NodeNG, fail: NodeNG | None) -> None:
1221 self.fail = fail
1222 self.test = test
1223
1224 def get_children(self):
1225 yield self.test
1226
1227 if self.fail is not None:
1228 yield self.fail
1229
1230
1231class Assign(_base_nodes.AssignTypeNode, _base_nodes.Statement):
1232 """Class representing an :class:`ast.Assign` node.
1233
1234 An :class:`Assign` is a statement where something is explicitly
1235 asssigned to.
1236
1237 >>> import astroid
1238 >>> node = astroid.extract_node('variable = range(10)')
1239 >>> node
1240 <Assign l.1 at 0x7effe1db8550>
1241 """
1242
1243 targets: list[NodeNG]
1244 """What is being assigned to."""
1245
1246 value: NodeNG
1247 """The value being assigned to the variables."""
1248
1249 type_annotation: NodeNG | None
1250 """If present, this will contain the type annotation passed by a type comment"""
1251
1252 _astroid_fields = ("targets", "value")
1253 _other_other_fields = ("type_annotation",)
1254
1255 def postinit(
1256 self,
1257 targets: list[NodeNG],
1258 value: NodeNG,
1259 type_annotation: NodeNG | None,
1260 ) -> None:
1261 self.targets = targets
1262 self.value = value
1263 self.type_annotation = type_annotation
1264
1265 assigned_stmts = protocols.assign_assigned_stmts
1266 """Returns the assigned statement (non inferred) according to the assignment type.
1267 See astroid/protocols.py for actual implementation.
1268 """
1269
1270 def get_children(self):
1271 yield from self.targets
1272
1273 yield self.value
1274
1275 @cached_property
1276 def _assign_nodes_in_scope(self) -> list[nodes.Assign]:
1277 return [self, *self.value._assign_nodes_in_scope]
1278
1279 def _get_yield_nodes_skip_functions(self):
1280 yield from self.value._get_yield_nodes_skip_functions()
1281
1282 def _get_yield_nodes_skip_lambdas(self):
1283 yield from self.value._get_yield_nodes_skip_lambdas()
1284
1285
1286class AnnAssign(_base_nodes.AssignTypeNode, _base_nodes.Statement):
1287 """Class representing an :class:`ast.AnnAssign` node.
1288
1289 An :class:`AnnAssign` is an assignment with a type annotation.
1290
1291 >>> import astroid
1292 >>> node = astroid.extract_node('variable: List[int] = range(10)')
1293 >>> node
1294 <AnnAssign l.1 at 0x7effe1d4c630>
1295 """
1296
1297 _astroid_fields = ("target", "annotation", "value")
1298 _other_fields = ("simple",)
1299
1300 target: Name | Attribute | Subscript
1301 """What is being assigned to."""
1302
1303 annotation: NodeNG
1304 """The type annotation of what is being assigned to."""
1305
1306 value: NodeNG | None
1307 """The value being assigned to the variables."""
1308
1309 simple: int
1310 """Whether :attr:`target` is a pure name or a complex statement."""
1311
1312 def postinit(
1313 self,
1314 target: Name | Attribute | Subscript,
1315 annotation: NodeNG,
1316 simple: int,
1317 value: NodeNG | None,
1318 ) -> None:
1319 self.target = target
1320 self.annotation = annotation
1321 self.value = value
1322 self.simple = simple
1323
1324 assigned_stmts = protocols.assign_annassigned_stmts
1325 """Returns the assigned statement (non inferred) according to the assignment type.
1326 See astroid/protocols.py for actual implementation.
1327 """
1328
1329 def get_children(self):
1330 yield self.target
1331 yield self.annotation
1332
1333 if self.value is not None:
1334 yield self.value
1335
1336
1337class AugAssign(
1338 _base_nodes.AssignTypeNode, _base_nodes.OperatorNode, _base_nodes.Statement
1339):
1340 """Class representing an :class:`ast.AugAssign` node.
1341
1342 An :class:`AugAssign` is an assignment paired with an operator.
1343
1344 >>> import astroid
1345 >>> node = astroid.extract_node('variable += 1')
1346 >>> node
1347 <AugAssign l.1 at 0x7effe1db4d68>
1348 """
1349
1350 _astroid_fields = ("target", "value")
1351 _other_fields = ("op",)
1352
1353 target: Name | Attribute | Subscript
1354 """What is being assigned to."""
1355
1356 value: NodeNG
1357 """The value being assigned to the variable."""
1358
1359 def __init__(
1360 self,
1361 op: str,
1362 lineno: int,
1363 col_offset: int,
1364 parent: NodeNG,
1365 *,
1366 end_lineno: int | None,
1367 end_col_offset: int | None,
1368 ) -> None:
1369 self.op = op
1370 """The operator that is being combined with the assignment.
1371
1372 This includes the equals sign.
1373 """
1374
1375 super().__init__(
1376 lineno=lineno,
1377 col_offset=col_offset,
1378 end_lineno=end_lineno,
1379 end_col_offset=end_col_offset,
1380 parent=parent,
1381 )
1382
1383 def postinit(self, target: Name | Attribute | Subscript, value: NodeNG) -> None:
1384 self.target = target
1385 self.value = value
1386
1387 assigned_stmts = protocols.assign_assigned_stmts
1388 """Returns the assigned statement (non inferred) according to the assignment type.
1389 See astroid/protocols.py for actual implementation.
1390 """
1391
1392 def type_errors(
1393 self, context: InferenceContext | None = None
1394 ) -> list[util.BadBinaryOperationMessage]:
1395 """Get a list of type errors which can occur during inference.
1396
1397 Each TypeError is represented by a :class:`BadBinaryOperationMessage` ,
1398 which holds the original exception.
1399
1400 If any inferred result is uninferable, an empty list is returned.
1401 """
1402 bad = []
1403 try:
1404 for result in self._infer_augassign(context=context):
1405 if result is util.Uninferable:
1406 raise InferenceError
1407 if isinstance(result, util.BadBinaryOperationMessage):
1408 bad.append(result)
1409 except InferenceError:
1410 return []
1411 return bad
1412
1413 def get_children(self):
1414 yield self.target
1415 yield self.value
1416
1417 def _get_yield_nodes_skip_functions(self):
1418 """An AugAssign node can contain a Yield node in the value"""
1419 yield from self.value._get_yield_nodes_skip_functions()
1420 yield from super()._get_yield_nodes_skip_functions()
1421
1422 def _get_yield_nodes_skip_lambdas(self):
1423 """An AugAssign node can contain a Yield node in the value"""
1424 yield from self.value._get_yield_nodes_skip_lambdas()
1425 yield from super()._get_yield_nodes_skip_lambdas()
1426
1427 def _infer_augassign(
1428 self, context: InferenceContext | None = None
1429 ) -> Generator[InferenceResult | util.BadBinaryOperationMessage]:
1430 """Inference logic for augmented binary operations."""
1431 context = context or InferenceContext()
1432
1433 rhs_context = context.clone()
1434
1435 lhs_iter = self.target.infer_lhs(context=context)
1436 rhs_iter = self.value.infer(context=rhs_context)
1437
1438 for lhs, rhs in itertools.product(lhs_iter, rhs_iter):
1439 if any(isinstance(value, util.UninferableBase) for value in (rhs, lhs)):
1440 # Don't know how to process this.
1441 yield util.Uninferable
1442 return
1443
1444 try:
1445 yield from self._infer_binary_operation(
1446 left=lhs,
1447 right=rhs,
1448 binary_opnode=self,
1449 context=context,
1450 flow_factory=self._get_aug_flow,
1451 )
1452 except _NonDeducibleTypeHierarchy:
1453 yield util.Uninferable
1454
1455 @decorators.raise_if_nothing_inferred
1456 @decorators.path_wrapper
1457 def _infer(
1458 self, context: InferenceContext | None = None, **kwargs: Any
1459 ) -> Generator[InferenceResult]:
1460 return self._filter_operation_errors(
1461 self._infer_augassign, context, util.BadBinaryOperationMessage
1462 )
1463
1464
1465class BinOp(_base_nodes.OperatorNode):
1466 """Class representing an :class:`ast.BinOp` node.
1467
1468 A :class:`BinOp` node is an application of a binary operator.
1469
1470 >>> import astroid
1471 >>> node = astroid.extract_node('a + b')
1472 >>> node
1473 <BinOp l.1 at 0x7f23b2e8cfd0>
1474 """
1475
1476 _astroid_fields = ("left", "right")
1477 _other_fields = ("op",)
1478
1479 left: NodeNG
1480 """What is being applied to the operator on the left side."""
1481
1482 right: NodeNG
1483 """What is being applied to the operator on the right side."""
1484
1485 def __init__(
1486 self,
1487 op: str,
1488 lineno: int,
1489 col_offset: int,
1490 parent: NodeNG,
1491 *,
1492 end_lineno: int | None,
1493 end_col_offset: int | None,
1494 ) -> None:
1495 self.op = op
1496 """The operator."""
1497
1498 super().__init__(
1499 lineno=lineno,
1500 col_offset=col_offset,
1501 end_lineno=end_lineno,
1502 end_col_offset=end_col_offset,
1503 parent=parent,
1504 )
1505
1506 def postinit(self, left: NodeNG, right: NodeNG) -> None:
1507 self.left = left
1508 self.right = right
1509
1510 def type_errors(
1511 self, context: InferenceContext | None = None
1512 ) -> list[util.BadBinaryOperationMessage]:
1513 """Get a list of type errors which can occur during inference.
1514
1515 Each TypeError is represented by a :class:`BadBinaryOperationMessage`,
1516 which holds the original exception.
1517
1518 If any inferred result is uninferable, an empty list is returned.
1519 """
1520 bad = []
1521 try:
1522 for result in self._infer_binop(context=context):
1523 if result is util.Uninferable:
1524 raise InferenceError
1525 if isinstance(result, util.BadBinaryOperationMessage):
1526 bad.append(result)
1527 except InferenceError:
1528 return []
1529 return bad
1530
1531 def get_children(self):
1532 yield self.left
1533 yield self.right
1534
1535 def op_precedence(self) -> int:
1536 return OP_PRECEDENCE[self.op]
1537
1538 def op_left_associative(self) -> bool:
1539 # 2**3**4 == 2**(3**4)
1540 return self.op != "**"
1541
1542 def _infer_binop(
1543 self, context: InferenceContext | None = None, **kwargs: Any
1544 ) -> Generator[InferenceResult]:
1545 """Binary operation inference logic."""
1546 left = self.left
1547 right = self.right
1548
1549 # we use two separate contexts for evaluating lhs and rhs because
1550 # 1. evaluating lhs may leave some undesired entries in context.path
1551 # which may not let us infer right value of rhs
1552 context = context or InferenceContext()
1553 lhs_context = copy_context(context)
1554 rhs_context = copy_context(context)
1555 lhs_iter = left.infer(context=lhs_context)
1556 rhs_iter = right.infer(context=rhs_context)
1557 for lhs, rhs in itertools.product(lhs_iter, rhs_iter):
1558 if any(isinstance(value, util.UninferableBase) for value in (rhs, lhs)):
1559 # Don't know how to process this.
1560 yield util.Uninferable
1561 return
1562
1563 try:
1564 yield from self._infer_binary_operation(
1565 lhs, rhs, self, context, self._get_binop_flow
1566 )
1567 except _NonDeducibleTypeHierarchy:
1568 yield util.Uninferable
1569
1570 @decorators.yes_if_nothing_inferred
1571 @decorators.path_wrapper
1572 def _infer(
1573 self, context: InferenceContext | None = None, **kwargs: Any
1574 ) -> Generator[InferenceResult]:
1575 return self._filter_operation_errors(
1576 self._infer_binop, context, util.BadBinaryOperationMessage
1577 )
1578
1579
1580class BoolOp(NodeNG):
1581 """Class representing an :class:`ast.BoolOp` node.
1582
1583 A :class:`BoolOp` is an application of a boolean operator.
1584
1585 >>> import astroid
1586 >>> node = astroid.extract_node('a and b')
1587 >>> node
1588 <BinOp l.1 at 0x7f23b2e71c50>
1589 """
1590
1591 _astroid_fields = ("values",)
1592 _other_fields = ("op",)
1593
1594 def __init__(
1595 self,
1596 op: str,
1597 lineno: int | None = None,
1598 col_offset: int | None = None,
1599 parent: NodeNG | None = None,
1600 *,
1601 end_lineno: int | None = None,
1602 end_col_offset: int | None = None,
1603 ) -> None:
1604 """
1605 :param op: The operator.
1606
1607 :param lineno: The line that this node appears on in the source code.
1608
1609 :param col_offset: The column that this node appears on in the
1610 source code.
1611
1612 :param parent: The parent node in the syntax tree.
1613
1614 :param end_lineno: The last line this node appears on in the source code.
1615
1616 :param end_col_offset: The end column this node appears on in the
1617 source code. Note: This is after the last symbol.
1618 """
1619 self.op: str = op
1620 """The operator."""
1621
1622 self.values: list[NodeNG] = []
1623 """The values being applied to the operator."""
1624
1625 super().__init__(
1626 lineno=lineno,
1627 col_offset=col_offset,
1628 end_lineno=end_lineno,
1629 end_col_offset=end_col_offset,
1630 parent=parent,
1631 )
1632
1633 def postinit(self, values: list[NodeNG] | None = None) -> None:
1634 """Do some setup after initialisation.
1635
1636 :param values: The values being applied to the operator.
1637 """
1638 if values is not None:
1639 self.values = values
1640
1641 def get_children(self):
1642 yield from self.values
1643
1644 def op_precedence(self) -> int:
1645 return OP_PRECEDENCE[self.op]
1646
1647 @decorators.raise_if_nothing_inferred
1648 @decorators.path_wrapper
1649 def _infer(
1650 self, context: InferenceContext | None = None, **kwargs: Any
1651 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
1652 """Infer a boolean operation (and / or / not).
1653
1654 The function will calculate the boolean operation
1655 for all pairs generated through inference for each component
1656 node.
1657 """
1658 values = self.values
1659 if self.op == "or":
1660 predicate = operator.truth
1661 else:
1662 predicate = operator.not_
1663
1664 try:
1665 inferred_values = [value.infer(context=context) for value in values]
1666 except InferenceError:
1667 yield util.Uninferable
1668 return None
1669
1670 for pair in itertools.product(*inferred_values):
1671 if any(isinstance(item, util.UninferableBase) for item in pair):
1672 # Can't infer the final result, just yield Uninferable.
1673 yield util.Uninferable
1674 continue
1675
1676 bool_values = [item.bool_value() for item in pair]
1677 if any(isinstance(item, util.UninferableBase) for item in bool_values):
1678 # Can't infer the final result, just yield Uninferable.
1679 yield util.Uninferable
1680 continue
1681
1682 # Since the boolean operations are short circuited operations,
1683 # this code yields the first value for which the predicate is True
1684 # and if no value respected the predicate, then the last value will
1685 # be returned (or Uninferable if there was no last value).
1686 # This is conforming to the semantics of `and` and `or`:
1687 # 1 and 0 -> 1
1688 # 0 and 1 -> 0
1689 # 1 or 0 -> 1
1690 # 0 or 1 -> 1
1691 value = util.Uninferable
1692 for value, bool_value in zip(pair, bool_values):
1693 if predicate(bool_value):
1694 yield value
1695 break
1696 else:
1697 yield value
1698
1699 return InferenceErrorInfo(node=self, context=context)
1700
1701
1702class Break(_base_nodes.NoChildrenNode, _base_nodes.Statement):
1703 """Class representing an :class:`ast.Break` node.
1704
1705 >>> import astroid
1706 >>> node = astroid.extract_node('break')
1707 >>> node
1708 <Break l.1 at 0x7f23b2e9e5c0>
1709 """
1710
1711
1712class Call(NodeNG):
1713 """Class representing an :class:`ast.Call` node.
1714
1715 A :class:`Call` node is a call to a function, method, etc.
1716
1717 >>> import astroid
1718 >>> node = astroid.extract_node('function()')
1719 >>> node
1720 <Call l.1 at 0x7f23b2e71eb8>
1721 """
1722
1723 _astroid_fields = ("func", "args", "keywords")
1724
1725 func: NodeNG
1726 """What is being called."""
1727
1728 args: list[NodeNG]
1729 """The positional arguments being given to the call."""
1730
1731 keywords: list[Keyword]
1732 """The keyword arguments being given to the call."""
1733
1734 def postinit(
1735 self, func: NodeNG, args: list[NodeNG], keywords: list[Keyword]
1736 ) -> None:
1737 self.func = func
1738 self.args = args
1739 self.keywords = keywords
1740
1741 @property
1742 def starargs(self) -> list[Starred]:
1743 """The positional arguments that unpack something."""
1744 return [arg for arg in self.args if isinstance(arg, Starred)]
1745
1746 @property
1747 def kwargs(self) -> list[Keyword]:
1748 """The keyword arguments that unpack something."""
1749 return [keyword for keyword in self.keywords if keyword.arg is None]
1750
1751 def get_children(self):
1752 yield self.func
1753
1754 yield from self.args
1755
1756 yield from self.keywords
1757
1758 @decorators.raise_if_nothing_inferred
1759 @decorators.path_wrapper
1760 def _infer(
1761 self, context: InferenceContext | None = None, **kwargs: Any
1762 ) -> Generator[InferenceResult, None, InferenceErrorInfo]:
1763 """Infer a Call node by trying to guess what the function returns."""
1764 callcontext = copy_context(context)
1765 callcontext.boundnode = None
1766 if context is not None:
1767 callcontext.extra_context = self._populate_context_lookup(context.clone())
1768
1769 for callee in self.func.infer(context):
1770 if isinstance(callee, util.UninferableBase):
1771 yield callee
1772 continue
1773 try:
1774 if hasattr(callee, "infer_call_result"):
1775 callcontext.callcontext = CallContext(
1776 args=self.args, keywords=self.keywords, callee=callee
1777 )
1778 yield from callee.infer_call_result(
1779 caller=self, context=callcontext
1780 )
1781 except InferenceError:
1782 continue
1783 return InferenceErrorInfo(node=self, context=context)
1784
1785 def _populate_context_lookup(self, context: InferenceContext | None):
1786 """Allows context to be saved for later for inference inside a function."""
1787 context_lookup: dict[InferenceResult, InferenceContext] = {}
1788 if context is None:
1789 return context_lookup
1790 for arg in self.args:
1791 if isinstance(arg, Starred):
1792 context_lookup[arg.value] = context
1793 else:
1794 context_lookup[arg] = context
1795 keywords = self.keywords if self.keywords is not None else []
1796 for keyword in keywords:
1797 context_lookup[keyword.value] = context
1798 return context_lookup
1799
1800
1801COMPARE_OPS: dict[str, Callable[[Any, Any], bool]] = {
1802 "==": operator.eq,
1803 "!=": operator.ne,
1804 "<": operator.lt,
1805 "<=": operator.le,
1806 ">": operator.gt,
1807 ">=": operator.ge,
1808 "in": lambda a, b: a in b,
1809 "not in": lambda a, b: a not in b,
1810}
1811UNINFERABLE_OPS = {
1812 "is",
1813 "is not",
1814}
1815
1816
1817class Compare(NodeNG):
1818 """Class representing an :class:`ast.Compare` node.
1819
1820 A :class:`Compare` node indicates a comparison.
1821
1822 >>> import astroid
1823 >>> node = astroid.extract_node('a <= b <= c')
1824 >>> node
1825 <Compare l.1 at 0x7f23b2e9e6d8>
1826 >>> node.ops
1827 [('<=', <Name.b l.1 at 0x7f23b2e9e2b0>), ('<=', <Name.c l.1 at 0x7f23b2e9e390>)]
1828 """
1829
1830 _astroid_fields = ("left", "ops")
1831
1832 left: NodeNG
1833 """The value at the left being applied to a comparison operator."""
1834
1835 ops: list[tuple[str, NodeNG]]
1836 """The remainder of the operators and their relevant right hand value."""
1837
1838 def postinit(self, left: NodeNG, ops: list[tuple[str, NodeNG]]) -> None:
1839 self.left = left
1840 self.ops = ops
1841
1842 def get_children(self):
1843 """Get the child nodes below this node.
1844
1845 Overridden to handle the tuple fields and skip returning the operator
1846 strings.
1847
1848 :returns: The children.
1849 :rtype: iterable(NodeNG)
1850 """
1851 yield self.left
1852 for _, comparator in self.ops:
1853 yield comparator # we don't want the 'op'
1854
1855 def last_child(self):
1856 """An optimized version of list(get_children())[-1]
1857
1858 :returns: The last child.
1859 :rtype: NodeNG
1860 """
1861 # XXX maybe if self.ops:
1862 return self.ops[-1][1]
1863 # return self.left
1864
1865 # TODO: move to util?
1866 @staticmethod
1867 def _to_literal(node: SuccessfulInferenceResult) -> Any:
1868 # Can raise SyntaxError, ValueError, or TypeError from ast.literal_eval
1869 # Can raise AttributeError from node.as_string() as not all nodes have a visitor
1870 # Is this the stupidest idea or the simplest idea?
1871 return ast.literal_eval(node.as_string())
1872
1873 def _do_compare(
1874 self,
1875 left_iter: Iterable[InferenceResult],
1876 op: str,
1877 right_iter: Iterable[InferenceResult],
1878 ) -> bool | util.UninferableBase:
1879 """
1880 If all possible combinations are either True or False, return that:
1881 >>> _do_compare([1, 2], '<=', [3, 4])
1882 True
1883 >>> _do_compare([1, 2], '==', [3, 4])
1884 False
1885
1886 If any item is uninferable, or if some combinations are True and some
1887 are False, return Uninferable:
1888 >>> _do_compare([1, 3], '<=', [2, 4])
1889 util.Uninferable
1890 """
1891 retval: bool | None = None
1892 if op in UNINFERABLE_OPS:
1893 return util.Uninferable
1894 op_func = COMPARE_OPS[op]
1895
1896 for left, right in itertools.product(left_iter, right_iter):
1897 if isinstance(left, util.UninferableBase) or isinstance(
1898 right, util.UninferableBase
1899 ):
1900 return util.Uninferable
1901
1902 try:
1903 left, right = self._to_literal(left), self._to_literal(right)
1904 except (SyntaxError, ValueError, AttributeError, TypeError):
1905 return util.Uninferable
1906
1907 try:
1908 expr = op_func(left, right)
1909 except TypeError as exc:
1910 raise AstroidTypeError from exc
1911
1912 if retval is None:
1913 retval = expr
1914 elif retval != expr:
1915 return util.Uninferable
1916 # (or both, but "True | False" is basically the same)
1917
1918 assert retval is not None
1919 return retval # it was all the same value
1920
1921 def _infer(
1922 self, context: InferenceContext | None = None, **kwargs: Any
1923 ) -> Generator[nodes.Const | util.UninferableBase]:
1924 """Chained comparison inference logic."""
1925 retval: bool | util.UninferableBase = True
1926
1927 ops = self.ops
1928 left_node = self.left
1929 lhs = list(left_node.infer(context=context))
1930 # should we break early if first element is uninferable?
1931 for op, right_node in ops:
1932 # eagerly evaluate rhs so that values can be re-used as lhs
1933 rhs = list(right_node.infer(context=context))
1934 try:
1935 retval = self._do_compare(lhs, op, rhs)
1936 except AstroidTypeError:
1937 retval = util.Uninferable
1938 break
1939 if retval is not True:
1940 break # short-circuit
1941 lhs = rhs # continue
1942 if retval is util.Uninferable:
1943 yield retval # type: ignore[misc]
1944 else:
1945 yield Const(retval)
1946
1947
1948class Comprehension(NodeNG):
1949 """Class representing an :class:`ast.comprehension` node.
1950
1951 A :class:`Comprehension` indicates the loop inside any type of
1952 comprehension including generator expressions.
1953
1954 >>> import astroid
1955 >>> node = astroid.extract_node('[x for x in some_values]')
1956 >>> list(node.get_children())
1957 [<Name.x l.1 at 0x7f23b2e352b0>, <Comprehension l.1 at 0x7f23b2e35320>]
1958 >>> list(node.get_children())[1].as_string()
1959 'for x in some_values'
1960 """
1961
1962 _astroid_fields = ("target", "iter", "ifs")
1963 _other_fields = ("is_async",)
1964
1965 optional_assign = True
1966 """Whether this node optionally assigns a variable."""
1967
1968 target: NodeNG
1969 """What is assigned to by the comprehension."""
1970
1971 iter: NodeNG
1972 """What is iterated over by the comprehension."""
1973
1974 ifs: list[NodeNG]
1975 """The contents of any if statements that filter the comprehension."""
1976
1977 is_async: bool
1978 """Whether this is an asynchronous comprehension or not."""
1979
1980 def postinit(
1981 self,
1982 target: NodeNG,
1983 iter: NodeNG, # pylint: disable = redefined-builtin
1984 ifs: list[NodeNG],
1985 is_async: bool,
1986 ) -> None:
1987 self.target = target
1988 self.iter = iter
1989 self.ifs = ifs
1990 self.is_async = is_async
1991
1992 assigned_stmts = protocols.for_assigned_stmts
1993 """Returns the assigned statement (non inferred) according to the assignment type.
1994 See astroid/protocols.py for actual implementation.
1995 """
1996
1997 def assign_type(self):
1998 """The type of assignment that this node performs.
1999
2000 :returns: The assignment type.
2001 :rtype: NodeNG
2002 """
2003 return self
2004
2005 def _get_filtered_stmts(
2006 self, lookup_node, node, stmts, mystmt: _base_nodes.Statement | None
2007 ):
2008 """method used in filter_stmts"""
2009 if self is mystmt:
2010 if isinstance(lookup_node, (Const, Name)):
2011 return [lookup_node], True
2012
2013 elif self.statement() is mystmt:
2014 # original node's statement is the assignment, only keeps
2015 # current node (gen exp, list comp)
2016
2017 return [node], True
2018
2019 return stmts, False
2020
2021 def get_children(self):
2022 yield self.target
2023 yield self.iter
2024
2025 yield from self.ifs
2026
2027
2028class Const(_base_nodes.NoChildrenNode, Instance):
2029 """Class representing any constant including num, str, bool, None, bytes.
2030
2031 >>> import astroid
2032 >>> node = astroid.extract_node('(5, "This is a string.", True, None, b"bytes")')
2033 >>> node
2034 <Tuple.tuple l.1 at 0x7f23b2e358d0>
2035 >>> list(node.get_children())
2036 [<Const.int l.1 at 0x7f23b2e35940>,
2037 <Const.str l.1 at 0x7f23b2e35978>,
2038 <Const.bool l.1 at 0x7f23b2e359b0>,
2039 <Const.NoneType l.1 at 0x7f23b2e359e8>,
2040 <Const.bytes l.1 at 0x7f23b2e35a20>]
2041 """
2042
2043 _other_fields = ("value", "kind")
2044
2045 def __init__(
2046 self,
2047 value: Any,
2048 lineno: int | None = None,
2049 col_offset: int | None = None,
2050 parent: NodeNG = SYNTHETIC_ROOT,
2051 kind: str | None = None,
2052 *,
2053 end_lineno: int | None = None,
2054 end_col_offset: int | None = None,
2055 ) -> None:
2056 """
2057 :param value: The value that the constant represents.
2058
2059 :param lineno: The line that this node appears on in the source code.
2060
2061 :param col_offset: The column that this node appears on in the
2062 source code.
2063
2064 :param parent: The parent node in the syntax tree.
2065
2066 :param kind: The string prefix. "u" for u-prefixed strings and ``None`` otherwise. Python 3.8+ only.
2067
2068 :param end_lineno: The last line this node appears on in the source code.
2069
2070 :param end_col_offset: The end column this node appears on in the
2071 source code. Note: This is after the last symbol.
2072 """
2073 if getattr(value, "__name__", None) == "__doc__":
2074 warnings.warn( # pragma: no cover
2075 "You have most likely called a __doc__ field of some object "
2076 "and it didn't return a string. "
2077 "That happens to some symbols from the standard library. "
2078 "Check for isinstance(<X>.__doc__, str).",
2079 RuntimeWarning,
2080 stacklevel=0,
2081 )
2082 self.value = value
2083 """The value that the constant represents."""
2084
2085 self.kind: str | None = kind # can be None
2086 """"The string prefix. "u" for u-prefixed strings and ``None`` otherwise. Python 3.8+ only."""
2087
2088 super().__init__(
2089 lineno=lineno,
2090 col_offset=col_offset,
2091 end_lineno=end_lineno,
2092 end_col_offset=end_col_offset,
2093 parent=parent,
2094 )
2095
2096 Instance.__init__(self, None)
2097
2098 infer_unary_op = protocols.const_infer_unary_op
2099 infer_binary_op = protocols.const_infer_binary_op
2100
2101 def __getattr__(self, name):
2102 # This is needed because of Proxy's __getattr__ method.
2103 # Calling object.__new__ on this class without calling
2104 # __init__ would result in an infinite loop otherwise
2105 # since __getattr__ is called when an attribute doesn't
2106 # exist and self._proxied indirectly calls self.value
2107 # and Proxy __getattr__ calls self.value
2108 if name == "value":
2109 raise AttributeError
2110 return super().__getattr__(name)
2111
2112 def getitem(self, index, context: InferenceContext | None = None):
2113 """Get an item from this node if subscriptable.
2114
2115 :param index: The node to use as a subscript index.
2116 :type index: Const or Slice
2117
2118 :raises AstroidTypeError: When the given index cannot be used as a
2119 subscript index, or if this node is not subscriptable.
2120 """
2121 if isinstance(index, Const):
2122 index_value = index.value
2123 elif isinstance(index, Slice):
2124 index_value = _infer_slice(index, context=context)
2125
2126 else:
2127 raise AstroidTypeError(
2128 f"Could not use type {type(index)} as subscript index"
2129 )
2130
2131 try:
2132 if isinstance(self.value, (str, bytes)):
2133 return Const(self.value[index_value])
2134 except ValueError as exc:
2135 raise AstroidValueError(
2136 f"Could not index {self.value!r} with {index_value!r}"
2137 ) from exc
2138 except IndexError as exc:
2139 raise AstroidIndexError(
2140 message="Index {index!r} out of range",
2141 node=self,
2142 index=index,
2143 context=context,
2144 ) from exc
2145 except TypeError as exc:
2146 raise AstroidTypeError(
2147 message="Type error {error!r}", node=self, index=index, context=context
2148 ) from exc
2149
2150 try:
2151 value_str = str(self.value)
2152 except ValueError:
2153 value_str = f"<{type(self.value).__name__} (too large to display)>"
2154 raise AstroidTypeError(f"{self!r} (value={value_str})")
2155
2156 def has_dynamic_getattr(self) -> bool:
2157 """Check if the node has a custom __getattr__ or __getattribute__.
2158
2159 :returns: Whether the class has a custom __getattr__ or __getattribute__.
2160 For a :class:`Const` this is always ``False``.
2161 """
2162 return False
2163
2164 def itered(self):
2165 """An iterator over the elements this node contains.
2166
2167 :returns: The contents of this node.
2168 :rtype: iterable(Const)
2169
2170 :raises TypeError: If this node does not represent something that is iterable.
2171 """
2172 if isinstance(self.value, str):
2173 return [const_factory(elem) for elem in self.value]
2174 raise TypeError(f"Cannot iterate over type {type(self.value)!r}")
2175
2176 def pytype(self) -> str:
2177 """Get the name of the type that this node represents.
2178
2179 :returns: The name of the type.
2180 """
2181 return self._proxied.qname()
2182
2183 def bool_value(self, context: InferenceContext | None = None):
2184 """Determine the boolean value of this node.
2185
2186 :returns: The boolean value of this node.
2187 :rtype: bool or Uninferable
2188 """
2189 # bool(NotImplemented) is deprecated; it raises TypeError starting from Python 3.14
2190 # and returns True for versions under 3.14
2191 if self.value is NotImplemented:
2192 return util.Uninferable if PY314_PLUS else True
2193 return bool(self.value)
2194
2195 def _infer(
2196 self, context: InferenceContext | None = None, **kwargs: Any
2197 ) -> Iterator[Const]:
2198 yield self
2199
2200
2201class Continue(_base_nodes.NoChildrenNode, _base_nodes.Statement):
2202 """Class representing an :class:`ast.Continue` node.
2203
2204 >>> import astroid
2205 >>> node = astroid.extract_node('continue')
2206 >>> node
2207 <Continue l.1 at 0x7f23b2e35588>
2208 """
2209
2210
2211class Decorators(NodeNG):
2212 """A node representing a list of decorators.
2213
2214 A :class:`Decorators` is the decorators that are applied to
2215 a method or function.
2216
2217 >>> import astroid
2218 >>> node = astroid.extract_node('''
2219 @property
2220 def my_property(self):
2221 return 3
2222 ''')
2223 >>> node
2224 <FunctionDef.my_property l.2 at 0x7f23b2e35d30>
2225 >>> list(node.get_children())[0]
2226 <Decorators l.1 at 0x7f23b2e35d68>
2227 """
2228
2229 _astroid_fields = ("nodes",)
2230
2231 nodes: list[NodeNG]
2232 """The decorators that this node contains."""
2233
2234 def postinit(self, nodes: list[NodeNG]) -> None:
2235 self.nodes = nodes
2236
2237 def scope(self) -> LocalsDictNodeNG:
2238 """The first parent node defining a new scope.
2239 These can be Module, FunctionDef, ClassDef, Lambda, or GeneratorExp nodes.
2240
2241 :returns: The first parent scope node.
2242 """
2243 # skip the function or class node to go directly to the upper level scope
2244 if not self.parent:
2245 raise ParentMissingError(target=self)
2246 if not self.parent.parent:
2247 raise ParentMissingError(target=self.parent)
2248 return self.parent.parent.scope()
2249
2250 def frame(self) -> FrameType:
2251 """The first parent node defining a new frame."""
2252 # skip the function or class node to go directly to the upper level frame
2253 if not self.parent:
2254 raise ParentMissingError(target=self)
2255 if not self.parent.parent:
2256 raise ParentMissingError(target=self.parent)
2257 return self.parent.parent.frame()
2258
2259 def get_children(self):
2260 yield from self.nodes
2261
2262
2263class DelAttr(_base_nodes.ParentAssignNode):
2264 """Variation of :class:`ast.Delete` representing deletion of an attribute.
2265
2266 >>> import astroid
2267 >>> node = astroid.extract_node('del self.attr')
2268 >>> node
2269 <Delete l.1 at 0x7f23b2e35f60>
2270 >>> list(node.get_children())[0]
2271 <DelAttr.attr l.1 at 0x7f23b2e411d0>
2272 """
2273
2274 _astroid_fields = ("expr",)
2275 _other_fields = ("attrname",)
2276
2277 expr: NodeNG
2278 """The name that this node represents."""
2279
2280 def __init__(
2281 self,
2282 attrname: str,
2283 lineno: int,
2284 col_offset: int,
2285 parent: NodeNG,
2286 *,
2287 end_lineno: int | None,
2288 end_col_offset: int | None,
2289 ) -> None:
2290 self.attrname = attrname
2291 """The name of the attribute that is being deleted."""
2292
2293 super().__init__(
2294 lineno=lineno,
2295 col_offset=col_offset,
2296 end_lineno=end_lineno,
2297 end_col_offset=end_col_offset,
2298 parent=parent,
2299 )
2300
2301 def postinit(self, expr: NodeNG) -> None:
2302 self.expr = expr
2303
2304 def get_children(self):
2305 yield self.expr
2306
2307
2308class Delete(_base_nodes.AssignTypeNode, _base_nodes.Statement):
2309 """Class representing an :class:`ast.Delete` node.
2310
2311 A :class:`Delete` is a ``del`` statement this is deleting something.
2312
2313 >>> import astroid
2314 >>> node = astroid.extract_node('del self.attr')
2315 >>> node
2316 <Delete l.1 at 0x7f23b2e35f60>
2317 """
2318
2319 _astroid_fields = ("targets",)
2320
2321 def __init__(
2322 self,
2323 lineno: int,
2324 col_offset: int,
2325 parent: NodeNG,
2326 *,
2327 end_lineno: int | None,
2328 end_col_offset: int | None,
2329 ) -> None:
2330 self.targets: list[NodeNG] = []
2331 """What is being deleted."""
2332
2333 super().__init__(
2334 lineno=lineno,
2335 col_offset=col_offset,
2336 end_lineno=end_lineno,
2337 end_col_offset=end_col_offset,
2338 parent=parent,
2339 )
2340
2341 def postinit(self, targets: list[NodeNG]) -> None:
2342 self.targets = targets
2343
2344 def get_children(self):
2345 yield from self.targets
2346
2347
2348class Dict(NodeNG, Instance):
2349 """Class representing an :class:`ast.Dict` node.
2350
2351 A :class:`Dict` is a dictionary that is created with ``{}`` syntax.
2352
2353 >>> import astroid
2354 >>> node = astroid.extract_node('{1: "1"}')
2355 >>> node
2356 <Dict.dict l.1 at 0x7f23b2e35cc0>
2357 """
2358
2359 _astroid_fields = ("items",)
2360
2361 def __init__(
2362 self,
2363 lineno: int | None,
2364 col_offset: int | None,
2365 parent: NodeNG | None,
2366 *,
2367 end_lineno: int | None,
2368 end_col_offset: int | None,
2369 ) -> None:
2370 self.items: list[tuple[InferenceResult, InferenceResult]] = []
2371 """The key-value pairs contained in the dictionary."""
2372
2373 super().__init__(
2374 lineno=lineno,
2375 col_offset=col_offset,
2376 end_lineno=end_lineno,
2377 end_col_offset=end_col_offset,
2378 parent=parent,
2379 )
2380
2381 def postinit(self, items: list[tuple[InferenceResult, InferenceResult]]) -> None:
2382 """Do some setup after initialisation.
2383
2384 :param items: The key-value pairs contained in the dictionary.
2385 """
2386 self.items = items
2387
2388 infer_unary_op = protocols.dict_infer_unary_op
2389
2390 def pytype(self) -> Literal["builtins.dict"]:
2391 """Get the name of the type that this node represents.
2392
2393 :returns: The name of the type.
2394 """
2395 return "builtins.dict"
2396
2397 def get_children(self):
2398 """Get the key and value nodes below this node.
2399
2400 Children are returned in the order that they are defined in the source
2401 code, key first then the value.
2402
2403 :returns: The children.
2404 :rtype: iterable(NodeNG)
2405 """
2406 for key, value in self.items:
2407 yield key
2408 yield value
2409
2410 def last_child(self):
2411 """An optimized version of list(get_children())[-1]
2412
2413 :returns: The last child, or None if no children exist.
2414 :rtype: NodeNG or None
2415 """
2416 if self.items:
2417 return self.items[-1][1]
2418 return None
2419
2420 def itered(self):
2421 """An iterator over the keys this node contains.
2422
2423 :returns: The keys of this node.
2424 :rtype: iterable(NodeNG)
2425 """
2426 return [key for (key, _) in self.items]
2427
2428 def getitem(
2429 self, index: Const | Slice, context: InferenceContext | None = None
2430 ) -> NodeNG:
2431 """Get an item from this node.
2432
2433 :param index: The node to use as a subscript index.
2434
2435 :raises AstroidTypeError: When the given index cannot be used as a
2436 subscript index, or if this node is not subscriptable.
2437 :raises AstroidIndexError: If the given index does not exist in the
2438 dictionary.
2439 """
2440 for key, value in self.items:
2441 # TODO(cpopa): no support for overriding yet, {1:2, **{1: 3}}.
2442 if isinstance(key, DictUnpack):
2443 inferred_value = util.safe_infer(value, context)
2444 if not isinstance(inferred_value, Dict):
2445 continue
2446
2447 try:
2448 return inferred_value.getitem(index, context)
2449 except (AstroidTypeError, AstroidIndexError):
2450 continue
2451
2452 for inferredkey in key.infer(context):
2453 if isinstance(inferredkey, util.UninferableBase):
2454 continue
2455 if isinstance(inferredkey, Const) and isinstance(index, Const):
2456 if inferredkey.value == index.value:
2457 return value
2458
2459 raise AstroidIndexError(index)
2460
2461 def bool_value(self, context: InferenceContext | None = None):
2462 """Determine the boolean value of this node.
2463
2464 :returns: The boolean value of this node.
2465 :rtype: bool
2466 """
2467 return bool(self.items)
2468
2469 def _infer(
2470 self, context: InferenceContext | None = None, **kwargs: Any
2471 ) -> Iterator[nodes.Dict]:
2472 if not any(isinstance(k, DictUnpack) for k, _ in self.items):
2473 yield self
2474 else:
2475 items = self._infer_map(context)
2476 new_seq = type(self)(
2477 lineno=self.lineno,
2478 col_offset=self.col_offset,
2479 parent=self.parent,
2480 end_lineno=self.end_lineno,
2481 end_col_offset=self.end_col_offset,
2482 )
2483 new_seq.postinit(list(items.items()))
2484 yield new_seq
2485
2486 @staticmethod
2487 def _update_with_replacement(
2488 lhs_dict: dict[SuccessfulInferenceResult, SuccessfulInferenceResult],
2489 rhs_dict: dict[SuccessfulInferenceResult, SuccessfulInferenceResult],
2490 ) -> dict[SuccessfulInferenceResult, SuccessfulInferenceResult]:
2491 """Delete nodes that equate to duplicate keys.
2492
2493 Since an astroid node doesn't 'equal' another node with the same value,
2494 this function uses the as_string method to make sure duplicate keys
2495 don't get through
2496
2497 Note that both the key and the value are astroid nodes
2498
2499 Fixes issue with DictUnpack causing duplicate keys
2500 in inferred Dict items
2501
2502 :param lhs_dict: Dictionary to 'merge' nodes into
2503 :param rhs_dict: Dictionary with nodes to pull from
2504 :return : merged dictionary of nodes
2505 """
2506 combined_dict = itertools.chain(lhs_dict.items(), rhs_dict.items())
2507 # Overwrite keys which have the same string values
2508 string_map = {key.as_string(): (key, value) for key, value in combined_dict}
2509 # Return to dictionary
2510 return dict(string_map.values())
2511
2512 def _infer_map(
2513 self, context: InferenceContext | None
2514 ) -> dict[SuccessfulInferenceResult, SuccessfulInferenceResult]:
2515 """Infer all values based on Dict.items."""
2516 values: dict[SuccessfulInferenceResult, SuccessfulInferenceResult] = {}
2517 for name, value in self.items:
2518 if isinstance(name, DictUnpack):
2519 double_starred = util.safe_infer(value, context)
2520 if not double_starred:
2521 raise InferenceError
2522 if not isinstance(double_starred, Dict):
2523 raise InferenceError(node=self, context=context)
2524 unpack_items = double_starred._infer_map(context)
2525 values = self._update_with_replacement(values, unpack_items)
2526 else:
2527 key = util.safe_infer(name, context=context)
2528 safe_value = util.safe_infer(value, context=context)
2529 if any(not elem for elem in (key, safe_value)):
2530 raise InferenceError(node=self, context=context)
2531 # safe_value is SuccessfulInferenceResult as bool(Uninferable) == False
2532 values = self._update_with_replacement(values, {key: safe_value})
2533 return values
2534
2535
2536class Expr(_base_nodes.Statement):
2537 """Class representing an :class:`ast.Expr` node.
2538
2539 An :class:`Expr` is any expression that does not have its value used or
2540 stored.
2541
2542 >>> import astroid
2543 >>> node = astroid.extract_node('method()')
2544 >>> node
2545 <Call l.1 at 0x7f23b2e352b0>
2546 >>> node.parent
2547 <Expr l.1 at 0x7f23b2e35278>
2548 """
2549
2550 _astroid_fields = ("value",)
2551
2552 value: NodeNG
2553 """What the expression does."""
2554
2555 def postinit(self, value: NodeNG) -> None:
2556 self.value = value
2557
2558 def get_children(self):
2559 yield self.value
2560
2561 def _get_yield_nodes_skip_functions(self):
2562 if not self.value.is_function:
2563 yield from self.value._get_yield_nodes_skip_functions()
2564
2565 def _get_yield_nodes_skip_lambdas(self):
2566 if not self.value.is_lambda:
2567 yield from self.value._get_yield_nodes_skip_lambdas()
2568
2569
2570class EmptyNode(_base_nodes.NoChildrenNode):
2571 """Holds an arbitrary object in the :attr:`LocalsDictNodeNG.locals`."""
2572
2573 object = None
2574
2575 def __init__(
2576 self,
2577 lineno: None = None,
2578 col_offset: None = None,
2579 parent: NodeNG = SYNTHETIC_ROOT,
2580 *,
2581 end_lineno: None = None,
2582 end_col_offset: None = None,
2583 ) -> None:
2584 super().__init__(
2585 lineno=lineno,
2586 col_offset=col_offset,
2587 end_lineno=end_lineno,
2588 end_col_offset=end_col_offset,
2589 parent=parent,
2590 )
2591
2592 def has_underlying_object(self) -> bool:
2593 return self.object is not None and self.object is not _EMPTY_OBJECT_MARKER
2594
2595 @decorators.raise_if_nothing_inferred
2596 @decorators.path_wrapper
2597 def _infer(
2598 self, context: InferenceContext | None = None, **kwargs: Any
2599 ) -> Generator[InferenceResult]:
2600 if not self.has_underlying_object():
2601 yield util.Uninferable
2602 else:
2603 try:
2604 yield from AstroidManager().infer_ast_from_something(
2605 self.object, context=context
2606 )
2607 except AstroidError:
2608 yield util.Uninferable
2609
2610
2611class ExceptHandler(
2612 _base_nodes.MultiLineBlockNode, _base_nodes.AssignTypeNode, _base_nodes.Statement
2613):
2614 """Class representing an :class:`ast.ExceptHandler`. node.
2615
2616 An :class:`ExceptHandler` is an ``except`` block on a try-except.
2617
2618 >>> import astroid
2619 >>> node = astroid.extract_node('''
2620 try:
2621 do_something()
2622 except Exception as error:
2623 print("Error!")
2624 ''')
2625 >>> node
2626 <Try l.2 at 0x7f23b2e9d908>
2627 >>> node.handlers
2628 [<ExceptHandler l.4 at 0x7f23b2e9e860>]
2629 """
2630
2631 _astroid_fields = ("type", "name", "body")
2632 _multi_line_block_fields = ("body",)
2633
2634 type: NodeNG | None
2635 """The types that the block handles."""
2636
2637 name: AssignName | None
2638 """The name that the caught exception is assigned to."""
2639
2640 body: list[NodeNG]
2641 """The contents of the block."""
2642
2643 assigned_stmts = protocols.excepthandler_assigned_stmts
2644 """Returns the assigned statement (non inferred) according to the assignment type.
2645 See astroid/protocols.py for actual implementation.
2646 """
2647
2648 def postinit(
2649 self,
2650 type: NodeNG | None, # pylint: disable = redefined-builtin
2651 name: AssignName | None,
2652 body: list[NodeNG],
2653 ) -> None:
2654 self.type = type
2655 self.name = name
2656 self.body = body
2657
2658 def get_children(self):
2659 if self.type is not None:
2660 yield self.type
2661
2662 if self.name is not None:
2663 yield self.name
2664
2665 yield from self.body
2666
2667 @cached_property
2668 def blockstart_tolineno(self):
2669 """The line on which the beginning of this block ends.
2670
2671 :type: int
2672 """
2673 if self.name:
2674 return self.name.tolineno
2675 if self.type:
2676 return self.type.tolineno
2677 return self.lineno
2678
2679 def catch(self, exceptions: list[str] | None) -> bool:
2680 """Check if this node handles any of the given
2681
2682 :param exceptions: The names of the exceptions to check for.
2683 """
2684 if self.type is None or exceptions is None:
2685 return True
2686 return any(node.name in exceptions for node in self.type._get_name_nodes())
2687
2688
2689class For(
2690 _base_nodes.MultiLineWithElseBlockNode,
2691 _base_nodes.AssignTypeNode,
2692 _base_nodes.Statement,
2693):
2694 """Class representing an :class:`ast.For` node.
2695
2696 >>> import astroid
2697 >>> node = astroid.extract_node('for thing in things: print(thing)')
2698 >>> node
2699 <For l.1 at 0x7f23b2e8cf28>
2700 """
2701
2702 _astroid_fields = ("target", "iter", "body", "orelse")
2703 _other_other_fields = ("type_annotation",)
2704 _multi_line_block_fields = ("body", "orelse")
2705
2706 optional_assign = True
2707 """Whether this node optionally assigns a variable.
2708
2709 This is always ``True`` for :class:`For` nodes.
2710 """
2711
2712 target: NodeNG
2713 """What the loop assigns to."""
2714
2715 iter: NodeNG
2716 """What the loop iterates over."""
2717
2718 body: list[NodeNG]
2719 """The contents of the body of the loop."""
2720
2721 orelse: list[NodeNG]
2722 """The contents of the ``else`` block of the loop."""
2723
2724 type_annotation: NodeNG | None
2725 """If present, this will contain the type annotation passed by a type comment"""
2726
2727 def postinit(
2728 self,
2729 target: NodeNG,
2730 iter: NodeNG, # pylint: disable = redefined-builtin
2731 body: list[NodeNG],
2732 orelse: list[NodeNG],
2733 type_annotation: NodeNG | None,
2734 ) -> None:
2735 self.target = target
2736 self.iter = iter
2737 self.body = body
2738 self.orelse = orelse
2739 self.type_annotation = type_annotation
2740
2741 assigned_stmts = protocols.for_assigned_stmts
2742 """Returns the assigned statement (non inferred) according to the assignment type.
2743 See astroid/protocols.py for actual implementation.
2744 """
2745
2746 @cached_property
2747 def blockstart_tolineno(self):
2748 """The line on which the beginning of this block ends.
2749
2750 :type: int
2751 """
2752 return self.iter.tolineno
2753
2754 def get_children(self):
2755 yield self.target
2756 yield self.iter
2757
2758 yield from self.body
2759 yield from self.orelse
2760
2761
2762class AsyncFor(For):
2763 """Class representing an :class:`ast.AsyncFor` node.
2764
2765 An :class:`AsyncFor` is an asynchronous :class:`For` built with
2766 the ``async`` keyword.
2767
2768 >>> import astroid
2769 >>> node = astroid.extract_node('''
2770 async def func(things):
2771 async for thing in things:
2772 print(thing)
2773 ''')
2774 >>> node
2775 <AsyncFunctionDef.func l.2 at 0x7f23b2e416d8>
2776 >>> node.body[0]
2777 <AsyncFor l.3 at 0x7f23b2e417b8>
2778 """
2779
2780
2781class Await(NodeNG):
2782 """Class representing an :class:`ast.Await` node.
2783
2784 An :class:`Await` is the ``await`` keyword.
2785
2786 >>> import astroid
2787 >>> node = astroid.extract_node('''
2788 async def func(things):
2789 await other_func()
2790 ''')
2791 >>> node
2792 <AsyncFunctionDef.func l.2 at 0x7f23b2e41748>
2793 >>> node.body[0]
2794 <Expr l.3 at 0x7f23b2e419e8>
2795 >>> list(node.body[0].get_children())[0]
2796 <Await l.3 at 0x7f23b2e41a20>
2797 """
2798
2799 _astroid_fields = ("value",)
2800
2801 value: NodeNG
2802 """What to wait for."""
2803
2804 def postinit(self, value: NodeNG) -> None:
2805 self.value = value
2806
2807 def get_children(self):
2808 yield self.value
2809
2810
2811class ImportFrom(_base_nodes.ImportNode):
2812 """Class representing an :class:`ast.ImportFrom` node.
2813
2814 >>> import astroid
2815 >>> node = astroid.extract_node('from my_package import my_module')
2816 >>> node
2817 <ImportFrom l.1 at 0x7f23b2e415c0>
2818 """
2819
2820 _other_fields = ("modname", "names", "level")
2821
2822 def __init__(
2823 self,
2824 fromname: str | None,
2825 names: list[tuple[str, str | None]],
2826 level: int | None = 0,
2827 lineno: int | None = None,
2828 col_offset: int | None = None,
2829 parent: NodeNG | None = None,
2830 *,
2831 end_lineno: int | None = None,
2832 end_col_offset: int | None = None,
2833 ) -> None:
2834 """
2835 :param fromname: The module that is being imported from.
2836
2837 :param names: What is being imported from the module.
2838
2839 :param level: The level of relative import.
2840
2841 :param lineno: The line that this node appears on in the source code.
2842
2843 :param col_offset: The column that this node appears on in the
2844 source code.
2845
2846 :param parent: The parent node in the syntax tree.
2847
2848 :param end_lineno: The last line this node appears on in the source code.
2849
2850 :param end_col_offset: The end column this node appears on in the
2851 source code. Note: This is after the last symbol.
2852 """
2853 self.modname: str | None = fromname # can be None
2854 """The module that is being imported from.
2855
2856 This is ``None`` for relative imports.
2857 """
2858
2859 self.names: list[tuple[str, str | None]] = names
2860 """What is being imported from the module.
2861
2862 Each entry is a :class:`tuple` of the name being imported,
2863 and the alias that the name is assigned to (if any).
2864 """
2865
2866 self.level: int | None = level # can be None
2867 """The level of relative import.
2868
2869 Essentially this is the number of dots in the import.
2870 This is ``None`` for absolute imports.
2871 """
2872
2873 super().__init__(
2874 lineno=lineno,
2875 col_offset=col_offset,
2876 end_lineno=end_lineno,
2877 end_col_offset=end_col_offset,
2878 parent=parent,
2879 )
2880
2881 @decorators.raise_if_nothing_inferred
2882 @decorators.path_wrapper
2883 def _infer(
2884 self,
2885 context: InferenceContext | None = None,
2886 asname: bool = True,
2887 **kwargs: Any,
2888 ) -> Generator[InferenceResult]:
2889 """Infer a ImportFrom node: return the imported module/object."""
2890 context = context or InferenceContext()
2891 name = context.lookupname
2892 if name is None:
2893 raise InferenceError(node=self, context=context)
2894 if asname:
2895 try:
2896 name = self.real_name(name)
2897 except AttributeInferenceError as exc:
2898 # See https://github.com/pylint-dev/pylint/issues/4692
2899 raise InferenceError(node=self, context=context) from exc
2900 try:
2901 module = self.do_import_module()
2902 except AstroidBuildingError as exc:
2903 raise InferenceError(node=self, context=context) from exc
2904
2905 try:
2906 context = copy_context(context)
2907 context.lookupname = name
2908 stmts = module.getattr(name, ignore_locals=module is self.root())
2909 return _infer_stmts(stmts, context)
2910 except AttributeInferenceError as error:
2911 raise InferenceError(
2912 str(error), target=self, attribute=name, context=context
2913 ) from error
2914
2915
2916class Attribute(NodeNG):
2917 """Class representing an :class:`ast.Attribute` node."""
2918
2919 expr: NodeNG
2920
2921 _astroid_fields = ("expr",)
2922 _other_fields = ("attrname",)
2923
2924 def __init__(
2925 self,
2926 attrname: str,
2927 lineno: int,
2928 col_offset: int,
2929 parent: NodeNG,
2930 *,
2931 end_lineno: int | None,
2932 end_col_offset: int | None,
2933 ) -> None:
2934 self.attrname = attrname
2935 """The name of the attribute."""
2936
2937 super().__init__(
2938 lineno=lineno,
2939 col_offset=col_offset,
2940 end_lineno=end_lineno,
2941 end_col_offset=end_col_offset,
2942 parent=parent,
2943 )
2944
2945 def postinit(self, expr: NodeNG) -> None:
2946 self.expr = expr
2947
2948 def get_children(self):
2949 yield self.expr
2950
2951 @decorators.raise_if_nothing_inferred
2952 @decorators.path_wrapper
2953 def _infer(
2954 self, context: InferenceContext | None = None, **kwargs: Any
2955 ) -> Generator[InferenceResult, None, InferenceErrorInfo]:
2956 return _infer_attribute(self, context, **kwargs)
2957
2958
2959class Global(_base_nodes.NoChildrenNode, _base_nodes.Statement):
2960 """Class representing an :class:`ast.Global` node.
2961
2962 >>> import astroid
2963 >>> node = astroid.extract_node('global a_global')
2964 >>> node
2965 <Global l.1 at 0x7f23b2e9de10>
2966 """
2967
2968 _other_fields = ("names",)
2969
2970 def __init__(
2971 self,
2972 names: list[str],
2973 lineno: int | None = None,
2974 col_offset: int | None = None,
2975 parent: NodeNG | None = None,
2976 *,
2977 end_lineno: int | None = None,
2978 end_col_offset: int | None = None,
2979 ) -> None:
2980 """
2981 :param names: The names being declared as global.
2982
2983 :param lineno: The line that this node appears on in the source code.
2984
2985 :param col_offset: The column that this node appears on in the
2986 source code.
2987
2988 :param parent: The parent node in the syntax tree.
2989
2990 :param end_lineno: The last line this node appears on in the source code.
2991
2992 :param end_col_offset: The end column this node appears on in the
2993 source code. Note: This is after the last symbol.
2994 """
2995 self.names: list[str] = names
2996 """The names being declared as global."""
2997
2998 super().__init__(
2999 lineno=lineno,
3000 col_offset=col_offset,
3001 end_lineno=end_lineno,
3002 end_col_offset=end_col_offset,
3003 parent=parent,
3004 )
3005
3006 def _infer_name(self, frame, name):
3007 return name
3008
3009 @decorators.raise_if_nothing_inferred
3010 @decorators.path_wrapper
3011 def _infer(
3012 self, context: InferenceContext | None = None, **kwargs: Any
3013 ) -> Generator[InferenceResult]:
3014 if context is None or context.lookupname is None:
3015 raise InferenceError(node=self, context=context)
3016 try:
3017 # pylint: disable-next=no-member
3018 return _infer_stmts(self.root().getattr(context.lookupname), context)
3019 except AttributeInferenceError as error:
3020 raise InferenceError(
3021 str(error), target=self, attribute=context.lookupname, context=context
3022 ) from error
3023
3024
3025class If(_base_nodes.MultiLineWithElseBlockNode, _base_nodes.Statement):
3026 """Class representing an :class:`ast.If` node.
3027
3028 >>> import astroid
3029 >>> node = astroid.extract_node('if condition: print(True)')
3030 >>> node
3031 <If l.1 at 0x7f23b2e9dd30>
3032 """
3033
3034 _astroid_fields = ("test", "body", "orelse")
3035 _multi_line_block_fields = ("body", "orelse")
3036
3037 test: NodeNG
3038 """The condition that the statement tests."""
3039
3040 body: list[NodeNG]
3041 """The contents of the block."""
3042
3043 orelse: list[NodeNG]
3044 """The contents of the ``else`` block."""
3045
3046 def postinit(self, test: NodeNG, body: list[NodeNG], orelse: list[NodeNG]) -> None:
3047 self.test = test
3048 self.body = body
3049 self.orelse = orelse
3050
3051 @cached_property
3052 def blockstart_tolineno(self):
3053 """The line on which the beginning of this block ends.
3054
3055 :type: int
3056 """
3057 return self.test.tolineno
3058
3059 def get_children(self):
3060 yield self.test
3061
3062 yield from self.body
3063 yield from self.orelse
3064
3065 def has_elif_block(self) -> bool:
3066 return len(self.orelse) == 1 and isinstance(self.orelse[0], If)
3067
3068 def _get_yield_nodes_skip_functions(self):
3069 """An If node can contain a Yield node in the test"""
3070 yield from self.test._get_yield_nodes_skip_functions()
3071 yield from super()._get_yield_nodes_skip_functions()
3072
3073 def _get_yield_nodes_skip_lambdas(self):
3074 """An If node can contain a Yield node in the test"""
3075 yield from self.test._get_yield_nodes_skip_lambdas()
3076 yield from super()._get_yield_nodes_skip_lambdas()
3077
3078
3079class IfExp(NodeNG):
3080 """Class representing an :class:`ast.IfExp` node.
3081 >>> import astroid
3082 >>> node = astroid.extract_node('value if condition else other')
3083 >>> node
3084 <IfExp l.1 at 0x7f23b2e9dbe0>
3085 """
3086
3087 _astroid_fields = ("test", "body", "orelse")
3088
3089 test: NodeNG
3090 """The condition that the statement tests."""
3091
3092 body: NodeNG
3093 """The contents of the block."""
3094
3095 orelse: NodeNG
3096 """The contents of the ``else`` block."""
3097
3098 def postinit(self, test: NodeNG, body: NodeNG, orelse: NodeNG) -> None:
3099 self.test = test
3100 self.body = body
3101 self.orelse = orelse
3102
3103 def get_children(self):
3104 yield self.test
3105 yield self.body
3106 yield self.orelse
3107
3108 def op_left_associative(self) -> Literal[False]:
3109 # `1 if True else 2 if False else 3` is parsed as
3110 # `1 if True else (2 if False else 3)`
3111 return False
3112
3113 @decorators.raise_if_nothing_inferred
3114 def _infer(
3115 self, context: InferenceContext | None = None, **kwargs: Any
3116 ) -> Generator[InferenceResult]:
3117 """Support IfExp inference.
3118
3119 If we can't infer the truthiness of the condition, we default
3120 to inferring both branches. Otherwise, we infer either branch
3121 depending on the condition.
3122 """
3123
3124 # We use two separate contexts for evaluating lhs and rhs because
3125 # evaluating lhs may leave some undesired entries in context.path
3126 # which may not let us infer right value of rhs.
3127 context = context or InferenceContext()
3128 lhs_context = copy_context(context)
3129 rhs_context = copy_context(context)
3130
3131 # Infer bool condition. Stop inferring if in doubt and fallback to
3132 # evaluating both branches.
3133 condition: bool | None = None
3134 try:
3135 for test in self.test.infer(context=context.clone()):
3136 if isinstance(test, util.UninferableBase):
3137 condition = None
3138 break
3139 test_bool_value = test.bool_value()
3140 if isinstance(test_bool_value, util.UninferableBase):
3141 condition = None
3142 break
3143 if condition is None:
3144 condition = test_bool_value
3145 elif test_bool_value != condition:
3146 condition = None
3147 break
3148 except InferenceError:
3149 condition = None
3150
3151 if condition is True or condition is None:
3152 yield from self.body.infer(context=lhs_context)
3153 if condition is False or condition is None:
3154 yield from self.orelse.infer(context=rhs_context)
3155
3156
3157class Import(_base_nodes.ImportNode):
3158 """Class representing an :class:`ast.Import` node.
3159 >>> import astroid
3160 >>> node = astroid.extract_node('import astroid')
3161 >>> node
3162 <Import l.1 at 0x7f23b2e4e5c0>
3163 """
3164
3165 _other_fields = ("names",)
3166
3167 def __init__(
3168 self,
3169 names: list[tuple[str, str | None]],
3170 lineno: int | None = None,
3171 col_offset: int | None = None,
3172 parent: NodeNG | None = None,
3173 *,
3174 end_lineno: int | None = None,
3175 end_col_offset: int | None = None,
3176 ) -> None:
3177 """
3178 :param names: The names being imported.
3179
3180 :param lineno: The line that this node appears on in the source code.
3181
3182 :param col_offset: The column that this node appears on in the
3183 source code.
3184
3185 :param parent: The parent node in the syntax tree.
3186
3187 :param end_lineno: The last line this node appears on in the source code.
3188
3189 :param end_col_offset: The end column this node appears on in the
3190 source code. Note: This is after the last symbol.
3191 """
3192 self.names: list[tuple[str, str | None]] = names
3193 """The names being imported.
3194
3195 Each entry is a :class:`tuple` of the name being imported,
3196 and the alias that the name is assigned to (if any).
3197 """
3198
3199 super().__init__(
3200 lineno=lineno,
3201 col_offset=col_offset,
3202 end_lineno=end_lineno,
3203 end_col_offset=end_col_offset,
3204 parent=parent,
3205 )
3206
3207 @decorators.raise_if_nothing_inferred
3208 @decorators.path_wrapper
3209 def _infer(
3210 self,
3211 context: InferenceContext | None = None,
3212 asname: bool = True,
3213 **kwargs: Any,
3214 ) -> Generator[nodes.Module]:
3215 """Infer an Import node: return the imported module/object."""
3216 context = context or InferenceContext()
3217 name = context.lookupname
3218 if name is None:
3219 raise InferenceError(node=self, context=context)
3220
3221 try:
3222 if asname:
3223 yield self.do_import_module(self.real_name(name))
3224 else:
3225 yield self.do_import_module(name)
3226 except AstroidBuildingError as exc:
3227 raise InferenceError(node=self, context=context) from exc
3228
3229
3230class Keyword(NodeNG):
3231 """Class representing an :class:`ast.keyword` node.
3232
3233 >>> import astroid
3234 >>> node = astroid.extract_node('function(a_kwarg=True)')
3235 >>> node
3236 <Call l.1 at 0x7f23b2e9e320>
3237 >>> node.keywords
3238 [<Keyword l.1 at 0x7f23b2e9e9b0>]
3239 """
3240
3241 _astroid_fields = ("value",)
3242 _other_fields = ("arg",)
3243
3244 value: NodeNG
3245 """The value being assigned to the keyword argument."""
3246
3247 def __init__(
3248 self,
3249 arg: str | None,
3250 lineno: int | None,
3251 col_offset: int | None,
3252 parent: NodeNG,
3253 *,
3254 end_lineno: int | None,
3255 end_col_offset: int | None,
3256 ) -> None:
3257 self.arg = arg
3258 """The argument being assigned to."""
3259
3260 super().__init__(
3261 lineno=lineno,
3262 col_offset=col_offset,
3263 end_lineno=end_lineno,
3264 end_col_offset=end_col_offset,
3265 parent=parent,
3266 )
3267
3268 def postinit(self, value: NodeNG) -> None:
3269 self.value = value
3270
3271 def get_children(self):
3272 yield self.value
3273
3274
3275class List(BaseContainer):
3276 """Class representing an :class:`ast.List` node.
3277
3278 >>> import astroid
3279 >>> node = astroid.extract_node('[1, 2, 3]')
3280 >>> node
3281 <List.list l.1 at 0x7f23b2e9e128>
3282 """
3283
3284 _other_fields = ("ctx",)
3285
3286 def __init__(
3287 self,
3288 ctx: Context | None = None,
3289 lineno: int | None = None,
3290 col_offset: int | None = None,
3291 parent: NodeNG | None = None,
3292 *,
3293 end_lineno: int | None = None,
3294 end_col_offset: int | None = None,
3295 ) -> None:
3296 """
3297 :param ctx: Whether the list is assigned to or loaded from.
3298
3299 :param lineno: The line that this node appears on in the source code.
3300
3301 :param col_offset: The column that this node appears on in the
3302 source code.
3303
3304 :param parent: The parent node in the syntax tree.
3305
3306 :param end_lineno: The last line this node appears on in the source code.
3307
3308 :param end_col_offset: The end column this node appears on in the
3309 source code. Note: This is after the last symbol.
3310 """
3311 self.ctx: Context | None = ctx
3312 """Whether the list is assigned to or loaded from."""
3313
3314 super().__init__(
3315 lineno=lineno,
3316 col_offset=col_offset,
3317 end_lineno=end_lineno,
3318 end_col_offset=end_col_offset,
3319 parent=parent,
3320 )
3321
3322 assigned_stmts = protocols.sequence_assigned_stmts
3323 """Returns the assigned statement (non inferred) according to the assignment type.
3324 See astroid/protocols.py for actual implementation.
3325 """
3326
3327 infer_unary_op = protocols.list_infer_unary_op
3328 infer_binary_op = protocols.tl_infer_binary_op
3329
3330 def pytype(self) -> Literal["builtins.list"]:
3331 """Get the name of the type that this node represents.
3332
3333 :returns: The name of the type.
3334 """
3335 return "builtins.list"
3336
3337 def getitem(self, index, context: InferenceContext | None = None):
3338 """Get an item from this node.
3339
3340 :param index: The node to use as a subscript index.
3341 :type index: Const or Slice
3342 """
3343 return _container_getitem(self, self.elts, index, context=context)
3344
3345
3346class Nonlocal(_base_nodes.NoChildrenNode, _base_nodes.Statement):
3347 """Class representing an :class:`ast.Nonlocal` node.
3348
3349 >>> import astroid
3350 >>> node = astroid.extract_node('''
3351 def function():
3352 nonlocal var
3353 ''')
3354 >>> node
3355 <FunctionDef.function l.2 at 0x7f23b2e9e208>
3356 >>> node.body[0]
3357 <Nonlocal l.3 at 0x7f23b2e9e908>
3358 """
3359
3360 _other_fields = ("names",)
3361
3362 def __init__(
3363 self,
3364 names: list[str],
3365 lineno: int | None = None,
3366 col_offset: int | None = None,
3367 parent: NodeNG | None = None,
3368 *,
3369 end_lineno: int | None = None,
3370 end_col_offset: int | None = None,
3371 ) -> None:
3372 """
3373 :param names: The names being declared as not local.
3374
3375 :param lineno: The line that this node appears on in the source code.
3376
3377 :param col_offset: The column that this node appears on in the
3378 source code.
3379
3380 :param parent: The parent node in the syntax tree.
3381
3382 :param end_lineno: The last line this node appears on in the source code.
3383
3384 :param end_col_offset: The end column this node appears on in the
3385 source code. Note: This is after the last symbol.
3386 """
3387 self.names: list[str] = names
3388 """The names being declared as not local."""
3389
3390 super().__init__(
3391 lineno=lineno,
3392 col_offset=col_offset,
3393 end_lineno=end_lineno,
3394 end_col_offset=end_col_offset,
3395 parent=parent,
3396 )
3397
3398 def _infer_name(self, frame, name):
3399 return name
3400
3401
3402class ParamSpec(_base_nodes.AssignTypeNode):
3403 """Class representing a :class:`ast.ParamSpec` node.
3404
3405 >>> import astroid
3406 >>> node = astroid.extract_node('type Alias[**P] = Callable[P, int]')
3407 >>> node.type_params[0]
3408 <ParamSpec l.1 at 0x7f23b2e4e198>
3409 """
3410
3411 _astroid_fields = ("name", "default_value")
3412 name: AssignName
3413 default_value: NodeNG | None
3414
3415 def __init__(
3416 self,
3417 lineno: int,
3418 col_offset: int,
3419 parent: NodeNG,
3420 *,
3421 end_lineno: int,
3422 end_col_offset: int,
3423 ) -> None:
3424 super().__init__(
3425 lineno=lineno,
3426 col_offset=col_offset,
3427 end_lineno=end_lineno,
3428 end_col_offset=end_col_offset,
3429 parent=parent,
3430 )
3431
3432 def postinit(self, *, name: AssignName, default_value: NodeNG | None) -> None:
3433 self.name = name
3434 self.default_value = default_value
3435
3436 def _infer(
3437 self, context: InferenceContext | None = None, **kwargs: Any
3438 ) -> Iterator[ParamSpec]:
3439 yield self
3440
3441 assigned_stmts = protocols.generic_type_assigned_stmts
3442 """Returns the assigned statement (non inferred) according to the assignment type.
3443 See astroid/protocols.py for actual implementation.
3444 """
3445
3446
3447class Pass(_base_nodes.NoChildrenNode, _base_nodes.Statement):
3448 """Class representing an :class:`ast.Pass` node.
3449
3450 >>> import astroid
3451 >>> node = astroid.extract_node('pass')
3452 >>> node
3453 <Pass l.1 at 0x7f23b2e9e748>
3454 """
3455
3456
3457class Raise(_base_nodes.Statement):
3458 """Class representing an :class:`ast.Raise` node.
3459
3460 >>> import astroid
3461 >>> node = astroid.extract_node('raise RuntimeError("Something bad happened!")')
3462 >>> node
3463 <Raise l.1 at 0x7f23b2e9e828>
3464 """
3465
3466 _astroid_fields = ("exc", "cause")
3467
3468 exc: NodeNG | None
3469 """What is being raised."""
3470
3471 cause: NodeNG | None
3472 """The exception being used to raise this one."""
3473
3474 def postinit(
3475 self,
3476 exc: NodeNG | None,
3477 cause: NodeNG | None,
3478 ) -> None:
3479 self.exc = exc
3480 self.cause = cause
3481
3482 def raises_not_implemented(self) -> bool:
3483 """Check if this node raises a :class:`NotImplementedError`.
3484
3485 :returns: Whether this node raises a :class:`NotImplementedError`.
3486 """
3487 if not self.exc:
3488 return False
3489 return any(
3490 name.name == "NotImplementedError" for name in self.exc._get_name_nodes()
3491 )
3492
3493 def get_children(self):
3494 if self.exc is not None:
3495 yield self.exc
3496
3497 if self.cause is not None:
3498 yield self.cause
3499
3500
3501class Return(_base_nodes.Statement):
3502 """Class representing an :class:`ast.Return` node.
3503
3504 >>> import astroid
3505 >>> node = astroid.extract_node('return True')
3506 >>> node
3507 <Return l.1 at 0x7f23b8211908>
3508 """
3509
3510 _astroid_fields = ("value",)
3511
3512 value: NodeNG | None
3513 """The value being returned."""
3514
3515 def postinit(self, value: NodeNG | None) -> None:
3516 self.value = value
3517
3518 def get_children(self):
3519 if self.value is not None:
3520 yield self.value
3521
3522 def is_tuple_return(self) -> bool:
3523 return isinstance(self.value, Tuple)
3524
3525 def _get_return_nodes_skip_functions(self):
3526 yield self
3527
3528
3529class Set(BaseContainer):
3530 """Class representing an :class:`ast.Set` node.
3531
3532 >>> import astroid
3533 >>> node = astroid.extract_node('{1, 2, 3}')
3534 >>> node
3535 <Set.set l.1 at 0x7f23b2e71d68>
3536 """
3537
3538 infer_unary_op = protocols.set_infer_unary_op
3539
3540 def pytype(self) -> Literal["builtins.set"]:
3541 """Get the name of the type that this node represents.
3542
3543 :returns: The name of the type.
3544 """
3545 return "builtins.set"
3546
3547
3548class Slice(NodeNG):
3549 """Class representing an :class:`ast.Slice` node.
3550
3551 >>> import astroid
3552 >>> node = astroid.extract_node('things[1:3]')
3553 >>> node
3554 <Subscript l.1 at 0x7f23b2e71f60>
3555 >>> node.slice
3556 <Slice l.1 at 0x7f23b2e71e80>
3557 """
3558
3559 _astroid_fields = ("lower", "upper", "step")
3560
3561 lower: NodeNG | None
3562 """The lower index in the slice."""
3563
3564 upper: NodeNG | None
3565 """The upper index in the slice."""
3566
3567 step: NodeNG | None
3568 """The step to take between indexes."""
3569
3570 def postinit(
3571 self,
3572 lower: NodeNG | None,
3573 upper: NodeNG | None,
3574 step: NodeNG | None,
3575 ) -> None:
3576 self.lower = lower
3577 self.upper = upper
3578 self.step = step
3579
3580 def _wrap_attribute(self, attr):
3581 """Wrap the empty attributes of the Slice in a Const node."""
3582 if not attr:
3583 const = const_factory(attr)
3584 const.parent = self
3585 return const
3586 return attr
3587
3588 @cached_property
3589 def _proxied(self) -> nodes.ClassDef:
3590 builtins = AstroidManager().builtins_module
3591 return builtins.getattr("slice")[0]
3592
3593 def pytype(self) -> Literal["builtins.slice"]:
3594 """Get the name of the type that this node represents.
3595
3596 :returns: The name of the type.
3597 """
3598 return "builtins.slice"
3599
3600 def display_type(self) -> Literal["Slice"]:
3601 """A human readable type of this node.
3602
3603 :returns: The type of this node.
3604 """
3605 return "Slice"
3606
3607 def igetattr(
3608 self, attrname: str, context: InferenceContext | None = None
3609 ) -> Iterator[SuccessfulInferenceResult]:
3610 """Infer the possible values of the given attribute on the slice.
3611
3612 :param attrname: The name of the attribute to infer.
3613
3614 :returns: The inferred possible values.
3615 """
3616 if attrname == "start":
3617 yield self._wrap_attribute(self.lower)
3618 elif attrname == "stop":
3619 yield self._wrap_attribute(self.upper)
3620 elif attrname == "step":
3621 yield self._wrap_attribute(self.step)
3622 else:
3623 yield from self.getattr(attrname, context=context)
3624
3625 def getattr(self, attrname, context: InferenceContext | None = None):
3626 return self._proxied.getattr(attrname, context)
3627
3628 def get_children(self):
3629 if self.lower is not None:
3630 yield self.lower
3631
3632 if self.upper is not None:
3633 yield self.upper
3634
3635 if self.step is not None:
3636 yield self.step
3637
3638 def _infer(
3639 self, context: InferenceContext | None = None, **kwargs: Any
3640 ) -> Iterator[Slice]:
3641 yield self
3642
3643
3644class Starred(_base_nodes.ParentAssignNode):
3645 """Class representing an :class:`ast.Starred` node.
3646
3647 >>> import astroid
3648 >>> node = astroid.extract_node('*args')
3649 >>> node
3650 <Starred l.1 at 0x7f23b2e41978>
3651 """
3652
3653 _astroid_fields = ("value",)
3654 _other_fields = ("ctx",)
3655
3656 value: NodeNG
3657 """What is being unpacked."""
3658
3659 def __init__(
3660 self,
3661 ctx: Context,
3662 lineno: int,
3663 col_offset: int,
3664 parent: NodeNG,
3665 *,
3666 end_lineno: int | None,
3667 end_col_offset: int | None,
3668 ) -> None:
3669 self.ctx = ctx
3670 """Whether the starred item is assigned to or loaded from."""
3671
3672 super().__init__(
3673 lineno=lineno,
3674 col_offset=col_offset,
3675 end_lineno=end_lineno,
3676 end_col_offset=end_col_offset,
3677 parent=parent,
3678 )
3679
3680 def postinit(self, value: NodeNG) -> None:
3681 self.value = value
3682
3683 assigned_stmts = protocols.starred_assigned_stmts
3684 """Returns the assigned statement (non inferred) according to the assignment type.
3685 See astroid/protocols.py for actual implementation.
3686 """
3687
3688 def get_children(self):
3689 yield self.value
3690
3691
3692class Subscript(NodeNG):
3693 """Class representing an :class:`ast.Subscript` node.
3694
3695 >>> import astroid
3696 >>> node = astroid.extract_node('things[1:3]')
3697 >>> node
3698 <Subscript l.1 at 0x7f23b2e71f60>
3699 """
3700
3701 _SUBSCRIPT_SENTINEL = object()
3702 _astroid_fields = ("value", "slice")
3703 _other_fields = ("ctx",)
3704
3705 value: NodeNG
3706 """What is being indexed."""
3707
3708 slice: NodeNG
3709 """The slice being used to lookup."""
3710
3711 def __init__(
3712 self,
3713 ctx: Context,
3714 lineno: int,
3715 col_offset: int,
3716 parent: NodeNG,
3717 *,
3718 end_lineno: int | None,
3719 end_col_offset: int | None,
3720 ) -> None:
3721 self.ctx = ctx
3722 """Whether the subscripted item is assigned to or loaded from."""
3723
3724 super().__init__(
3725 lineno=lineno,
3726 col_offset=col_offset,
3727 end_lineno=end_lineno,
3728 end_col_offset=end_col_offset,
3729 parent=parent,
3730 )
3731
3732 # pylint: disable=redefined-builtin; had to use the same name as builtin ast module.
3733 def postinit(self, value: NodeNG, slice: NodeNG) -> None:
3734 self.value = value
3735 self.slice = slice
3736
3737 def get_children(self):
3738 yield self.value
3739 yield self.slice
3740
3741 def _infer_subscript(
3742 self, context: InferenceContext | None = None, **kwargs: Any
3743 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
3744 """Inference for subscripts.
3745
3746 We're understanding if the index is a Const
3747 or a slice, passing the result of inference
3748 to the value's `getitem` method, which should
3749 handle each supported index type accordingly.
3750 """
3751 from astroid import helpers # pylint: disable=import-outside-toplevel
3752
3753 found_one = False
3754 for value in self.value.infer(context):
3755 if isinstance(value, util.UninferableBase):
3756 yield util.Uninferable
3757 return None
3758 for index in self.slice.infer(context):
3759 if isinstance(index, util.UninferableBase):
3760 yield util.Uninferable
3761 return None
3762
3763 # Try to deduce the index value.
3764 index_value = self._SUBSCRIPT_SENTINEL
3765 if value.__class__ == Instance:
3766 index_value = index
3767 elif index.__class__ == Instance:
3768 instance_as_index = helpers.class_instance_as_index(index)
3769 if instance_as_index:
3770 index_value = instance_as_index
3771 else:
3772 index_value = index
3773
3774 if index_value is self._SUBSCRIPT_SENTINEL:
3775 raise InferenceError(node=self, context=context)
3776
3777 try:
3778 assigned = value.getitem(index_value, context)
3779 except (
3780 AstroidTypeError,
3781 AstroidIndexError,
3782 AstroidValueError,
3783 AttributeInferenceError,
3784 AttributeError,
3785 ) as exc:
3786 raise InferenceError(node=self, context=context) from exc
3787
3788 # Prevent inferring if the inferred subscript
3789 # is the same as the original subscripted object.
3790 if self is assigned or isinstance(assigned, util.UninferableBase):
3791 yield util.Uninferable
3792 return None
3793 yield from assigned.infer(context)
3794 found_one = True
3795
3796 if found_one:
3797 return InferenceErrorInfo(node=self, context=context)
3798 return None
3799
3800 @decorators.raise_if_nothing_inferred
3801 @decorators.path_wrapper
3802 def _infer(self, context: InferenceContext | None = None, **kwargs: Any):
3803 return self._infer_subscript(context, **kwargs)
3804
3805 @decorators.raise_if_nothing_inferred
3806 def infer_lhs(self, context: InferenceContext | None = None, **kwargs: Any):
3807 return self._infer_subscript(context, **kwargs)
3808
3809
3810class Try(_base_nodes.MultiLineWithElseBlockNode, _base_nodes.Statement):
3811 """Class representing a :class:`ast.Try` node.
3812
3813 >>> import astroid
3814 >>> node = astroid.extract_node('''
3815 try:
3816 do_something()
3817 except Exception as error:
3818 print("Error!")
3819 finally:
3820 print("Cleanup!")
3821 ''')
3822 >>> node
3823 <Try l.2 at 0x7f23b2e41d68>
3824 """
3825
3826 _astroid_fields = ("body", "handlers", "orelse", "finalbody")
3827 _multi_line_block_fields = ("body", "handlers", "orelse", "finalbody")
3828
3829 def __init__(
3830 self,
3831 *,
3832 lineno: int,
3833 col_offset: int,
3834 end_lineno: int,
3835 end_col_offset: int,
3836 parent: NodeNG,
3837 ) -> None:
3838 """
3839 :param lineno: The line that this node appears on in the source code.
3840
3841 :param col_offset: The column that this node appears on in the
3842 source code.
3843
3844 :param parent: The parent node in the syntax tree.
3845
3846 :param end_lineno: The last line this node appears on in the source code.
3847
3848 :param end_col_offset: The end column this node appears on in the
3849 source code. Note: This is after the last symbol.
3850 """
3851 self.body: list[NodeNG] = []
3852 """The contents of the block to catch exceptions from."""
3853
3854 self.handlers: list[ExceptHandler] = []
3855 """The exception handlers."""
3856
3857 self.orelse: list[NodeNG] = []
3858 """The contents of the ``else`` block."""
3859
3860 self.finalbody: list[NodeNG] = []
3861 """The contents of the ``finally`` block."""
3862
3863 super().__init__(
3864 lineno=lineno,
3865 col_offset=col_offset,
3866 end_lineno=end_lineno,
3867 end_col_offset=end_col_offset,
3868 parent=parent,
3869 )
3870
3871 def postinit(
3872 self,
3873 *,
3874 body: list[NodeNG],
3875 handlers: list[ExceptHandler],
3876 orelse: list[NodeNG],
3877 finalbody: list[NodeNG],
3878 ) -> None:
3879 """Do some setup after initialisation.
3880
3881 :param body: The contents of the block to catch exceptions from.
3882
3883 :param handlers: The exception handlers.
3884
3885 :param orelse: The contents of the ``else`` block.
3886
3887 :param finalbody: The contents of the ``finally`` block.
3888 """
3889 self.body = body
3890 self.handlers = handlers
3891 self.orelse = orelse
3892 self.finalbody = finalbody
3893
3894 def _infer_name(self, frame, name):
3895 return name
3896
3897 def block_range(self, lineno: int) -> tuple[int, int]:
3898 """Get a range from a given line number to where this node ends."""
3899 for exhandler in self.handlers:
3900 if exhandler.type and lineno == exhandler.type.fromlineno:
3901 return lineno, exhandler.tolineno
3902 if exhandler.body[0].fromlineno <= lineno <= exhandler.body[-1].tolineno:
3903 return lineno, exhandler.body[-1].tolineno
3904 if self.finalbody:
3905 if self.finalbody[0].fromlineno - 1 == lineno:
3906 return lineno, self.finalbody[0].tolineno
3907 if self.finalbody[0].fromlineno <= lineno <= self.finalbody[-1].tolineno:
3908 return lineno, self.finalbody[-1].tolineno
3909
3910 # If not within any of the ExceptHandlers or `finally` body, fall back to regular
3911 # handling of block_range for nodes with a potential `else` statement.
3912 return super().block_range(lineno)
3913
3914 def get_children(self):
3915 yield from self.body
3916 yield from self.handlers
3917 yield from self.orelse
3918 yield from self.finalbody
3919
3920
3921class TryStar(_base_nodes.MultiLineWithElseBlockNode, _base_nodes.Statement):
3922 """Class representing an :class:`ast.TryStar` node."""
3923
3924 _astroid_fields = ("body", "handlers", "orelse", "finalbody")
3925 _multi_line_block_fields = ("body", "handlers", "orelse", "finalbody")
3926
3927 def __init__(
3928 self,
3929 *,
3930 lineno: int | None = None,
3931 col_offset: int | None = None,
3932 end_lineno: int | None = None,
3933 end_col_offset: int | None = None,
3934 parent: NodeNG | None = None,
3935 ) -> None:
3936 """
3937 :param lineno: The line that this node appears on in the source code.
3938 :param col_offset: The column that this node appears on in the
3939 source code.
3940 :param parent: The parent node in the syntax tree.
3941 :param end_lineno: The last line this node appears on in the source code.
3942 :param end_col_offset: The end column this node appears on in the
3943 source code. Note: This is after the last symbol.
3944 """
3945 self.body: list[NodeNG] = []
3946 """The contents of the block to catch exceptions from."""
3947
3948 self.handlers: list[ExceptHandler] = []
3949 """The exception handlers."""
3950
3951 self.orelse: list[NodeNG] = []
3952 """The contents of the ``else`` block."""
3953
3954 self.finalbody: list[NodeNG] = []
3955 """The contents of the ``finally`` block."""
3956
3957 super().__init__(
3958 lineno=lineno,
3959 col_offset=col_offset,
3960 end_lineno=end_lineno,
3961 end_col_offset=end_col_offset,
3962 parent=parent,
3963 )
3964
3965 def postinit(
3966 self,
3967 *,
3968 body: list[NodeNG] | None = None,
3969 handlers: list[ExceptHandler] | None = None,
3970 orelse: list[NodeNG] | None = None,
3971 finalbody: list[NodeNG] | None = None,
3972 ) -> None:
3973 """Do some setup after initialisation.
3974 :param body: The contents of the block to catch exceptions from.
3975 :param handlers: The exception handlers.
3976 :param orelse: The contents of the ``else`` block.
3977 :param finalbody: The contents of the ``finally`` block.
3978 """
3979 if body:
3980 self.body = body
3981 if handlers:
3982 self.handlers = handlers
3983 if orelse:
3984 self.orelse = orelse
3985 if finalbody:
3986 self.finalbody = finalbody
3987
3988 def _infer_name(self, frame, name):
3989 return name
3990
3991 def get_children(self):
3992 yield from self.body
3993 yield from self.handlers
3994 yield from self.orelse
3995 yield from self.finalbody
3996
3997 def block_range(self, lineno: int) -> tuple[int, int]:
3998 """Get a range from a given line number to where this node ends."""
3999 for exhandler in self.handlers:
4000 if exhandler.type and lineno == exhandler.type.fromlineno:
4001 return lineno, exhandler.tolineno
4002 if exhandler.body[0].fromlineno <= lineno <= exhandler.body[-1].tolineno:
4003 return lineno, exhandler.body[-1].tolineno
4004 if self.finalbody:
4005 if self.finalbody[0].fromlineno - 1 == lineno:
4006 return lineno, self.finalbody[0].tolineno
4007 if self.finalbody[0].fromlineno <= lineno <= self.finalbody[-1].tolineno:
4008 return lineno, self.finalbody[-1].tolineno
4009
4010 # If not within any of the ExceptHandlers or `finally` body, fall back to regular
4011 # handling of block_range for nodes with a potential `else` statement.
4012 return super().block_range(lineno)
4013
4014
4015class Tuple(BaseContainer):
4016 """Class representing an :class:`ast.Tuple` node.
4017
4018 >>> import astroid
4019 >>> node = astroid.extract_node('(1, 2, 3)')
4020 >>> node
4021 <Tuple.tuple l.1 at 0x7f23b2e41780>
4022 """
4023
4024 _other_fields = ("ctx",)
4025
4026 def __init__(
4027 self,
4028 ctx: Context | None = None,
4029 lineno: int | None = None,
4030 col_offset: int | None = None,
4031 parent: NodeNG | None = None,
4032 *,
4033 end_lineno: int | None = None,
4034 end_col_offset: int | None = None,
4035 ) -> None:
4036 """
4037 :param ctx: Whether the tuple is assigned to or loaded from.
4038
4039 :param lineno: The line that this node appears on in the source code.
4040
4041 :param col_offset: The column that this node appears on in the
4042 source code.
4043
4044 :param parent: The parent node in the syntax tree.
4045
4046 :param end_lineno: The last line this node appears on in the source code.
4047
4048 :param end_col_offset: The end column this node appears on in the
4049 source code. Note: This is after the last symbol.
4050 """
4051 self.ctx: Context | None = ctx
4052 """Whether the tuple is assigned to or loaded from."""
4053
4054 super().__init__(
4055 lineno=lineno,
4056 col_offset=col_offset,
4057 end_lineno=end_lineno,
4058 end_col_offset=end_col_offset,
4059 parent=parent,
4060 )
4061
4062 assigned_stmts = protocols.sequence_assigned_stmts
4063 """Returns the assigned statement (non inferred) according to the assignment type.
4064 See astroid/protocols.py for actual implementation.
4065 """
4066
4067 infer_unary_op = protocols.tuple_infer_unary_op
4068 infer_binary_op = protocols.tl_infer_binary_op
4069
4070 def pytype(self) -> Literal["builtins.tuple"]:
4071 """Get the name of the type that this node represents.
4072
4073 :returns: The name of the type.
4074 """
4075 return "builtins.tuple"
4076
4077 def getitem(self, index, context: InferenceContext | None = None):
4078 """Get an item from this node.
4079
4080 :param index: The node to use as a subscript index.
4081 :type index: Const or Slice
4082 """
4083 return _container_getitem(self, self.elts, index, context=context)
4084
4085
4086class TypeAlias(_base_nodes.AssignTypeNode, _base_nodes.Statement):
4087 """Class representing a :class:`ast.TypeAlias` node.
4088
4089 >>> import astroid
4090 >>> node = astroid.extract_node('type Point = tuple[float, float]')
4091 >>> node
4092 <TypeAlias l.1 at 0x7f23b2e4e198>
4093 """
4094
4095 _astroid_fields = ("name", "type_params", "value")
4096
4097 name: AssignName
4098 type_params: list[TypeVar | ParamSpec | TypeVarTuple]
4099 value: NodeNG
4100
4101 def __init__(
4102 self,
4103 lineno: int,
4104 col_offset: int,
4105 parent: NodeNG,
4106 *,
4107 end_lineno: int,
4108 end_col_offset: int,
4109 ) -> None:
4110 super().__init__(
4111 lineno=lineno,
4112 col_offset=col_offset,
4113 end_lineno=end_lineno,
4114 end_col_offset=end_col_offset,
4115 parent=parent,
4116 )
4117
4118 def postinit(
4119 self,
4120 *,
4121 name: AssignName,
4122 type_params: list[TypeVar | ParamSpec | TypeVarTuple],
4123 value: NodeNG,
4124 ) -> None:
4125 self.name = name
4126 self.type_params = type_params
4127 self.value = value
4128
4129 def _infer(
4130 self, context: InferenceContext | None = None, **kwargs: Any
4131 ) -> Iterator[TypeAlias]:
4132 yield self
4133
4134 assigned_stmts: ClassVar[
4135 Callable[
4136 [
4137 TypeAlias,
4138 AssignName,
4139 InferenceContext | None,
4140 None,
4141 ],
4142 Generator[NodeNG],
4143 ]
4144 ] = protocols.assign_assigned_stmts
4145
4146
4147class TypeVar(_base_nodes.AssignTypeNode):
4148 """Class representing a :class:`ast.TypeVar` node.
4149
4150 >>> import astroid
4151 >>> node = astroid.extract_node('type Point[T] = tuple[float, float]')
4152 >>> node.type_params[0]
4153 <TypeVar l.1 at 0x7f23b2e4e198>
4154 """
4155
4156 _astroid_fields = ("name", "bound", "default_value")
4157 name: AssignName
4158 bound: NodeNG | None
4159 default_value: NodeNG | None
4160
4161 def __init__(
4162 self,
4163 lineno: int,
4164 col_offset: int,
4165 parent: NodeNG,
4166 *,
4167 end_lineno: int,
4168 end_col_offset: int,
4169 ) -> None:
4170 super().__init__(
4171 lineno=lineno,
4172 col_offset=col_offset,
4173 end_lineno=end_lineno,
4174 end_col_offset=end_col_offset,
4175 parent=parent,
4176 )
4177
4178 def postinit(
4179 self,
4180 *,
4181 name: AssignName,
4182 bound: NodeNG | None,
4183 default_value: NodeNG | None = None,
4184 ) -> None:
4185 self.name = name
4186 self.bound = bound
4187 self.default_value = default_value
4188
4189 def _infer(
4190 self, context: InferenceContext | None = None, **kwargs: Any
4191 ) -> Iterator[TypeVar]:
4192 yield self
4193
4194 assigned_stmts = protocols.generic_type_assigned_stmts
4195 """Returns the assigned statement (non inferred) according to the assignment type.
4196 See astroid/protocols.py for actual implementation.
4197 """
4198
4199
4200class TypeVarTuple(_base_nodes.AssignTypeNode):
4201 """Class representing a :class:`ast.TypeVarTuple` node.
4202
4203 >>> import astroid
4204 >>> node = astroid.extract_node('type Alias[*Ts] = tuple[*Ts]')
4205 >>> node.type_params[0]
4206 <TypeVarTuple l.1 at 0x7f23b2e4e198>
4207 """
4208
4209 _astroid_fields = ("name", "default_value")
4210 name: AssignName
4211 default_value: NodeNG | None
4212
4213 def __init__(
4214 self,
4215 lineno: int,
4216 col_offset: int,
4217 parent: NodeNG,
4218 *,
4219 end_lineno: int,
4220 end_col_offset: int,
4221 ) -> None:
4222 super().__init__(
4223 lineno=lineno,
4224 col_offset=col_offset,
4225 end_lineno=end_lineno,
4226 end_col_offset=end_col_offset,
4227 parent=parent,
4228 )
4229
4230 def postinit(
4231 self, *, name: AssignName, default_value: NodeNG | None = None
4232 ) -> None:
4233 self.name = name
4234 self.default_value = default_value
4235
4236 def _infer(
4237 self, context: InferenceContext | None = None, **kwargs: Any
4238 ) -> Iterator[TypeVarTuple]:
4239 yield self
4240
4241 assigned_stmts = protocols.generic_type_assigned_stmts
4242 """Returns the assigned statement (non inferred) according to the assignment type.
4243 See astroid/protocols.py for actual implementation.
4244 """
4245
4246
4247UNARY_OP_METHOD = {
4248 "+": "__pos__",
4249 "-": "__neg__",
4250 "~": "__invert__",
4251 "not": None, # XXX not '__nonzero__'
4252}
4253
4254
4255class UnaryOp(_base_nodes.OperatorNode):
4256 """Class representing an :class:`ast.UnaryOp` node.
4257
4258 >>> import astroid
4259 >>> node = astroid.extract_node('-5')
4260 >>> node
4261 <UnaryOp l.1 at 0x7f23b2e4e198>
4262 """
4263
4264 _astroid_fields = ("operand",)
4265 _other_fields = ("op",)
4266
4267 operand: NodeNG
4268 """What the unary operator is applied to."""
4269
4270 def __init__(
4271 self,
4272 op: str,
4273 lineno: int,
4274 col_offset: int,
4275 parent: NodeNG,
4276 *,
4277 end_lineno: int | None,
4278 end_col_offset: int | None,
4279 ) -> None:
4280 self.op = op
4281 """The operator."""
4282
4283 super().__init__(
4284 lineno=lineno,
4285 col_offset=col_offset,
4286 end_lineno=end_lineno,
4287 end_col_offset=end_col_offset,
4288 parent=parent,
4289 )
4290
4291 def postinit(self, operand: NodeNG) -> None:
4292 self.operand = operand
4293
4294 def type_errors(
4295 self, context: InferenceContext | None = None
4296 ) -> list[util.BadUnaryOperationMessage]:
4297 """Get a list of type errors which can occur during inference.
4298
4299 Each TypeError is represented by a :class:`BadUnaryOperationMessage`,
4300 which holds the original exception.
4301
4302 If any inferred result is uninferable, an empty list is returned.
4303 """
4304 bad = []
4305 try:
4306 for result in self._infer_unaryop(context=context):
4307 if result is util.Uninferable:
4308 raise InferenceError
4309 if isinstance(result, util.BadUnaryOperationMessage):
4310 bad.append(result)
4311 except InferenceError:
4312 return []
4313 return bad
4314
4315 def get_children(self):
4316 yield self.operand
4317
4318 def op_precedence(self) -> int:
4319 if self.op == "not":
4320 return OP_PRECEDENCE[self.op]
4321
4322 return super().op_precedence()
4323
4324 def _infer_unaryop(
4325 self, context: InferenceContext | None = None, **kwargs: Any
4326 ) -> Generator[
4327 InferenceResult | util.BadUnaryOperationMessage, None, InferenceErrorInfo
4328 ]:
4329 """Infer what an UnaryOp should return when evaluated."""
4330 from astroid.nodes import ClassDef # pylint: disable=import-outside-toplevel
4331
4332 for operand in self.operand.infer(context):
4333 try:
4334 yield operand.infer_unary_op(self.op)
4335 except TypeError as exc:
4336 # The operand doesn't support this operation.
4337 yield util.BadUnaryOperationMessage(operand, self.op, exc)
4338 except AttributeError as exc:
4339 meth = UNARY_OP_METHOD[self.op]
4340 if meth is None:
4341 # `not node`. Determine node's boolean
4342 # value and negate its result, unless it is
4343 # Uninferable, which will be returned as is.
4344 bool_value = operand.bool_value()
4345 if not isinstance(bool_value, util.UninferableBase):
4346 yield const_factory(not bool_value)
4347 else:
4348 yield util.Uninferable
4349 else:
4350 if not isinstance(operand, (Instance, ClassDef)):
4351 # The operation was used on something which
4352 # doesn't support it.
4353 yield util.BadUnaryOperationMessage(operand, self.op, exc)
4354 continue
4355
4356 try:
4357 try:
4358 methods = dunder_lookup.lookup(operand, meth)
4359 except AttributeInferenceError:
4360 yield util.BadUnaryOperationMessage(operand, self.op, exc)
4361 continue
4362
4363 meth = methods[0]
4364 inferred = next(meth.infer(context=context), None)
4365 if (
4366 isinstance(inferred, util.UninferableBase)
4367 or not inferred.callable()
4368 ):
4369 continue
4370
4371 context = copy_context(context)
4372 context.boundnode = operand
4373 context.callcontext = CallContext(args=[], callee=inferred)
4374
4375 call_results = inferred.infer_call_result(self, context=context)
4376 result = next(call_results, None)
4377 if result is None:
4378 # Failed to infer, return the same type.
4379 yield operand
4380 else:
4381 yield result
4382 except AttributeInferenceError as inner_exc:
4383 # The unary operation special method was not found.
4384 yield util.BadUnaryOperationMessage(operand, self.op, inner_exc)
4385 except InferenceError:
4386 yield util.Uninferable
4387
4388 @decorators.raise_if_nothing_inferred
4389 @decorators.path_wrapper
4390 def _infer(
4391 self, context: InferenceContext | None = None, **kwargs: Any
4392 ) -> Generator[InferenceResult, None, InferenceErrorInfo]:
4393 """Infer what an UnaryOp should return when evaluated."""
4394 yield from self._filter_operation_errors(
4395 self._infer_unaryop, context, util.BadUnaryOperationMessage
4396 )
4397 return InferenceErrorInfo(node=self, context=context)
4398
4399
4400class While(_base_nodes.MultiLineWithElseBlockNode, _base_nodes.Statement):
4401 """Class representing an :class:`ast.While` node.
4402
4403 >>> import astroid
4404 >>> node = astroid.extract_node('''
4405 while condition():
4406 print("True")
4407 ''')
4408 >>> node
4409 <While l.2 at 0x7f23b2e4e390>
4410 """
4411
4412 _astroid_fields = ("test", "body", "orelse")
4413 _multi_line_block_fields = ("body", "orelse")
4414
4415 test: NodeNG
4416 """The condition that the loop tests."""
4417
4418 body: list[NodeNG]
4419 """The contents of the loop."""
4420
4421 orelse: list[NodeNG]
4422 """The contents of the ``else`` block."""
4423
4424 def postinit(
4425 self,
4426 test: NodeNG,
4427 body: list[NodeNG],
4428 orelse: list[NodeNG],
4429 ) -> None:
4430 self.test = test
4431 self.body = body
4432 self.orelse = orelse
4433
4434 @cached_property
4435 def blockstart_tolineno(self):
4436 """The line on which the beginning of this block ends.
4437
4438 :type: int
4439 """
4440 return self.test.tolineno
4441
4442 def get_children(self):
4443 yield self.test
4444
4445 yield from self.body
4446 yield from self.orelse
4447
4448 def _get_yield_nodes_skip_functions(self):
4449 """A While node can contain a Yield node in the test"""
4450 yield from self.test._get_yield_nodes_skip_functions()
4451 yield from super()._get_yield_nodes_skip_functions()
4452
4453 def _get_yield_nodes_skip_lambdas(self):
4454 """A While node can contain a Yield node in the test"""
4455 yield from self.test._get_yield_nodes_skip_lambdas()
4456 yield from super()._get_yield_nodes_skip_lambdas()
4457
4458
4459class With(
4460 _base_nodes.MultiLineWithElseBlockNode,
4461 _base_nodes.AssignTypeNode,
4462 _base_nodes.Statement,
4463):
4464 """Class representing an :class:`ast.With` node.
4465
4466 >>> import astroid
4467 >>> node = astroid.extract_node('''
4468 with open(file_path) as file_:
4469 print(file_.read())
4470 ''')
4471 >>> node
4472 <With l.2 at 0x7f23b2e4e710>
4473 """
4474
4475 _astroid_fields = ("items", "body")
4476 _other_other_fields = ("type_annotation",)
4477 _multi_line_block_fields = ("body",)
4478
4479 def __init__(
4480 self,
4481 lineno: int | None = None,
4482 col_offset: int | None = None,
4483 parent: NodeNG | None = None,
4484 *,
4485 end_lineno: int | None = None,
4486 end_col_offset: int | None = None,
4487 ) -> None:
4488 """
4489 :param lineno: The line that this node appears on in the source code.
4490
4491 :param col_offset: The column that this node appears on in the
4492 source code.
4493
4494 :param parent: The parent node in the syntax tree.
4495
4496 :param end_lineno: The last line this node appears on in the source code.
4497
4498 :param end_col_offset: The end column this node appears on in the
4499 source code. Note: This is after the last symbol.
4500 """
4501 self.items: list[tuple[NodeNG, NodeNG | None]] = []
4502 """The pairs of context managers and the names they are assigned to."""
4503
4504 self.body: list[NodeNG] = []
4505 """The contents of the ``with`` block."""
4506
4507 self.type_annotation: NodeNG | None = None # can be None
4508 """If present, this will contain the type annotation passed by a type comment"""
4509
4510 super().__init__(
4511 lineno=lineno,
4512 col_offset=col_offset,
4513 end_lineno=end_lineno,
4514 end_col_offset=end_col_offset,
4515 parent=parent,
4516 )
4517
4518 def postinit(
4519 self,
4520 items: list[tuple[NodeNG, NodeNG | None]] | None = None,
4521 body: list[NodeNG] | None = None,
4522 type_annotation: NodeNG | None = None,
4523 ) -> None:
4524 """Do some setup after initialisation.
4525
4526 :param items: The pairs of context managers and the names
4527 they are assigned to.
4528
4529 :param body: The contents of the ``with`` block.
4530 """
4531 if items is not None:
4532 self.items = items
4533 if body is not None:
4534 self.body = body
4535 self.type_annotation = type_annotation
4536
4537 assigned_stmts = protocols.with_assigned_stmts
4538 """Returns the assigned statement (non inferred) according to the assignment type.
4539 See astroid/protocols.py for actual implementation.
4540 """
4541
4542 @cached_property
4543 def blockstart_tolineno(self):
4544 """The line on which the beginning of this block ends.
4545
4546 :type: int
4547 """
4548 return self.items[-1][0].tolineno
4549
4550 def get_children(self):
4551 """Get the child nodes below this node.
4552
4553 :returns: The children.
4554 :rtype: iterable(NodeNG)
4555 """
4556 for expr, var in self.items:
4557 yield expr
4558 if var:
4559 yield var
4560 yield from self.body
4561
4562
4563class AsyncWith(With):
4564 """Asynchronous ``with`` built with the ``async`` keyword."""
4565
4566
4567class Yield(NodeNG):
4568 """Class representing an :class:`ast.Yield` node.
4569
4570 >>> import astroid
4571 >>> node = astroid.extract_node('yield True')
4572 >>> node
4573 <Yield l.1 at 0x7f23b2e4e5f8>
4574 """
4575
4576 _astroid_fields = ("value",)
4577
4578 value: NodeNG | None
4579 """The value to yield."""
4580
4581 def postinit(self, value: NodeNG | None) -> None:
4582 self.value = value
4583
4584 def get_children(self):
4585 if self.value is not None:
4586 yield self.value
4587
4588 def _get_yield_nodes_skip_functions(self):
4589 yield self
4590
4591 def _get_yield_nodes_skip_lambdas(self):
4592 yield self
4593
4594
4595class YieldFrom(Yield): # TODO value is required, not optional
4596 """Class representing an :class:`ast.YieldFrom` node."""
4597
4598
4599class DictUnpack(_base_nodes.NoChildrenNode):
4600 """Represents the unpacking of dicts into dicts using :pep:`448`."""
4601
4602
4603class FormattedValue(NodeNG):
4604 """Class representing an :class:`ast.FormattedValue` node.
4605
4606 Represents a :pep:`498` format string.
4607
4608 >>> import astroid
4609 >>> node = astroid.extract_node('f"Format {type_}"')
4610 >>> node
4611 <JoinedStr l.1 at 0x7f23b2e4ed30>
4612 >>> node.values
4613 [<Const.str l.1 at 0x7f23b2e4eda0>, <FormattedValue l.1 at 0x7f23b2e4edd8>]
4614 """
4615
4616 _astroid_fields = ("value", "format_spec")
4617 _other_fields = ("conversion",)
4618
4619 def __init__(
4620 self,
4621 lineno: int | None = None,
4622 col_offset: int | None = None,
4623 parent: NodeNG | None = None,
4624 *,
4625 end_lineno: int | None = None,
4626 end_col_offset: int | None = None,
4627 ) -> None:
4628 """
4629 :param lineno: The line that this node appears on in the source code.
4630
4631 :param col_offset: The column that this node appears on in the
4632 source code.
4633
4634 :param parent: The parent node in the syntax tree.
4635
4636 :param end_lineno: The last line this node appears on in the source code.
4637
4638 :param end_col_offset: The end column this node appears on in the
4639 source code. Note: This is after the last symbol.
4640 """
4641 self.value: NodeNG
4642 """The value to be formatted into the string."""
4643
4644 self.conversion: int
4645 """The type of formatting to be applied to the value.
4646
4647 .. seealso::
4648 :class:`ast.FormattedValue`
4649 """
4650
4651 self.format_spec: JoinedStr | None = None
4652 """The formatting to be applied to the value.
4653
4654 .. seealso::
4655 :class:`ast.FormattedValue`
4656 """
4657
4658 super().__init__(
4659 lineno=lineno,
4660 col_offset=col_offset,
4661 end_lineno=end_lineno,
4662 end_col_offset=end_col_offset,
4663 parent=parent,
4664 )
4665
4666 def postinit(
4667 self,
4668 *,
4669 value: NodeNG,
4670 conversion: int,
4671 format_spec: JoinedStr | None = None,
4672 ) -> None:
4673 """Do some setup after initialisation.
4674
4675 :param value: The value to be formatted into the string.
4676
4677 :param conversion: The type of formatting to be applied to the value.
4678
4679 :param format_spec: The formatting to be applied to the value.
4680 :type format_spec: JoinedStr or None
4681 """
4682 self.value = value
4683 self.conversion = conversion
4684 self.format_spec = format_spec
4685
4686 def get_children(self):
4687 yield self.value
4688
4689 if self.format_spec is not None:
4690 yield self.format_spec
4691
4692 def _infer(
4693 self, context: InferenceContext | None = None, **kwargs: Any
4694 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
4695 format_specs = Const("") if self.format_spec is None else self.format_spec
4696 uninferable_already_generated = False
4697 for format_spec in format_specs.infer(context, **kwargs):
4698 if not isinstance(format_spec, Const):
4699 if not uninferable_already_generated:
4700 yield util.Uninferable
4701 uninferable_already_generated = True
4702 continue
4703 for value in self.value.infer(context, **kwargs):
4704 if value is util.Uninferable:
4705 yield util.Uninferable
4706 return
4707 value_to_format = value
4708 if isinstance(value, Const):
4709 value_to_format = value.value
4710 try:
4711 formatted = format(value_to_format, format_spec.value)
4712 yield Const(
4713 formatted,
4714 lineno=self.lineno,
4715 col_offset=self.col_offset,
4716 end_lineno=self.end_lineno,
4717 end_col_offset=self.end_col_offset,
4718 )
4719 continue
4720 except (ValueError, TypeError, MemoryError):
4721 # ValueError/TypeError: invalid format spec
4722 # MemoryError: format spec with huge width (e.g. f'{0:11111111111}')
4723 yield util.Uninferable
4724 uninferable_already_generated = True
4725 continue
4726
4727
4728UNINFERABLE_VALUE = "{Uninferable}"
4729
4730
4731class JoinedStr(NodeNG):
4732 """Represents a list of string expressions to be joined.
4733
4734 >>> import astroid
4735 >>> node = astroid.extract_node('f"Format {type_}"')
4736 >>> node
4737 <JoinedStr l.1 at 0x7f23b2e4ed30>
4738 """
4739
4740 _astroid_fields = ("values",)
4741
4742 def __init__(
4743 self,
4744 lineno: int | None = None,
4745 col_offset: int | None = None,
4746 parent: NodeNG | None = None,
4747 *,
4748 end_lineno: int | None = None,
4749 end_col_offset: int | None = None,
4750 ) -> None:
4751 """
4752 :param lineno: The line that this node appears on in the source code.
4753
4754 :param col_offset: The column that this node appears on in the
4755 source code.
4756
4757 :param parent: The parent node in the syntax tree.
4758
4759 :param end_lineno: The last line this node appears on in the source code.
4760
4761 :param end_col_offset: The end column this node appears on in the
4762 source code. Note: This is after the last symbol.
4763 """
4764 self.values: list[NodeNG] = []
4765 """The string expressions to be joined.
4766
4767 :type: list(FormattedValue or Const)
4768 """
4769
4770 super().__init__(
4771 lineno=lineno,
4772 col_offset=col_offset,
4773 end_lineno=end_lineno,
4774 end_col_offset=end_col_offset,
4775 parent=parent,
4776 )
4777
4778 def postinit(self, values: list[NodeNG] | None = None) -> None:
4779 """Do some setup after initialisation.
4780
4781 :param value: The string expressions to be joined.
4782
4783 :type: list(FormattedValue or Const)
4784 """
4785 if values is not None:
4786 self.values = values
4787
4788 def get_children(self):
4789 yield from self.values
4790
4791 def _infer(
4792 self, context: InferenceContext | None = None, **kwargs: Any
4793 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
4794 if self.values:
4795 yield from self._infer_with_values(context)
4796 else:
4797 yield Const("")
4798
4799 def _infer_with_values(
4800 self, context: InferenceContext | None = None, **kwargs: Any
4801 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
4802 uninferable_already_generated = False
4803 for inferred in self._infer_from_values(self.values, context):
4804 failed = inferred is util.Uninferable or (
4805 isinstance(inferred, Const) and UNINFERABLE_VALUE in inferred.value
4806 )
4807 if failed:
4808 if not uninferable_already_generated:
4809 uninferable_already_generated = True
4810 yield util.Uninferable
4811 continue
4812 yield inferred
4813
4814 @classmethod
4815 def _infer_from_values(
4816 cls, nodes: list[NodeNG], context: InferenceContext | None = None, **kwargs: Any
4817 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
4818 if not nodes:
4819 return
4820 if len(nodes) == 1:
4821 for node in cls._safe_infer_from_node(nodes[0], context, **kwargs):
4822 if isinstance(node, Const):
4823 yield node
4824 continue
4825 yield Const(UNINFERABLE_VALUE)
4826 return
4827 for prefix in cls._safe_infer_from_node(nodes[0], context, **kwargs):
4828 for suffix in cls._infer_from_values(nodes[1:], context, **kwargs):
4829 result = ""
4830 for node in (prefix, suffix):
4831 if isinstance(node, Const):
4832 result += str(node.value)
4833 continue
4834 result += UNINFERABLE_VALUE
4835 yield Const(result)
4836
4837 @classmethod
4838 def _safe_infer_from_node(
4839 cls, node: NodeNG, context: InferenceContext | None = None, **kwargs: Any
4840 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
4841 try:
4842 yield from node._infer(context, **kwargs)
4843 except InferenceError:
4844 yield util.Uninferable
4845
4846
4847class NamedExpr(_base_nodes.AssignTypeNode):
4848 """Represents the assignment from the assignment expression
4849
4850 >>> import astroid
4851 >>> module = astroid.parse('if a := 1: pass')
4852 >>> module.body[0].test
4853 <NamedExpr l.1 at 0x7f23b2e4ed30>
4854 """
4855
4856 _astroid_fields = ("target", "value")
4857
4858 optional_assign = True
4859 """Whether this node optionally assigns a variable.
4860
4861 Since NamedExpr are not always called they do not always assign."""
4862
4863 def __init__(
4864 self,
4865 lineno: int | None = None,
4866 col_offset: int | None = None,
4867 parent: NodeNG | None = None,
4868 *,
4869 end_lineno: int | None = None,
4870 end_col_offset: int | None = None,
4871 ) -> None:
4872 """
4873 :param lineno: The line that this node appears on in the source code.
4874
4875 :param col_offset: The column that this node appears on in the
4876 source code.
4877
4878 :param parent: The parent node in the syntax tree.
4879
4880 :param end_lineno: The last line this node appears on in the source code.
4881
4882 :param end_col_offset: The end column this node appears on in the
4883 source code. Note: This is after the last symbol.
4884 """
4885 self.target: NodeNG
4886 """The assignment target
4887
4888 :type: Name
4889 """
4890
4891 self.value: NodeNG
4892 """The value that gets assigned in the expression"""
4893
4894 super().__init__(
4895 lineno=lineno,
4896 col_offset=col_offset,
4897 end_lineno=end_lineno,
4898 end_col_offset=end_col_offset,
4899 parent=parent,
4900 )
4901
4902 def postinit(self, target: NodeNG, value: NodeNG) -> None:
4903 self.target = target
4904 self.value = value
4905
4906 assigned_stmts = protocols.named_expr_assigned_stmts
4907 """Returns the assigned statement (non inferred) according to the assignment type.
4908 See astroid/protocols.py for actual implementation.
4909 """
4910
4911 def frame(self) -> FrameType:
4912 """The first parent frame node.
4913
4914 A frame node is a :class:`Module`, :class:`FunctionDef`,
4915 or :class:`ClassDef`.
4916
4917 :returns: The first parent frame node.
4918 """
4919 if not self.parent:
4920 raise ParentMissingError(target=self)
4921
4922 # For certain parents NamedExpr evaluate to the scope of the parent
4923 if isinstance(self.parent, (Arguments, Keyword, Comprehension)):
4924 if not self.parent.parent:
4925 raise ParentMissingError(target=self.parent)
4926 if not self.parent.parent.parent:
4927 raise ParentMissingError(target=self.parent.parent)
4928 return self.parent.parent.parent.frame()
4929
4930 return self.parent.frame()
4931
4932 def scope(self) -> LocalsDictNodeNG:
4933 """The first parent node defining a new scope.
4934 These can be Module, FunctionDef, ClassDef, Lambda, or GeneratorExp nodes.
4935
4936 :returns: The first parent scope node.
4937 """
4938 if not self.parent:
4939 raise ParentMissingError(target=self)
4940
4941 # For certain parents NamedExpr evaluate to the scope of the parent
4942 if isinstance(self.parent, (Arguments, Keyword, Comprehension)):
4943 if not self.parent.parent:
4944 raise ParentMissingError(target=self.parent)
4945 if not self.parent.parent.parent:
4946 raise ParentMissingError(target=self.parent.parent)
4947 return self.parent.parent.parent.scope()
4948
4949 return self.parent.scope()
4950
4951 def set_local(self, name: str, stmt: NodeNG) -> None:
4952 """Define that the given name is declared in the given statement node.
4953 NamedExpr's in Arguments, Keyword or Comprehension are evaluated in their
4954 parent's parent scope. So we add to their frame's locals.
4955
4956 .. seealso:: :meth:`scope`
4957
4958 :param name: The name that is being defined.
4959
4960 :param stmt: The statement that defines the given name.
4961 """
4962 self.frame().set_local(name, stmt)
4963
4964
4965class Unknown(_base_nodes.AssignTypeNode):
4966 """This node represents a node in a constructed AST where
4967 introspection is not possible.
4968
4969 Used in the args attribute of FunctionDef nodes where function signature
4970 introspection failed, and as a placeholder in ObjectModel.
4971 """
4972
4973 name = "Unknown"
4974
4975 def __init__(
4976 self,
4977 parent: NodeNG,
4978 lineno: None = None,
4979 col_offset: None = None,
4980 *,
4981 end_lineno: None = None,
4982 end_col_offset: None = None,
4983 ) -> None:
4984 super().__init__(
4985 lineno=lineno,
4986 col_offset=col_offset,
4987 end_lineno=end_lineno,
4988 end_col_offset=end_col_offset,
4989 parent=parent,
4990 )
4991
4992 def qname(self) -> Literal["Unknown"]:
4993 return "Unknown"
4994
4995 def _infer(self, context: InferenceContext | None = None, **kwargs):
4996 """Inference on an Unknown node immediately terminates."""
4997 yield util.Uninferable
4998
4999
5000UNATTACHED_UNKNOWN = Unknown(parent=SYNTHETIC_ROOT)
5001
5002
5003class EvaluatedObject(NodeNG):
5004 """Contains an object that has already been inferred
5005
5006 This class is useful to pre-evaluate a particular node,
5007 with the resulting class acting as the non-evaluated node.
5008 """
5009
5010 name = "EvaluatedObject"
5011 _astroid_fields = ("original",)
5012 _other_fields = ("value",)
5013
5014 def __init__(
5015 self, original: SuccessfulInferenceResult, value: InferenceResult
5016 ) -> None:
5017 self.original: SuccessfulInferenceResult = original
5018 """The original node that has already been evaluated"""
5019
5020 self.value: InferenceResult = value
5021 """The inferred value"""
5022
5023 super().__init__(
5024 lineno=self.original.lineno,
5025 col_offset=self.original.col_offset,
5026 parent=self.original.parent,
5027 end_lineno=self.original.end_lineno,
5028 end_col_offset=self.original.end_col_offset,
5029 )
5030
5031 def _infer(
5032 self, context: InferenceContext | None = None, **kwargs: Any
5033 ) -> Generator[NodeNG | util.UninferableBase]:
5034 yield self.value
5035
5036
5037# Pattern matching #######################################################
5038
5039
5040class Match(_base_nodes.Statement, _base_nodes.MultiLineBlockNode):
5041 """Class representing a :class:`ast.Match` node.
5042
5043 >>> import astroid
5044 >>> node = astroid.extract_node('''
5045 match x:
5046 case 200:
5047 ...
5048 case _:
5049 ...
5050 ''')
5051 >>> node
5052 <Match l.2 at 0x10c24e170>
5053 """
5054
5055 _astroid_fields = ("subject", "cases")
5056 _multi_line_block_fields = ("cases",)
5057
5058 def __init__(
5059 self,
5060 lineno: int | None = None,
5061 col_offset: int | None = None,
5062 parent: NodeNG | None = None,
5063 *,
5064 end_lineno: int | None = None,
5065 end_col_offset: int | None = None,
5066 ) -> None:
5067 self.subject: NodeNG
5068 self.cases: list[MatchCase]
5069 super().__init__(
5070 lineno=lineno,
5071 col_offset=col_offset,
5072 end_lineno=end_lineno,
5073 end_col_offset=end_col_offset,
5074 parent=parent,
5075 )
5076
5077 def postinit(
5078 self,
5079 *,
5080 subject: NodeNG,
5081 cases: list[MatchCase],
5082 ) -> None:
5083 self.subject = subject
5084 self.cases = cases
5085
5086
5087class Pattern(NodeNG):
5088 """Base class for all Pattern nodes."""
5089
5090
5091class MatchCase(_base_nodes.MultiLineBlockNode):
5092 """Class representing a :class:`ast.match_case` node.
5093
5094 >>> import astroid
5095 >>> node = astroid.extract_node('''
5096 match x:
5097 case 200:
5098 ...
5099 ''')
5100 >>> node.cases[0]
5101 <MatchCase l.3 at 0x10c24e590>
5102 """
5103
5104 _astroid_fields = ("pattern", "guard", "body")
5105 _multi_line_block_fields = ("body",)
5106
5107 lineno: None
5108 col_offset: None
5109 end_lineno: None
5110 end_col_offset: None
5111
5112 def __init__(self, *, parent: NodeNG | None = None) -> None:
5113 self.pattern: Pattern
5114 self.guard: NodeNG | None
5115 self.body: list[NodeNG]
5116 super().__init__(
5117 parent=parent,
5118 lineno=None,
5119 col_offset=None,
5120 end_lineno=None,
5121 end_col_offset=None,
5122 )
5123
5124 def postinit(
5125 self,
5126 *,
5127 pattern: Pattern,
5128 guard: NodeNG | None,
5129 body: list[NodeNG],
5130 ) -> None:
5131 self.pattern = pattern
5132 self.guard = guard
5133 self.body = body
5134
5135
5136class MatchValue(Pattern):
5137 """Class representing a :class:`ast.MatchValue` node.
5138
5139 >>> import astroid
5140 >>> node = astroid.extract_node('''
5141 match x:
5142 case 200:
5143 ...
5144 ''')
5145 >>> node.cases[0].pattern
5146 <MatchValue l.3 at 0x10c24e200>
5147 """
5148
5149 _astroid_fields = ("value",)
5150
5151 def __init__(
5152 self,
5153 lineno: int | None = None,
5154 col_offset: int | None = None,
5155 parent: NodeNG | None = None,
5156 *,
5157 end_lineno: int | None = None,
5158 end_col_offset: int | None = None,
5159 ) -> None:
5160 self.value: NodeNG
5161 super().__init__(
5162 lineno=lineno,
5163 col_offset=col_offset,
5164 end_lineno=end_lineno,
5165 end_col_offset=end_col_offset,
5166 parent=parent,
5167 )
5168
5169 def postinit(self, *, value: NodeNG) -> None:
5170 self.value = value
5171
5172
5173class MatchSingleton(Pattern):
5174 """Class representing a :class:`ast.MatchSingleton` node.
5175
5176 >>> import astroid
5177 >>> node = astroid.extract_node('''
5178 match x:
5179 case True:
5180 ...
5181 case False:
5182 ...
5183 case None:
5184 ...
5185 ''')
5186 >>> node.cases[0].pattern
5187 <MatchSingleton l.3 at 0x10c2282e0>
5188 >>> node.cases[1].pattern
5189 <MatchSingleton l.5 at 0x10c228af0>
5190 >>> node.cases[2].pattern
5191 <MatchSingleton l.7 at 0x10c229f90>
5192 """
5193
5194 _other_fields = ("value",)
5195
5196 def __init__(
5197 self,
5198 *,
5199 value: Literal[True, False, None],
5200 lineno: int | None = None,
5201 col_offset: int | None = None,
5202 end_lineno: int | None = None,
5203 end_col_offset: int | None = None,
5204 parent: NodeNG | None = None,
5205 ) -> None:
5206 self.value = value
5207 super().__init__(
5208 lineno=lineno,
5209 col_offset=col_offset,
5210 end_lineno=end_lineno,
5211 end_col_offset=end_col_offset,
5212 parent=parent,
5213 )
5214
5215
5216class MatchSequence(Pattern):
5217 """Class representing a :class:`ast.MatchSequence` node.
5218
5219 >>> import astroid
5220 >>> node = astroid.extract_node('''
5221 match x:
5222 case [1, 2]:
5223 ...
5224 case (1, 2, *_):
5225 ...
5226 ''')
5227 >>> node.cases[0].pattern
5228 <MatchSequence l.3 at 0x10ca80d00>
5229 >>> node.cases[1].pattern
5230 <MatchSequence l.5 at 0x10ca80b20>
5231 """
5232
5233 _astroid_fields = ("patterns",)
5234
5235 def __init__(
5236 self,
5237 lineno: int | None = None,
5238 col_offset: int | None = None,
5239 parent: NodeNG | None = None,
5240 *,
5241 end_lineno: int | None = None,
5242 end_col_offset: int | None = None,
5243 ) -> None:
5244 self.patterns: list[Pattern]
5245 super().__init__(
5246 lineno=lineno,
5247 col_offset=col_offset,
5248 end_lineno=end_lineno,
5249 end_col_offset=end_col_offset,
5250 parent=parent,
5251 )
5252
5253 def postinit(self, *, patterns: list[Pattern]) -> None:
5254 self.patterns = patterns
5255
5256
5257class MatchMapping(_base_nodes.AssignTypeNode, Pattern):
5258 """Class representing a :class:`ast.MatchMapping` node.
5259
5260 >>> import astroid
5261 >>> node = astroid.extract_node('''
5262 match x:
5263 case {1: "Hello", 2: "World", 3: _, **rest}:
5264 ...
5265 ''')
5266 >>> node.cases[0].pattern
5267 <MatchMapping l.3 at 0x10c8a8850>
5268 """
5269
5270 _astroid_fields = ("keys", "patterns", "rest")
5271
5272 def __init__(
5273 self,
5274 lineno: int | None = None,
5275 col_offset: int | None = None,
5276 parent: NodeNG | None = None,
5277 *,
5278 end_lineno: int | None = None,
5279 end_col_offset: int | None = None,
5280 ) -> None:
5281 self.keys: list[NodeNG]
5282 self.patterns: list[Pattern]
5283 self.rest: AssignName | None
5284 super().__init__(
5285 lineno=lineno,
5286 col_offset=col_offset,
5287 end_lineno=end_lineno,
5288 end_col_offset=end_col_offset,
5289 parent=parent,
5290 )
5291
5292 def postinit(
5293 self,
5294 *,
5295 keys: list[NodeNG],
5296 patterns: list[Pattern],
5297 rest: AssignName | None,
5298 ) -> None:
5299 self.keys = keys
5300 self.patterns = patterns
5301 self.rest = rest
5302
5303 assigned_stmts = protocols.match_mapping_assigned_stmts
5304 """Returns the assigned statement (non inferred) according to the assignment type.
5305 See astroid/protocols.py for actual implementation.
5306 """
5307
5308
5309class MatchClass(Pattern):
5310 """Class representing a :class:`ast.MatchClass` node.
5311
5312 >>> import astroid
5313 >>> node = astroid.extract_node('''
5314 match x:
5315 case Point2D(0, 0):
5316 ...
5317 case Point3D(x=0, y=0, z=0):
5318 ...
5319 ''')
5320 >>> node.cases[0].pattern
5321 <MatchClass l.3 at 0x10ca83940>
5322 >>> node.cases[1].pattern
5323 <MatchClass l.5 at 0x10ca80880>
5324 """
5325
5326 _astroid_fields = ("cls", "patterns", "kwd_patterns")
5327 _other_fields = ("kwd_attrs",)
5328
5329 def __init__(
5330 self,
5331 lineno: int | None = None,
5332 col_offset: int | None = None,
5333 parent: NodeNG | None = None,
5334 *,
5335 end_lineno: int | None = None,
5336 end_col_offset: int | None = None,
5337 ) -> None:
5338 self.cls: NodeNG
5339 self.patterns: list[Pattern]
5340 self.kwd_attrs: list[str]
5341 self.kwd_patterns: list[Pattern]
5342 super().__init__(
5343 lineno=lineno,
5344 col_offset=col_offset,
5345 end_lineno=end_lineno,
5346 end_col_offset=end_col_offset,
5347 parent=parent,
5348 )
5349
5350 def postinit(
5351 self,
5352 *,
5353 cls: NodeNG,
5354 patterns: list[Pattern],
5355 kwd_attrs: list[str],
5356 kwd_patterns: list[Pattern],
5357 ) -> None:
5358 self.cls = cls
5359 self.patterns = patterns
5360 self.kwd_attrs = kwd_attrs
5361 self.kwd_patterns = kwd_patterns
5362
5363
5364class MatchStar(_base_nodes.AssignTypeNode, Pattern):
5365 """Class representing a :class:`ast.MatchStar` node.
5366
5367 >>> import astroid
5368 >>> node = astroid.extract_node('''
5369 match x:
5370 case [1, *_]:
5371 ...
5372 ''')
5373 >>> node.cases[0].pattern.patterns[1]
5374 <MatchStar l.3 at 0x10ca809a0>
5375 """
5376
5377 _astroid_fields = ("name",)
5378
5379 def __init__(
5380 self,
5381 lineno: int | None = None,
5382 col_offset: int | None = None,
5383 parent: NodeNG | None = None,
5384 *,
5385 end_lineno: int | None = None,
5386 end_col_offset: int | None = None,
5387 ) -> None:
5388 self.name: AssignName | None
5389 super().__init__(
5390 lineno=lineno,
5391 col_offset=col_offset,
5392 end_lineno=end_lineno,
5393 end_col_offset=end_col_offset,
5394 parent=parent,
5395 )
5396
5397 def postinit(self, *, name: AssignName | None) -> None:
5398 self.name = name
5399
5400 assigned_stmts = protocols.match_star_assigned_stmts
5401 """Returns the assigned statement (non inferred) according to the assignment type.
5402 See astroid/protocols.py for actual implementation.
5403 """
5404
5405
5406class MatchAs(_base_nodes.AssignTypeNode, Pattern):
5407 """Class representing a :class:`ast.MatchAs` node.
5408
5409 >>> import astroid
5410 >>> node = astroid.extract_node('''
5411 match x:
5412 case [1, a]:
5413 ...
5414 case {'key': b}:
5415 ...
5416 case Point2D(0, 0) as c:
5417 ...
5418 case d:
5419 ...
5420 ''')
5421 >>> node.cases[0].pattern.patterns[1]
5422 <MatchAs l.3 at 0x10d0b2da0>
5423 >>> node.cases[1].pattern.patterns[0]
5424 <MatchAs l.5 at 0x10d0b2920>
5425 >>> node.cases[2].pattern
5426 <MatchAs l.7 at 0x10d0b06a0>
5427 >>> node.cases[3].pattern
5428 <MatchAs l.9 at 0x10d09b880>
5429 """
5430
5431 _astroid_fields = ("pattern", "name")
5432
5433 def __init__(
5434 self,
5435 lineno: int | None = None,
5436 col_offset: int | None = None,
5437 parent: NodeNG | None = None,
5438 *,
5439 end_lineno: int | None = None,
5440 end_col_offset: int | None = None,
5441 ) -> None:
5442 self.pattern: Pattern | None
5443 self.name: AssignName | None
5444 super().__init__(
5445 lineno=lineno,
5446 col_offset=col_offset,
5447 end_lineno=end_lineno,
5448 end_col_offset=end_col_offset,
5449 parent=parent,
5450 )
5451
5452 def postinit(
5453 self,
5454 *,
5455 pattern: Pattern | None,
5456 name: AssignName | None,
5457 ) -> None:
5458 self.pattern = pattern
5459 self.name = name
5460
5461 assigned_stmts = protocols.match_as_assigned_stmts
5462 """Returns the assigned statement (non inferred) according to the assignment type.
5463 See astroid/protocols.py for actual implementation.
5464 """
5465
5466
5467class MatchOr(Pattern):
5468 """Class representing a :class:`ast.MatchOr` node.
5469
5470 >>> import astroid
5471 >>> node = astroid.extract_node('''
5472 match x:
5473 case 400 | 401 | 402:
5474 ...
5475 ''')
5476 >>> node.cases[0].pattern
5477 <MatchOr l.3 at 0x10d0b0b50>
5478 """
5479
5480 _astroid_fields = ("patterns",)
5481
5482 def __init__(
5483 self,
5484 lineno: int | None = None,
5485 col_offset: int | None = None,
5486 parent: NodeNG | None = None,
5487 *,
5488 end_lineno: int | None = None,
5489 end_col_offset: int | None = None,
5490 ) -> None:
5491 self.patterns: list[Pattern]
5492 super().__init__(
5493 lineno=lineno,
5494 col_offset=col_offset,
5495 end_lineno=end_lineno,
5496 end_col_offset=end_col_offset,
5497 parent=parent,
5498 )
5499
5500 def postinit(self, *, patterns: list[Pattern]) -> None:
5501 self.patterns = patterns
5502
5503
5504class TemplateStr(NodeNG):
5505 """Class representing an :class:`ast.TemplateStr` node.
5506
5507 >>> import astroid
5508 >>> node = astroid.extract_node('t"{name} finished {place!s}"')
5509 >>> node
5510 <TemplateStr l.1 at 0x103b7aa50>
5511 """
5512
5513 _astroid_fields = ("values",)
5514
5515 def __init__(
5516 self,
5517 lineno: int | None = None,
5518 col_offset: int | None = None,
5519 parent: NodeNG | None = None,
5520 *,
5521 end_lineno: int | None = None,
5522 end_col_offset: int | None = None,
5523 ) -> None:
5524 self.values: list[NodeNG]
5525 super().__init__(
5526 lineno=lineno,
5527 col_offset=col_offset,
5528 end_lineno=end_lineno,
5529 end_col_offset=end_col_offset,
5530 parent=parent,
5531 )
5532
5533 def postinit(self, *, values: list[NodeNG]) -> None:
5534 self.values = values
5535
5536 def get_children(self) -> Iterator[NodeNG]:
5537 yield from self.values
5538
5539
5540class Interpolation(NodeNG):
5541 """Class representing an :class:`ast.Interpolation` node.
5542
5543 >>> import astroid
5544 >>> node = astroid.extract_node('t"{name} finished {place!s}"')
5545 >>> node
5546 <TemplateStr l.1 at 0x103b7aa50>
5547 >>> node.values[0]
5548 <Interpolation l.1 at 0x103b7acf0>
5549 >>> node.values[2]
5550 <Interpolation l.1 at 0x10411e5d0>
5551 """
5552
5553 _astroid_fields = ("value", "format_spec")
5554 _other_fields = ("str", "conversion")
5555
5556 def __init__(
5557 self,
5558 lineno: int | None = None,
5559 col_offset: int | None = None,
5560 parent: NodeNG | None = None,
5561 *,
5562 end_lineno: int | None = None,
5563 end_col_offset: int | None = None,
5564 ) -> None:
5565 self.value: NodeNG
5566 """Any expression node."""
5567
5568 self.str: str
5569 """Text of the interpolation expression."""
5570
5571 self.conversion: int
5572 """The type of formatting to be applied to the value.
5573
5574 .. seealso::
5575 :class:`ast.Interpolation`
5576 """
5577
5578 self.format_spec: JoinedStr | None = None
5579 """The formatting to be applied to the value.
5580
5581 .. seealso::
5582 :class:`ast.Interpolation`
5583 """
5584
5585 super().__init__(
5586 lineno=lineno,
5587 col_offset=col_offset,
5588 end_lineno=end_lineno,
5589 end_col_offset=end_col_offset,
5590 parent=parent,
5591 )
5592
5593 def postinit(
5594 self,
5595 *,
5596 value: NodeNG,
5597 str: str, # pylint: disable=redefined-builtin
5598 conversion: int = -1,
5599 format_spec: JoinedStr | None = None,
5600 ) -> None:
5601 self.value = value
5602 self.str = str
5603 self.conversion = conversion
5604 self.format_spec = format_spec
5605
5606 def get_children(self) -> Iterator[NodeNG]:
5607 yield self.value
5608 if self.format_spec:
5609 yield self.format_spec
5610
5611
5612# constants ##############################################################
5613
5614# The _proxied attribute of all container types (List, Tuple, etc.)
5615# are set during bootstrapping by _astroid_bootstrapping().
5616CONST_CLS: dict[type, type[NodeNG]] = {
5617 list: List,
5618 tuple: Tuple,
5619 dict: Dict,
5620 set: Set,
5621 type(None): Const,
5622 type(NotImplemented): Const,
5623 type(...): Const,
5624 bool: Const,
5625 int: Const,
5626 float: Const,
5627 complex: Const,
5628 str: Const,
5629 bytes: Const,
5630}
5631
5632
5633def _create_basic_elements(
5634 value: Iterable[Any], node: List | Set | Tuple
5635) -> list[NodeNG]:
5636 """Create a list of nodes to function as the elements of a new node."""
5637 elements: list[NodeNG] = []
5638 for element in value:
5639 # NOTE: avoid accessing any attributes of element in the loop.
5640 element_node = const_factory(element)
5641 element_node.parent = node
5642 elements.append(element_node)
5643 return elements
5644
5645
5646def _create_dict_items(
5647 values: Mapping[Any, Any], node: Dict
5648) -> list[tuple[SuccessfulInferenceResult, SuccessfulInferenceResult]]:
5649 """Create a list of node pairs to function as the items of a new dict node."""
5650 elements: list[tuple[SuccessfulInferenceResult, SuccessfulInferenceResult]] = []
5651 for key, value in values.items():
5652 # NOTE: avoid accessing any attributes of both key and value in the loop.
5653 key_node = const_factory(key)
5654 key_node.parent = node
5655 value_node = const_factory(value)
5656 value_node.parent = node
5657 elements.append((key_node, value_node))
5658 return elements
5659
5660
5661def const_factory(value: Any) -> ConstFactoryResult:
5662 """Return an astroid node for a python value."""
5663 # NOTE: avoid accessing any attributes of value until it is known that value
5664 # is of a const type, to avoid possibly triggering code for a live object.
5665 # Accesses include value.__class__ and isinstance(value, ...), but not type(value).
5666 # See: https://github.com/pylint-dev/astroid/issues/2686
5667 value_type = type(value)
5668 assert not issubclass(value_type, NodeNG)
5669
5670 # This only handles instances of the CONST types. Any
5671 # subclasses get inferred as EmptyNode.
5672 # TODO: See if we should revisit these with the normal builder.
5673 if value_type not in CONST_CLS:
5674 node = EmptyNode()
5675 node.object = value
5676 return node
5677
5678 instance: List | Set | Tuple | Dict
5679 initializer_cls = CONST_CLS[value_type]
5680 if issubclass(initializer_cls, (List, Set, Tuple)):
5681 instance = initializer_cls(
5682 lineno=None,
5683 col_offset=None,
5684 parent=SYNTHETIC_ROOT,
5685 end_lineno=None,
5686 end_col_offset=None,
5687 )
5688 instance.postinit(_create_basic_elements(value, instance))
5689 return instance
5690 if issubclass(initializer_cls, Dict):
5691 instance = initializer_cls(
5692 lineno=None,
5693 col_offset=None,
5694 parent=SYNTHETIC_ROOT,
5695 end_lineno=None,
5696 end_col_offset=None,
5697 )
5698 instance.postinit(_create_dict_items(value, instance))
5699 return instance
5700 return Const(value)