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