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