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