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
5from __future__ import annotations
6
7import sys
8from collections.abc import Generator, Iterator
9from functools import cached_property
10from functools import singledispatch as _singledispatch
11from typing import (
12 TYPE_CHECKING,
13 ClassVar,
14 TypeVar,
15 cast,
16 overload,
17)
18
19from astroid import nodes, util
20from astroid.context import InferenceContext
21from astroid.exceptions import (
22 AstroidError,
23 InferenceError,
24 ParentMissingError,
25 StatementMissing,
26 UseInferenceDefault,
27)
28from astroid.manager import AstroidManager
29from astroid.nodes.as_string import AsStringVisitor
30from astroid.nodes.const import OP_PRECEDENCE
31from astroid.nodes.utils import Position
32from astroid.typing import InferenceErrorInfo, InferenceResult, InferFn
33
34if sys.version_info >= (3, 11):
35 from typing import Self
36else:
37 from typing_extensions import Self
38
39
40if TYPE_CHECKING:
41 from astroid.nodes import _base_nodes
42
43 FrameType = nodes.FunctionDef | nodes.Module | nodes.ClassDef | nodes.Lambda
44
45
46# Types for 'NodeNG.nodes_of_class()'
47_NodesT = TypeVar("_NodesT", bound="NodeNG")
48_NodesT2 = TypeVar("_NodesT2", bound="NodeNG")
49_NodesT3 = TypeVar("_NodesT3", bound="NodeNG")
50SkipKlassT = None | type["NodeNG"] | tuple[type["NodeNG"], ...]
51
52
53class NodeNG:
54 """A node of the new Abstract Syntax Tree (AST).
55
56 This is the base class for all Astroid node classes.
57 """
58
59 is_statement: ClassVar[bool] = False
60 """Whether this node indicates a statement."""
61 optional_assign: ClassVar[bool] = False # True for For
62 """Whether this node optionally assigns a variable.
63
64 This is for loop assignments because loop won't necessarily perform an
65 assignment if the loop has no iterations.
66 """
67 is_function: ClassVar[bool] = False # True for FunctionDef nodes
68 """Whether this node indicates a function."""
69 is_lambda: ClassVar[bool] = False
70
71 # Attributes below are set by the builder module or by raw factories
72 _astroid_fields: ClassVar[tuple[str, ...]] = ()
73 """Node attributes that contain child nodes.
74
75 This is redefined in most concrete classes.
76 """
77 _other_fields: ClassVar[tuple[str, ...]] = ()
78 """Node attributes that do not contain child nodes."""
79 _other_other_fields: ClassVar[tuple[str, ...]] = ()
80 """Attributes that contain AST-dependent fields."""
81 # instance specific inference function infer(node, context)
82 _explicit_inference: InferFn[Self] | None = None
83
84 def __init__(
85 self,
86 lineno: int | None,
87 col_offset: int | None,
88 parent: NodeNG | None,
89 *,
90 end_lineno: int | None,
91 end_col_offset: int | None,
92 ) -> None:
93 self.lineno = lineno
94 """The line that this node appears on in the source code."""
95
96 self.col_offset = col_offset
97 """The column that this node appears on in the source code."""
98
99 self.parent = parent
100 """The parent node in the syntax tree."""
101
102 self.end_lineno = end_lineno
103 """The last line this node appears on in the source code."""
104
105 self.end_col_offset = end_col_offset
106 """The end column this node appears on in the source code.
107
108 Note: This is after the last symbol.
109 """
110
111 self.position: Position | None = None
112 """Position of keyword(s) and name.
113
114 Used as fallback for block nodes which might not provide good
115 enough positional information. E.g. ClassDef, FunctionDef.
116 """
117
118 def infer(
119 self, context: InferenceContext | None = None
120 ) -> Generator[InferenceResult]:
121 """Get a generator of the inferred values.
122
123 This is the main entry point to the inference system.
124
125 .. seealso:: :ref:`inference`
126
127 If the instance has some explicit inference function set, it will be
128 called instead of the default interface.
129
130 :returns: The inferred values.
131 :rtype: Iterator
132 """
133 if context is None:
134 context = InferenceContext()
135 else:
136 context = context.extra_context.get(self, context)
137 if self._explicit_inference is not None:
138 # explicit_inference is not bound, give it self explicitly
139 try:
140 for result in self._explicit_inference(
141 self, # type: ignore[arg-type]
142 context,
143 ):
144 context.nodes_inferred += 1
145 yield result
146 return
147 except UseInferenceDefault:
148 pass
149
150 key = (self, context.lookupname, context.callcontext, context.boundnode)
151 if key in context.inferred:
152 yield from context.inferred[key]
153 return
154
155 results = []
156
157 # Limit inference amount to help with performance issues with
158 # exponentially exploding possible results.
159 limit = AstroidManager().max_inferable_values
160 for i, result in enumerate(self._infer(context=context)):
161 if i >= limit or (context.nodes_inferred > context.max_inferred):
162 results.append(util.Uninferable)
163 yield util.Uninferable
164 break
165 results.append(result)
166 yield result
167 context.nodes_inferred += 1
168
169 # Cache generated results for subsequent inferences of the
170 # same node using the same context
171 context.inferred[key] = tuple(results)
172 return
173
174 def repr_name(self) -> str:
175 """Get a name for nice representation.
176
177 This is either ``name``, ``attrname``, or the empty string.
178 """
179 if all(name not in self._astroid_fields for name in ("name", "attrname")):
180 return getattr(self, "name", "") or getattr(self, "attrname", "")
181 return ""
182
183 def __str__(self) -> str:
184 import pprint # pylint: disable=import-outside-toplevel
185
186 rname = self.repr_name()
187 cname = type(self).__name__
188 if rname:
189 string = "%(cname)s.%(rname)s(%(fields)s)"
190 alignment = len(cname) + len(rname) + 2
191 else:
192 string = "%(cname)s(%(fields)s)"
193 alignment = len(cname) + 1
194 result = []
195 for field in self._other_fields + self._astroid_fields:
196 value = getattr(self, field, "Unknown")
197 width = max(80 - len(field) - alignment, 1)
198 try:
199 lines = pprint.pformat(value, indent=2, width=width).splitlines(True)
200 except ValueError:
201 lines = [f"<{type(value).__name__}>"]
202
203 inner = [lines[0]]
204 for line in lines[1:]:
205 inner.append(" " * alignment + line)
206 result.append(f"{field}={''.join(inner)}")
207
208 return string % {
209 "cname": cname,
210 "rname": rname,
211 "fields": (",\n" + " " * alignment).join(result),
212 }
213
214 def __repr__(self) -> str:
215 rname = self.repr_name()
216 # The dependencies used to calculate fromlineno (if not cached) may not exist at the time
217 try:
218 lineno = self.fromlineno
219 except AttributeError:
220 lineno = 0
221 if rname:
222 string = "<%(cname)s.%(rname)s l.%(lineno)s at 0x%(id)x>"
223 else:
224 string = "<%(cname)s l.%(lineno)s at 0x%(id)x>"
225 return string % {
226 "cname": type(self).__name__,
227 "rname": rname,
228 "lineno": lineno,
229 "id": id(self),
230 }
231
232 def accept(self, visitor: AsStringVisitor) -> str:
233 """Visit this node using the given visitor."""
234 func = getattr(visitor, "visit_" + self.__class__.__name__.lower())
235 return func(self)
236
237 def get_children(self) -> Iterator[NodeNG]:
238 """Get the child nodes below this node."""
239 for field in self._astroid_fields:
240 attr = getattr(self, field)
241 if attr is None:
242 continue
243 if isinstance(attr, (list, tuple)):
244 yield from attr
245 else:
246 yield attr
247 yield from ()
248
249 def last_child(self) -> NodeNG | None:
250 """An optimized version of list(get_children())[-1]."""
251 for field in self._astroid_fields[::-1]:
252 attr = getattr(self, field)
253 if not attr: # None or empty list / tuple
254 continue
255 if isinstance(attr, (list, tuple)):
256 return attr[-1]
257 return attr
258 return None
259
260 def node_ancestors(self) -> Iterator[NodeNG]:
261 """Yield parent, grandparent, etc until there are no more."""
262 parent = self.parent
263 while parent is not None:
264 yield parent
265 parent = parent.parent
266
267 def parent_of(self, node) -> bool:
268 """Check if this node is the parent of the given node.
269
270 :param node: The node to check if it is the child.
271 :type node: NodeNG
272
273 :returns: Whether this node is the parent of the given node.
274 """
275 return any(self is parent for parent in node.node_ancestors())
276
277 def statement(self) -> _base_nodes.Statement:
278 """The first parent node, including self, marked as statement node.
279
280 :raises StatementMissing: If self has no parent attribute.
281 """
282 if self.is_statement:
283 return cast("_base_nodes.Statement", self)
284 if not self.parent:
285 raise StatementMissing(target=self)
286 return self.parent.statement()
287
288 def frame(self) -> FrameType:
289 """The first parent frame node.
290
291 A frame node is a :class:`Module`, :class:`FunctionDef`,
292 :class:`ClassDef` or :class:`Lambda`.
293
294 :returns: The first parent frame node.
295 :raises ParentMissingError: If self has no parent attribute.
296 """
297 if self.parent is None:
298 raise ParentMissingError(target=self)
299 return self.parent.frame()
300
301 def scope(self) -> nodes.LocalsDictNodeNG:
302 """The first parent node defining a new scope.
303
304 These can be Module, FunctionDef, ClassDef, Lambda, or GeneratorExp nodes.
305
306 :returns: The first parent scope node.
307 """
308 if not self.parent:
309 raise ParentMissingError(target=self)
310 return self.parent.scope()
311
312 def root(self) -> nodes.Module:
313 """Return the root node of the syntax tree.
314
315 :returns: The root node.
316 """
317 if not (parent := self.parent):
318 assert isinstance(self, nodes.Module)
319 return self
320
321 while parent.parent:
322 parent = parent.parent
323 assert isinstance(parent, nodes.Module)
324 return parent
325
326 def child_sequence(self, child):
327 """Search for the sequence that contains this child.
328
329 :param child: The child node to search sequences for.
330 :type child: NodeNG
331
332 :returns: The sequence containing the given child node.
333 :rtype: Iterator[NodeNG]
334
335 :raises AstroidError: If no sequence could be found that contains
336 the given child.
337 """
338 for field in self._astroid_fields:
339 node_or_sequence = getattr(self, field)
340 if node_or_sequence is child:
341 return [node_or_sequence]
342 # /!\ compiler.ast Nodes have an __iter__ walking over child nodes
343 if (
344 isinstance(node_or_sequence, (tuple, list))
345 and child in node_or_sequence
346 ):
347 return node_or_sequence
348
349 msg = "Could not find %s in %s's children"
350 raise AstroidError(msg % (repr(child), repr(self)))
351
352 def locate_child(self, child):
353 """Find the field of this node that contains the given child.
354
355 :param child: The child node to search fields for.
356 :type child: NodeNG
357
358 :returns: A tuple of the name of the field that contains the child,
359 and the sequence or node that contains the child node.
360 :rtype: tuple[str, Iterator[NodeNG] or NodeNG]
361
362 :raises AstroidError: If no field could be found that contains
363 the given child.
364 """
365 for field in self._astroid_fields:
366 node_or_sequence = getattr(self, field)
367 # /!\ compiler.ast Nodes have an __iter__ walking over child nodes
368 if child is node_or_sequence:
369 return field, child
370 if (
371 isinstance(node_or_sequence, (tuple, list))
372 and child in node_or_sequence
373 ):
374 return field, node_or_sequence
375 msg = "Could not find %s in %s's children"
376 raise AstroidError(msg % (repr(child), repr(self)))
377
378 # FIXME : should we merge child_sequence and locate_child ? locate_child
379 # is only used in are_exclusive, child_sequence one time in pylint.
380
381 def next_sibling(self):
382 """The next sibling statement node.
383
384 :returns: The next sibling statement node.
385 :rtype: NodeNG or None
386 """
387 return self.parent.next_sibling()
388
389 def previous_sibling(self):
390 """The previous sibling statement.
391
392 :returns: The previous sibling statement node.
393 :rtype: NodeNG or None
394 """
395 return self.parent.previous_sibling()
396
397 # these are lazy because they're relatively expensive to compute for every
398 # single node, and they rarely get looked at
399
400 @cached_property
401 def fromlineno(self) -> int:
402 """The first line that this node appears on in the source code.
403
404 Can also return 0 if the line can not be determined.
405 """
406 if self.lineno is None:
407 return self._fixed_source_line()
408 return self.lineno
409
410 @cached_property
411 def tolineno(self) -> int:
412 """The last line that this node appears on in the source code.
413
414 Can also return 0 if the line can not be determined.
415 """
416 if self.end_lineno is not None:
417 return self.end_lineno
418 if not self._astroid_fields:
419 # can't have children
420 last_child = None
421 else:
422 last_child = self.last_child()
423 if last_child is None:
424 return self.fromlineno
425 return last_child.tolineno
426
427 def _fixed_source_line(self) -> int:
428 """Attempt to find the line that this node appears on.
429
430 We need this method since not all nodes have :attr:`lineno` set.
431 Will return 0 if the line number can not be determined.
432 """
433 line = self.lineno
434 _node = self
435 try:
436 while line is None:
437 _node = next(_node.get_children())
438 line = _node.lineno
439 except StopIteration:
440 parent = self.parent
441 while parent and line is None:
442 line = parent.lineno
443 parent = parent.parent
444 return line or 0
445
446 def block_range(self, lineno: int) -> tuple[int, int]:
447 """Get a range from the given line number to where this node ends.
448
449 :param lineno: The line number to start the range at.
450
451 :returns: The range of line numbers that this node belongs to,
452 starting at the given line number.
453 """
454 return lineno, self.tolineno
455
456 def set_local(self, name: str, stmt: NodeNG) -> None:
457 """Define that the given name is declared in the given statement node.
458
459 This definition is stored on the parent scope node.
460
461 .. seealso:: :meth:`scope`
462
463 :param name: The name that is being defined.
464
465 :param stmt: The statement that defines the given name.
466 """
467 assert self.parent
468 self.parent.set_local(name, stmt)
469
470 @overload
471 def nodes_of_class(
472 self,
473 klass: type[_NodesT],
474 skip_klass: SkipKlassT = ...,
475 ) -> Iterator[_NodesT]: ...
476
477 @overload
478 def nodes_of_class(
479 self,
480 klass: tuple[type[_NodesT], type[_NodesT2]],
481 skip_klass: SkipKlassT = ...,
482 ) -> Iterator[_NodesT] | Iterator[_NodesT2]: ...
483
484 @overload
485 def nodes_of_class(
486 self,
487 klass: tuple[type[_NodesT], type[_NodesT2], type[_NodesT3]],
488 skip_klass: SkipKlassT = ...,
489 ) -> Iterator[_NodesT] | Iterator[_NodesT2] | Iterator[_NodesT3]: ...
490
491 @overload
492 def nodes_of_class(
493 self,
494 klass: tuple[type[_NodesT], ...],
495 skip_klass: SkipKlassT = ...,
496 ) -> Iterator[_NodesT]: ...
497
498 def nodes_of_class( # type: ignore[misc] # mypy doesn't correctly recognize the overloads
499 self,
500 klass: (
501 type[_NodesT]
502 | tuple[type[_NodesT], type[_NodesT2]]
503 | tuple[type[_NodesT], type[_NodesT2], type[_NodesT3]]
504 | tuple[type[_NodesT], ...]
505 ),
506 skip_klass: SkipKlassT = None,
507 ) -> Iterator[_NodesT] | Iterator[_NodesT2] | Iterator[_NodesT3]:
508 """Get the nodes (including this one or below) of the given types.
509
510 :param klass: The types of node to search for.
511
512 :param skip_klass: The types of node to ignore. This is useful to ignore
513 subclasses of ``klass``.
514
515 :returns: The node of the given types.
516 """
517 if isinstance(self, klass):
518 yield self
519
520 if skip_klass is None:
521 for child_node in self.get_children():
522 yield from child_node.nodes_of_class(klass, skip_klass)
523
524 return
525
526 for child_node in self.get_children():
527 if isinstance(child_node, skip_klass):
528 continue
529 yield from child_node.nodes_of_class(klass, skip_klass)
530
531 @cached_property
532 def _assign_nodes_in_scope(self) -> list[nodes.Assign]:
533 return []
534
535 def _get_name_nodes(self):
536 for child_node in self.get_children():
537 yield from child_node._get_name_nodes()
538
539 def _get_return_nodes_skip_functions(self):
540 yield from ()
541
542 def _get_yield_nodes_skip_functions(self):
543 yield from ()
544
545 def _get_yield_nodes_skip_lambdas(self):
546 yield from ()
547
548 def _infer_name(self, frame, name):
549 # overridden for ImportFrom, Import, Global, Try, TryStar and Arguments
550 pass
551
552 def _infer(
553 self, context: InferenceContext | None = None
554 ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
555 """We don't know how to resolve a statement by default."""
556 # this method is overridden by most concrete classes
557 raise InferenceError(
558 "No inference function for {node!r}.", node=self, context=context
559 )
560
561 def inferred(self):
562 """Get a list of the inferred values.
563
564 .. seealso:: :ref:`inference`
565
566 :returns: The inferred values.
567 :rtype: list
568 """
569 return list(self.infer())
570
571 def instantiate_class(self):
572 """Instantiate an instance of the defined class.
573
574 .. note::
575
576 On anything other than a :class:`ClassDef` this will return self.
577
578 :returns: An instance of the defined class.
579 :rtype: object
580 """
581 return self
582
583 def has_base(self, node) -> bool:
584 """Check if this node inherits from the given type.
585
586 :param node: The node defining the base to look for.
587 Usually this is a :class:`Name` node.
588 :type node: NodeNG
589 """
590 return False
591
592 def callable(self) -> bool:
593 """Whether this node defines something that is callable.
594
595 :returns: Whether this defines something that is callable.
596 """
597 return False
598
599 def eq(self, value) -> bool:
600 return False
601
602 def as_string(self) -> str:
603 """Get the source code that this node represents."""
604 return AsStringVisitor()(self)
605
606 def repr_tree(
607 self,
608 ids=False,
609 include_linenos=False,
610 ast_state=False,
611 indent=" ",
612 max_depth=0,
613 max_width=80,
614 ) -> str:
615 """Get a string representation of the AST from this node.
616
617 :param ids: If true, includes the ids with the node type names.
618 :type ids: bool
619
620 :param include_linenos: If true, includes the line numbers and
621 column offsets.
622 :type include_linenos: bool
623
624 :param ast_state: If true, includes information derived from
625 the whole AST like local and global variables.
626 :type ast_state: bool
627
628 :param indent: A string to use to indent the output string.
629 :type indent: str
630
631 :param max_depth: If set to a positive integer, won't return
632 nodes deeper than max_depth in the string.
633 :type max_depth: int
634
635 :param max_width: Attempt to format the output string to stay
636 within this number of characters, but can exceed it under some
637 circumstances. Only positive integer values are valid, the default is 80.
638 :type max_width: int
639
640 :returns: The string representation of the AST.
641 :rtype: str
642 """
643
644 # pylint: disable = too-many-statements
645 import pprint # pylint: disable=import-outside-toplevel
646
647 @_singledispatch
648 def _repr_tree(node, result, done, cur_indent="", depth=1):
649 """Outputs a representation of a non-tuple/list, non-node that's
650 contained within an AST, including strings.
651 """
652 lines = pprint.pformat(
653 node, width=max(max_width - len(cur_indent), 1)
654 ).splitlines(True)
655 result.append(lines[0])
656 result.extend([cur_indent + line for line in lines[1:]])
657 return len(lines) != 1
658
659 # pylint: disable=unused-variable,useless-suppression; doesn't understand singledispatch
660 @_repr_tree.register(tuple)
661 @_repr_tree.register(list)
662 def _repr_seq(node, result, done, cur_indent="", depth=1):
663 """Outputs a representation of a sequence that's contained within an
664 AST.
665 """
666 cur_indent += indent
667 result.append("[")
668 if not node:
669 broken = False
670 elif len(node) == 1:
671 broken = _repr_tree(node[0], result, done, cur_indent, depth)
672 elif len(node) == 2:
673 broken = _repr_tree(node[0], result, done, cur_indent, depth)
674 if not broken:
675 result.append(", ")
676 else:
677 result.append(",\n")
678 result.append(cur_indent)
679 broken = _repr_tree(node[1], result, done, cur_indent, depth) or broken
680 else:
681 result.append("\n")
682 result.append(cur_indent)
683 for child in node[:-1]:
684 _repr_tree(child, result, done, cur_indent, depth)
685 result.append(",\n")
686 result.append(cur_indent)
687 _repr_tree(node[-1], result, done, cur_indent, depth)
688 broken = True
689 result.append("]")
690 return broken
691
692 # pylint: disable=unused-variable,useless-suppression; doesn't understand singledispatch
693 @_repr_tree.register(NodeNG)
694 def _repr_node(node, result, done, cur_indent="", depth=1):
695 """Outputs a strings representation of an astroid node."""
696 if node in done:
697 result.append(
698 indent + f"<Recursion on {type(node).__name__} with id={id(node)}"
699 )
700 return False
701 done.add(node)
702
703 if max_depth and depth > max_depth:
704 result.append("...")
705 return False
706 depth += 1
707 cur_indent += indent
708 if ids:
709 result.append(f"{type(node).__name__}<0x{id(node):x}>(\n")
710 else:
711 result.append(f"{type(node).__name__}(")
712 fields = []
713 if include_linenos:
714 fields.extend(("lineno", "col_offset"))
715 fields.extend(node._other_fields)
716 fields.extend(node._astroid_fields)
717 if ast_state:
718 fields.extend(node._other_other_fields)
719 if not fields:
720 broken = False
721 elif len(fields) == 1:
722 result.append(f"{fields[0]}=")
723 broken = _repr_tree(
724 getattr(node, fields[0]), result, done, cur_indent, depth
725 )
726 else:
727 result.append("\n")
728 result.append(cur_indent)
729 for field in fields[:-1]:
730 # TODO: Remove this after removal of the 'doc' attribute
731 if field == "doc":
732 continue
733 result.append(f"{field}=")
734 _repr_tree(getattr(node, field), result, done, cur_indent, depth)
735 result.append(",\n")
736 result.append(cur_indent)
737 result.append(f"{fields[-1]}=")
738 _repr_tree(getattr(node, fields[-1]), result, done, cur_indent, depth)
739 broken = True
740 result.append(")")
741 return broken
742
743 result: list[str] = []
744 _repr_tree(self, result, set())
745 return "".join(result)
746
747 def bool_value(self, context: InferenceContext | None = None):
748 """Determine the boolean value of this node.
749
750 The boolean value of a node can have three
751 possible values:
752
753 * False: For instance, empty data structures,
754 False, empty strings, instances which return
755 explicitly False from the __nonzero__ / __bool__
756 method.
757 * True: Most of constructs are True by default:
758 classes, functions, modules etc
759 * Uninferable: The inference engine is uncertain of the
760 node's value.
761
762 :returns: The boolean value of this node.
763 :rtype: bool or Uninferable
764 """
765 return util.Uninferable
766
767 def op_precedence(self) -> int:
768 # Look up by class name or default to highest precedence
769 return OP_PRECEDENCE.get(self.__class__.__name__, len(OP_PRECEDENCE))
770
771 def op_left_associative(self) -> bool:
772 # Everything is left associative except `**` and IfExp
773 return True