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