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