blob: c1d547dd90fb55aea0d7c6aae5a33d501db4ed44 [file] [log] [blame]
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""This module contains utilities for rebuilding an _ast tree in
order to get a single Astroid representation.
"""
from __future__ import annotations
import ast
import sys
import token
from collections.abc import Callable, Generator
from io import StringIO
from tokenize import TokenInfo, generate_tokens
from typing import TYPE_CHECKING, Final, TypeVar, Union, cast, overload
from astroid import nodes
from astroid._ast import ParserModule, get_parser_module, parse_function_type_comment
from astroid.const import IS_PYPY, PY38, PY39_PLUS, PY312_PLUS, Context
from astroid.manager import AstroidManager
from astroid.nodes import NodeNG
from astroid.nodes.node_classes import AssignName
from astroid.nodes.utils import Position
from astroid.typing import InferenceResult
REDIRECT: Final[dict[str, str]] = {
"arguments": "Arguments",
"comprehension": "Comprehension",
"ListCompFor": "Comprehension",
"GenExprFor": "Comprehension",
"excepthandler": "ExceptHandler",
"keyword": "Keyword",
"match_case": "MatchCase",
}
T_Doc = TypeVar(
"T_Doc",
"ast.Module",
"ast.ClassDef",
Union["ast.FunctionDef", "ast.AsyncFunctionDef"],
)
_FunctionT = TypeVar("_FunctionT", nodes.FunctionDef, nodes.AsyncFunctionDef)
_ForT = TypeVar("_ForT", nodes.For, nodes.AsyncFor)
_WithT = TypeVar("_WithT", nodes.With, nodes.AsyncWith)
NodesWithDocsType = Union[nodes.Module, nodes.ClassDef, nodes.FunctionDef]
# noinspection PyMethodMayBeStatic
class TreeRebuilder:
"""Rebuilds the _ast tree to become an Astroid tree."""
def __init__(
self,
manager: AstroidManager,
parser_module: ParserModule | None = None,
data: str | None = None,
) -> None:
self._manager = manager
self._data = data.split("\n") if data else None
self._global_names: list[dict[str, list[nodes.Global]]] = []
self._import_from_nodes: list[nodes.ImportFrom] = []
self._delayed_assattr: list[nodes.AssignAttr] = []
self._visit_meths: dict[type[ast.AST], Callable[[ast.AST, NodeNG], NodeNG]] = {}
if parser_module is None:
self._parser_module = get_parser_module()
else:
self._parser_module = parser_module
def _get_doc(self, node: T_Doc) -> tuple[T_Doc, ast.Constant | ast.Str | None]:
"""Return the doc ast node."""
try:
if node.body and isinstance(node.body[0], ast.Expr):
first_value = node.body[0].value
if isinstance(first_value, ast.Constant) and isinstance(
first_value.value, str
):
doc_ast_node = first_value
node.body = node.body[1:]
# The ast parser of python < 3.8 sets col_offset of multi-line strings to -1
# as it is unable to determine the value correctly. We reset this to None.
if doc_ast_node.col_offset == -1:
doc_ast_node.col_offset = None
return node, doc_ast_node
except IndexError:
pass # ast built from scratch
return node, None
def _get_context(
self,
node: (
ast.Attribute
| ast.List
| ast.Name
| ast.Subscript
| ast.Starred
| ast.Tuple
),
) -> Context:
return self._parser_module.context_classes.get(type(node.ctx), Context.Load)
def _get_position_info(
self,
node: ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef,
parent: nodes.ClassDef | nodes.FunctionDef | nodes.AsyncFunctionDef,
) -> Position | None:
"""Return position information for ClassDef and FunctionDef nodes.
In contrast to AST positions, these only include the actual keyword(s)
and the class / function name.
>>> @decorator
>>> async def some_func(var: int) -> None:
>>> ^^^^^^^^^^^^^^^^^^^
"""
if not self._data:
return None
end_lineno = node.end_lineno
if node.body:
end_lineno = node.body[0].lineno
# pylint: disable-next=unsubscriptable-object
data = "\n".join(self._data[node.lineno - 1 : end_lineno])
start_token: TokenInfo | None = None
keyword_tokens: tuple[int, ...] = (token.NAME,)
if isinstance(parent, nodes.AsyncFunctionDef):
search_token = "async"
elif isinstance(parent, nodes.FunctionDef):
search_token = "def"
else:
search_token = "class"
for t in generate_tokens(StringIO(data).readline):
if (
start_token is not None
and t.type == token.NAME
and t.string == node.name
):
break
if t.type in keyword_tokens:
if t.string == search_token:
start_token = t
continue
if t.string in {"def"}:
continue
start_token = None
else:
return None
return Position(
lineno=node.lineno + start_token.start[0] - 1,
col_offset=start_token.start[1],
end_lineno=node.lineno + t.end[0] - 1,
end_col_offset=t.end[1],
)
def _reset_end_lineno(self, newnode: nodes.NodeNG) -> None:
"""Reset end_lineno and end_col_offset attributes for PyPy 3.8.
For some nodes, these are either set to -1 or only partially assigned.
To keep consistency across astroid and pylint, reset all.
This has been fixed in PyPy 3.9.
For reference, an (incomplete) list of nodes with issues:
- ClassDef - For
- FunctionDef - While
- Call - If
- Decorators - Try
- With - Assign
"""
newnode.end_lineno = None
newnode.end_col_offset = None
for child_node in newnode.get_children():
self._reset_end_lineno(child_node)
def visit_module(
self, node: ast.Module, modname: str, modpath: str, package: bool
) -> nodes.Module:
"""Visit a Module node by returning a fresh instance of it.
Note: Method not called by 'visit'
"""
node, doc_ast_node = self._get_doc(node)
newnode = nodes.Module(
name=modname,
file=modpath,
path=[modpath],
package=package,
)
newnode.postinit(
[self.visit(child, newnode) for child in node.body],
doc_node=self.visit(doc_ast_node, newnode),
)
if IS_PYPY and PY38:
self._reset_end_lineno(newnode)
return newnode
if TYPE_CHECKING: # noqa: C901
@overload
def visit(self, node: ast.arg, parent: NodeNG) -> nodes.AssignName: ...
@overload
def visit(self, node: ast.arguments, parent: NodeNG) -> nodes.Arguments: ...
@overload
def visit(self, node: ast.Assert, parent: NodeNG) -> nodes.Assert: ...
@overload
def visit(
self, node: ast.AsyncFunctionDef, parent: NodeNG
) -> nodes.AsyncFunctionDef: ...
@overload
def visit(self, node: ast.AsyncFor, parent: NodeNG) -> nodes.AsyncFor: ...
@overload
def visit(self, node: ast.Await, parent: NodeNG) -> nodes.Await: ...
@overload
def visit(self, node: ast.AsyncWith, parent: NodeNG) -> nodes.AsyncWith: ...
@overload
def visit(self, node: ast.Assign, parent: NodeNG) -> nodes.Assign: ...
@overload
def visit(self, node: ast.AnnAssign, parent: NodeNG) -> nodes.AnnAssign: ...
@overload
def visit(self, node: ast.AugAssign, parent: NodeNG) -> nodes.AugAssign: ...
@overload
def visit(self, node: ast.BinOp, parent: NodeNG) -> nodes.BinOp: ...
@overload
def visit(self, node: ast.BoolOp, parent: NodeNG) -> nodes.BoolOp: ...
@overload
def visit(self, node: ast.Break, parent: NodeNG) -> nodes.Break: ...
@overload
def visit(self, node: ast.Call, parent: NodeNG) -> nodes.Call: ...
@overload
def visit(self, node: ast.ClassDef, parent: NodeNG) -> nodes.ClassDef: ...
@overload
def visit(self, node: ast.Continue, parent: NodeNG) -> nodes.Continue: ...
@overload
def visit(self, node: ast.Compare, parent: NodeNG) -> nodes.Compare: ...
@overload
def visit(
self, node: ast.comprehension, parent: NodeNG
) -> nodes.Comprehension: ...
@overload
def visit(self, node: ast.Delete, parent: NodeNG) -> nodes.Delete: ...
@overload
def visit(self, node: ast.Dict, parent: NodeNG) -> nodes.Dict: ...
@overload
def visit(self, node: ast.DictComp, parent: NodeNG) -> nodes.DictComp: ...
@overload
def visit(self, node: ast.Expr, parent: NodeNG) -> nodes.Expr: ...
@overload
def visit(
self, node: ast.ExceptHandler, parent: NodeNG
) -> nodes.ExceptHandler: ...
@overload
def visit(self, node: ast.For, parent: NodeNG) -> nodes.For: ...
@overload
def visit(self, node: ast.ImportFrom, parent: NodeNG) -> nodes.ImportFrom: ...
@overload
def visit(self, node: ast.FunctionDef, parent: NodeNG) -> nodes.FunctionDef: ...
@overload
def visit(
self, node: ast.GeneratorExp, parent: NodeNG
) -> nodes.GeneratorExp: ...
@overload
def visit(self, node: ast.Attribute, parent: NodeNG) -> nodes.Attribute: ...
@overload
def visit(self, node: ast.Global, parent: NodeNG) -> nodes.Global: ...
@overload
def visit(self, node: ast.If, parent: NodeNG) -> nodes.If: ...
@overload
def visit(self, node: ast.IfExp, parent: NodeNG) -> nodes.IfExp: ...
@overload
def visit(self, node: ast.Import, parent: NodeNG) -> nodes.Import: ...
@overload
def visit(self, node: ast.JoinedStr, parent: NodeNG) -> nodes.JoinedStr: ...
@overload
def visit(
self, node: ast.FormattedValue, parent: NodeNG
) -> nodes.FormattedValue: ...
@overload
def visit(self, node: ast.NamedExpr, parent: NodeNG) -> nodes.NamedExpr: ...
if sys.version_info < (3, 9):
# Not used in Python 3.9+
@overload
def visit(
self, node: ast.ExtSlice, parent: nodes.Subscript
) -> nodes.Tuple: ...
@overload
def visit(self, node: ast.Index, parent: nodes.Subscript) -> NodeNG: ...
@overload
def visit(self, node: ast.keyword, parent: NodeNG) -> nodes.Keyword: ...
@overload
def visit(self, node: ast.Lambda, parent: NodeNG) -> nodes.Lambda: ...
@overload
def visit(self, node: ast.List, parent: NodeNG) -> nodes.List: ...
@overload
def visit(self, node: ast.ListComp, parent: NodeNG) -> nodes.ListComp: ...
@overload
def visit(
self, node: ast.Name, parent: NodeNG
) -> nodes.Name | nodes.Const | nodes.AssignName | nodes.DelName: ...
@overload
def visit(self, node: ast.Nonlocal, parent: NodeNG) -> nodes.Nonlocal: ...
@overload
def visit(self, node: ast.Constant, parent: NodeNG) -> nodes.Const: ...
if sys.version_info >= (3, 12):
@overload
def visit(self, node: ast.ParamSpec, parent: NodeNG) -> nodes.ParamSpec: ...
@overload
def visit(self, node: ast.Pass, parent: NodeNG) -> nodes.Pass: ...
@overload
def visit(self, node: ast.Raise, parent: NodeNG) -> nodes.Raise: ...
@overload
def visit(self, node: ast.Return, parent: NodeNG) -> nodes.Return: ...
@overload
def visit(self, node: ast.Set, parent: NodeNG) -> nodes.Set: ...
@overload
def visit(self, node: ast.SetComp, parent: NodeNG) -> nodes.SetComp: ...
@overload
def visit(self, node: ast.Slice, parent: nodes.Subscript) -> nodes.Slice: ...
@overload
def visit(self, node: ast.Subscript, parent: NodeNG) -> nodes.Subscript: ...
@overload
def visit(self, node: ast.Starred, parent: NodeNG) -> nodes.Starred: ...
@overload
def visit(self, node: ast.Try, parent: NodeNG) -> nodes.Try: ...
if sys.version_info >= (3, 11):
@overload
def visit(self, node: ast.TryStar, parent: NodeNG) -> nodes.TryStar: ...
@overload
def visit(self, node: ast.Tuple, parent: NodeNG) -> nodes.Tuple: ...
if sys.version_info >= (3, 12):
@overload
def visit(self, node: ast.TypeAlias, parent: NodeNG) -> nodes.TypeAlias: ...
@overload
def visit(self, node: ast.TypeVar, parent: NodeNG) -> nodes.TypeVar: ...
@overload
def visit(
self, node: ast.TypeVarTuple, parent: NodeNG
) -> nodes.TypeVarTuple: ...
@overload
def visit(self, node: ast.UnaryOp, parent: NodeNG) -> nodes.UnaryOp: ...
@overload
def visit(self, node: ast.While, parent: NodeNG) -> nodes.While: ...
@overload
def visit(self, node: ast.With, parent: NodeNG) -> nodes.With: ...
@overload
def visit(self, node: ast.Yield, parent: NodeNG) -> nodes.Yield: ...
@overload
def visit(self, node: ast.YieldFrom, parent: NodeNG) -> nodes.YieldFrom: ...
if sys.version_info >= (3, 10):
@overload
def visit(self, node: ast.Match, parent: NodeNG) -> nodes.Match: ...
@overload
def visit(
self, node: ast.match_case, parent: NodeNG
) -> nodes.MatchCase: ...
@overload
def visit(
self, node: ast.MatchValue, parent: NodeNG
) -> nodes.MatchValue: ...
@overload
def visit(
self, node: ast.MatchSingleton, parent: NodeNG
) -> nodes.MatchSingleton: ...
@overload
def visit(
self, node: ast.MatchSequence, parent: NodeNG
) -> nodes.MatchSequence: ...
@overload
def visit(
self, node: ast.MatchMapping, parent: NodeNG
) -> nodes.MatchMapping: ...
@overload
def visit(
self, node: ast.MatchClass, parent: NodeNG
) -> nodes.MatchClass: ...
@overload
def visit(self, node: ast.MatchStar, parent: NodeNG) -> nodes.MatchStar: ...
@overload
def visit(self, node: ast.MatchAs, parent: NodeNG) -> nodes.MatchAs: ...
@overload
def visit(self, node: ast.MatchOr, parent: NodeNG) -> nodes.MatchOr: ...
@overload
def visit(self, node: ast.pattern, parent: NodeNG) -> nodes.Pattern: ...
@overload
def visit(self, node: ast.AST, parent: NodeNG) -> NodeNG: ...
@overload
def visit(self, node: None, parent: NodeNG) -> None: ...
def visit(self, node: ast.AST | None, parent: NodeNG) -> NodeNG | None:
if node is None:
return None
cls = node.__class__
if cls in self._visit_meths:
visit_method = self._visit_meths[cls]
else:
cls_name = cls.__name__
visit_name = "visit_" + REDIRECT.get(cls_name, cls_name).lower()
visit_method = getattr(self, visit_name)
self._visit_meths[cls] = visit_method
return visit_method(node, parent)
def _save_assignment(self, node: nodes.AssignName | nodes.DelName) -> None:
"""Save assignment situation since node.parent is not available yet."""
if self._global_names and node.name in self._global_names[-1]:
node.root().set_local(node.name, node)
else:
assert node.parent
assert node.name
node.parent.set_local(node.name, node)
def visit_arg(self, node: ast.arg, parent: NodeNG) -> nodes.AssignName:
"""Visit an arg node by returning a fresh AssName instance."""
return self.visit_assignname(node, parent, node.arg)
def visit_arguments(self, node: ast.arguments, parent: NodeNG) -> nodes.Arguments:
"""Visit an Arguments node by returning a fresh instance of it."""
vararg: str | None = None
kwarg: str | None = None
vararg_node = node.vararg
kwarg_node = node.kwarg
newnode = nodes.Arguments(
node.vararg.arg if node.vararg else None,
node.kwarg.arg if node.kwarg else None,
parent,
(
AssignName(
vararg_node.arg,
vararg_node.lineno,
vararg_node.col_offset,
parent,
end_lineno=vararg_node.end_lineno,
end_col_offset=vararg_node.end_col_offset,
)
if vararg_node
else None
),
(
AssignName(
kwarg_node.arg,
kwarg_node.lineno,
kwarg_node.col_offset,
parent,
end_lineno=kwarg_node.end_lineno,
end_col_offset=kwarg_node.end_col_offset,
)
if kwarg_node
else None
),
)
args = [self.visit(child, newnode) for child in node.args]
defaults = [self.visit(child, newnode) for child in node.defaults]
varargannotation: NodeNG | None = None
kwargannotation: NodeNG | None = None
if node.vararg:
vararg = node.vararg.arg
varargannotation = self.visit(node.vararg.annotation, newnode)
if node.kwarg:
kwarg = node.kwarg.arg
kwargannotation = self.visit(node.kwarg.annotation, newnode)
if PY38:
# In Python 3.8 'end_lineno' and 'end_col_offset'
# for 'kwonlyargs' don't include the annotation.
for arg in node.kwonlyargs:
if arg.annotation is not None:
arg.end_lineno = arg.annotation.end_lineno
arg.end_col_offset = arg.annotation.end_col_offset
kwonlyargs = [self.visit(child, newnode) for child in node.kwonlyargs]
kw_defaults = [self.visit(child, newnode) for child in node.kw_defaults]
annotations = [self.visit(arg.annotation, newnode) for arg in node.args]
kwonlyargs_annotations = [
self.visit(arg.annotation, newnode) for arg in node.kwonlyargs
]
posonlyargs = [self.visit(child, newnode) for child in node.posonlyargs]
posonlyargs_annotations = [
self.visit(arg.annotation, newnode) for arg in node.posonlyargs
]
type_comment_args = [
self.check_type_comment(child, parent=newnode) for child in node.args
]
type_comment_kwonlyargs = [
self.check_type_comment(child, parent=newnode) for child in node.kwonlyargs
]
type_comment_posonlyargs = [
self.check_type_comment(child, parent=newnode) for child in node.posonlyargs
]
newnode.postinit(
args=args,
defaults=defaults,
kwonlyargs=kwonlyargs,
posonlyargs=posonlyargs,
kw_defaults=kw_defaults,
annotations=annotations,
kwonlyargs_annotations=kwonlyargs_annotations,
posonlyargs_annotations=posonlyargs_annotations,
varargannotation=varargannotation,
kwargannotation=kwargannotation,
type_comment_args=type_comment_args,
type_comment_kwonlyargs=type_comment_kwonlyargs,
type_comment_posonlyargs=type_comment_posonlyargs,
)
# save argument names in locals:
assert newnode.parent
if vararg:
newnode.parent.set_local(vararg, newnode)
if kwarg:
newnode.parent.set_local(kwarg, newnode)
return newnode
def visit_assert(self, node: ast.Assert, parent: NodeNG) -> nodes.Assert:
"""Visit a Assert node by returning a fresh instance of it."""
newnode = nodes.Assert(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
msg: NodeNG | None = None
if node.msg:
msg = self.visit(node.msg, newnode)
newnode.postinit(self.visit(node.test, newnode), msg)
return newnode
def check_type_comment(
self,
node: ast.Assign | ast.arg | ast.For | ast.AsyncFor | ast.With | ast.AsyncWith,
parent: (
nodes.Assign
| nodes.Arguments
| nodes.For
| nodes.AsyncFor
| nodes.With
| nodes.AsyncWith
),
) -> NodeNG | None:
if not node.type_comment:
return None
try:
type_comment_ast = self._parser_module.parse(node.type_comment)
except SyntaxError:
# Invalid type comment, just skip it.
return None
# For '# type: # any comment' ast.parse returns a Module node,
# without any nodes in the body.
if not type_comment_ast.body:
return None
type_object = self.visit(type_comment_ast.body[0], parent=parent)
if not isinstance(type_object, nodes.Expr):
return None
return type_object.value
def check_function_type_comment(
self, node: ast.FunctionDef | ast.AsyncFunctionDef, parent: NodeNG
) -> tuple[NodeNG | None, list[NodeNG]] | None:
if not node.type_comment:
return None
try:
type_comment_ast = parse_function_type_comment(node.type_comment)
except SyntaxError:
# Invalid type comment, just skip it.
return None
if not type_comment_ast:
return None
returns: NodeNG | None = None
argtypes: list[NodeNG] = [
self.visit(elem, parent) for elem in (type_comment_ast.argtypes or [])
]
if type_comment_ast.returns:
returns = self.visit(type_comment_ast.returns, parent)
return returns, argtypes
def visit_asyncfunctiondef(
self, node: ast.AsyncFunctionDef, parent: NodeNG
) -> nodes.AsyncFunctionDef:
return self._visit_functiondef(nodes.AsyncFunctionDef, node, parent)
def visit_asyncfor(self, node: ast.AsyncFor, parent: NodeNG) -> nodes.AsyncFor:
return self._visit_for(nodes.AsyncFor, node, parent)
def visit_await(self, node: ast.Await, parent: NodeNG) -> nodes.Await:
newnode = nodes.Await(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(value=self.visit(node.value, newnode))
return newnode
def visit_asyncwith(self, node: ast.AsyncWith, parent: NodeNG) -> nodes.AsyncWith:
return self._visit_with(nodes.AsyncWith, node, parent)
def visit_assign(self, node: ast.Assign, parent: NodeNG) -> nodes.Assign:
"""Visit a Assign node by returning a fresh instance of it."""
newnode = nodes.Assign(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
type_annotation = self.check_type_comment(node, parent=newnode)
newnode.postinit(
targets=[self.visit(child, newnode) for child in node.targets],
value=self.visit(node.value, newnode),
type_annotation=type_annotation,
)
return newnode
def visit_annassign(self, node: ast.AnnAssign, parent: NodeNG) -> nodes.AnnAssign:
"""Visit an AnnAssign node by returning a fresh instance of it."""
newnode = nodes.AnnAssign(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
target=self.visit(node.target, newnode),
annotation=self.visit(node.annotation, newnode),
simple=node.simple,
value=self.visit(node.value, newnode),
)
return newnode
@overload
def visit_assignname(
self, node: ast.AST, parent: NodeNG, node_name: str
) -> nodes.AssignName: ...
@overload
def visit_assignname(
self, node: ast.AST, parent: NodeNG, node_name: None
) -> None: ...
def visit_assignname(
self, node: ast.AST, parent: NodeNG, node_name: str | None
) -> nodes.AssignName | None:
"""Visit a node and return a AssignName node.
Note: Method not called by 'visit'
"""
if node_name is None:
return None
newnode = nodes.AssignName(
name=node_name,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
self._save_assignment(newnode)
return newnode
def visit_augassign(self, node: ast.AugAssign, parent: NodeNG) -> nodes.AugAssign:
"""Visit a AugAssign node by returning a fresh instance of it."""
newnode = nodes.AugAssign(
op=self._parser_module.bin_op_classes[type(node.op)] + "=",
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.target, newnode), self.visit(node.value, newnode)
)
return newnode
def visit_binop(self, node: ast.BinOp, parent: NodeNG) -> nodes.BinOp:
"""Visit a BinOp node by returning a fresh instance of it."""
newnode = nodes.BinOp(
op=self._parser_module.bin_op_classes[type(node.op)],
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.left, newnode), self.visit(node.right, newnode)
)
return newnode
def visit_boolop(self, node: ast.BoolOp, parent: NodeNG) -> nodes.BoolOp:
"""Visit a BoolOp node by returning a fresh instance of it."""
newnode = nodes.BoolOp(
op=self._parser_module.bool_op_classes[type(node.op)],
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit([self.visit(child, newnode) for child in node.values])
return newnode
def visit_break(self, node: ast.Break, parent: NodeNG) -> nodes.Break:
"""Visit a Break node by returning a fresh instance of it."""
return nodes.Break(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
def visit_call(self, node: ast.Call, parent: NodeNG) -> nodes.Call:
"""Visit a CallFunc node by returning a fresh instance of it."""
newnode = nodes.Call(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
func=self.visit(node.func, newnode),
args=[self.visit(child, newnode) for child in node.args],
keywords=[self.visit(child, newnode) for child in node.keywords],
)
return newnode
def visit_classdef(
self, node: ast.ClassDef, parent: NodeNG, newstyle: bool = True
) -> nodes.ClassDef:
"""Visit a ClassDef node to become astroid."""
node, doc_ast_node = self._get_doc(node)
newnode = nodes.ClassDef(
name=node.name,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
metaclass = None
for keyword in node.keywords:
if keyword.arg == "metaclass":
metaclass = self.visit(keyword, newnode).value
break
decorators = self.visit_decorators(node, newnode)
newnode.postinit(
[self.visit(child, newnode) for child in node.bases],
[self.visit(child, newnode) for child in node.body],
decorators,
newstyle,
metaclass,
[
self.visit(kwd, newnode)
for kwd in node.keywords
if kwd.arg != "metaclass"
],
position=self._get_position_info(node, newnode),
doc_node=self.visit(doc_ast_node, newnode),
type_params=(
[self.visit(param, newnode) for param in node.type_params]
if PY312_PLUS
else []
),
)
return newnode
def visit_continue(self, node: ast.Continue, parent: NodeNG) -> nodes.Continue:
"""Visit a Continue node by returning a fresh instance of it."""
return nodes.Continue(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
def visit_compare(self, node: ast.Compare, parent: NodeNG) -> nodes.Compare:
"""Visit a Compare node by returning a fresh instance of it."""
newnode = nodes.Compare(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.left, newnode),
[
(
self._parser_module.cmp_op_classes[op.__class__],
self.visit(expr, newnode),
)
for (op, expr) in zip(node.ops, node.comparators)
],
)
return newnode
def visit_comprehension(
self, node: ast.comprehension, parent: NodeNG
) -> nodes.Comprehension:
"""Visit a Comprehension node by returning a fresh instance of it."""
newnode = nodes.Comprehension(
parent=parent,
# Comprehension nodes don't have these attributes
# see https://docs.python.org/3/library/ast.html#abstract-grammar
lineno=None,
col_offset=None,
end_lineno=None,
end_col_offset=None,
)
newnode.postinit(
self.visit(node.target, newnode),
self.visit(node.iter, newnode),
[self.visit(child, newnode) for child in node.ifs],
bool(node.is_async),
)
return newnode
def visit_decorators(
self,
node: ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef,
parent: NodeNG,
) -> nodes.Decorators | None:
"""Visit a Decorators node by returning a fresh instance of it.
Note: Method not called by 'visit'
"""
if not node.decorator_list:
return None
# /!\ node is actually an _ast.FunctionDef node while
# parent is an astroid.nodes.FunctionDef node
# Set the line number of the first decorator for Python 3.8+.
lineno = node.decorator_list[0].lineno
end_lineno = node.decorator_list[-1].end_lineno
end_col_offset = node.decorator_list[-1].end_col_offset
newnode = nodes.Decorators(
lineno=lineno,
col_offset=node.col_offset,
end_lineno=end_lineno,
end_col_offset=end_col_offset,
parent=parent,
)
newnode.postinit([self.visit(child, newnode) for child in node.decorator_list])
return newnode
def visit_delete(self, node: ast.Delete, parent: NodeNG) -> nodes.Delete:
"""Visit a Delete node by returning a fresh instance of it."""
newnode = nodes.Delete(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit([self.visit(child, newnode) for child in node.targets])
return newnode
def _visit_dict_items(
self, node: ast.Dict, parent: NodeNG, newnode: nodes.Dict
) -> Generator[tuple[NodeNG, NodeNG], None, None]:
for key, value in zip(node.keys, node.values):
rebuilt_key: NodeNG
rebuilt_value = self.visit(value, newnode)
if not key:
# Extended unpacking
rebuilt_key = nodes.DictUnpack(
lineno=rebuilt_value.lineno,
col_offset=rebuilt_value.col_offset,
end_lineno=rebuilt_value.end_lineno,
end_col_offset=rebuilt_value.end_col_offset,
parent=parent,
)
else:
rebuilt_key = self.visit(key, newnode)
yield rebuilt_key, rebuilt_value
def visit_dict(self, node: ast.Dict, parent: NodeNG) -> nodes.Dict:
"""Visit a Dict node by returning a fresh instance of it."""
newnode = nodes.Dict(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
items: list[tuple[InferenceResult, InferenceResult]] = list(
self._visit_dict_items(node, parent, newnode)
)
newnode.postinit(items)
return newnode
def visit_dictcomp(self, node: ast.DictComp, parent: NodeNG) -> nodes.DictComp:
"""Visit a DictComp node by returning a fresh instance of it."""
newnode = nodes.DictComp(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.key, newnode),
self.visit(node.value, newnode),
[self.visit(child, newnode) for child in node.generators],
)
return newnode
def visit_expr(self, node: ast.Expr, parent: NodeNG) -> nodes.Expr:
"""Visit a Expr node by returning a fresh instance of it."""
newnode = nodes.Expr(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(self.visit(node.value, newnode))
return newnode
def visit_excepthandler(
self, node: ast.ExceptHandler, parent: NodeNG
) -> nodes.ExceptHandler:
"""Visit an ExceptHandler node by returning a fresh instance of it."""
newnode = nodes.ExceptHandler(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.type, newnode),
self.visit_assignname(node, newnode, node.name),
[self.visit(child, newnode) for child in node.body],
)
return newnode
@overload
def _visit_for(
self, cls: type[nodes.For], node: ast.For, parent: NodeNG
) -> nodes.For: ...
@overload
def _visit_for(
self, cls: type[nodes.AsyncFor], node: ast.AsyncFor, parent: NodeNG
) -> nodes.AsyncFor: ...
def _visit_for(
self, cls: type[_ForT], node: ast.For | ast.AsyncFor, parent: NodeNG
) -> _ForT:
"""Visit a For node by returning a fresh instance of it."""
col_offset = node.col_offset
if IS_PYPY and not PY39_PLUS and isinstance(node, ast.AsyncFor) and self._data:
# pylint: disable-next=unsubscriptable-object
col_offset = self._data[node.lineno - 1].index("async")
newnode = cls(
lineno=node.lineno,
col_offset=col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
type_annotation = self.check_type_comment(node, parent=newnode)
newnode.postinit(
target=self.visit(node.target, newnode),
iter=self.visit(node.iter, newnode),
body=[self.visit(child, newnode) for child in node.body],
orelse=[self.visit(child, newnode) for child in node.orelse],
type_annotation=type_annotation,
)
return newnode
def visit_for(self, node: ast.For, parent: NodeNG) -> nodes.For:
return self._visit_for(nodes.For, node, parent)
def visit_importfrom(
self, node: ast.ImportFrom, parent: NodeNG
) -> nodes.ImportFrom:
"""Visit an ImportFrom node by returning a fresh instance of it."""
names = [(alias.name, alias.asname) for alias in node.names]
newnode = nodes.ImportFrom(
fromname=node.module or "",
names=names,
level=node.level or None,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
# store From names to add them to locals after building
self._import_from_nodes.append(newnode)
return newnode
@overload
def _visit_functiondef(
self, cls: type[nodes.FunctionDef], node: ast.FunctionDef, parent: NodeNG
) -> nodes.FunctionDef: ...
@overload
def _visit_functiondef(
self,
cls: type[nodes.AsyncFunctionDef],
node: ast.AsyncFunctionDef,
parent: NodeNG,
) -> nodes.AsyncFunctionDef: ...
def _visit_functiondef(
self,
cls: type[_FunctionT],
node: ast.FunctionDef | ast.AsyncFunctionDef,
parent: NodeNG,
) -> _FunctionT:
"""Visit an FunctionDef node to become astroid."""
self._global_names.append({})
node, doc_ast_node = self._get_doc(node)
lineno = node.lineno
if node.decorator_list:
# Python 3.8 sets the line number of a decorated function
# to be the actual line number of the function, but the
# previous versions expected the decorator's line number instead.
# We reset the function's line number to that of the
# first decorator to maintain backward compatibility.
# It's not ideal but this discrepancy was baked into
# the framework for *years*.
lineno = node.decorator_list[0].lineno
newnode = cls(
name=node.name,
lineno=lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
decorators = self.visit_decorators(node, newnode)
returns: NodeNG | None
if node.returns:
returns = self.visit(node.returns, newnode)
else:
returns = None
type_comment_args = type_comment_returns = None
type_comment_annotation = self.check_function_type_comment(node, newnode)
if type_comment_annotation:
type_comment_returns, type_comment_args = type_comment_annotation
newnode.postinit(
args=self.visit(node.args, newnode),
body=[self.visit(child, newnode) for child in node.body],
decorators=decorators,
returns=returns,
type_comment_returns=type_comment_returns,
type_comment_args=type_comment_args,
position=self._get_position_info(node, newnode),
doc_node=self.visit(doc_ast_node, newnode),
type_params=(
[self.visit(param, newnode) for param in node.type_params]
if PY312_PLUS
else []
),
)
self._global_names.pop()
return newnode
def visit_functiondef(
self, node: ast.FunctionDef, parent: NodeNG
) -> nodes.FunctionDef:
return self._visit_functiondef(nodes.FunctionDef, node, parent)
def visit_generatorexp(
self, node: ast.GeneratorExp, parent: NodeNG
) -> nodes.GeneratorExp:
"""Visit a GeneratorExp node by returning a fresh instance of it."""
newnode = nodes.GeneratorExp(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.elt, newnode),
[self.visit(child, newnode) for child in node.generators],
)
return newnode
def visit_attribute(
self, node: ast.Attribute, parent: NodeNG
) -> nodes.Attribute | nodes.AssignAttr | nodes.DelAttr:
"""Visit an Attribute node by returning a fresh instance of it."""
context = self._get_context(node)
newnode: nodes.Attribute | nodes.AssignAttr | nodes.DelAttr
if context == Context.Del:
# FIXME : maybe we should reintroduce and visit_delattr ?
# for instance, deactivating assign_ctx
newnode = nodes.DelAttr(
attrname=node.attr,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
elif context == Context.Store:
newnode = nodes.AssignAttr(
attrname=node.attr,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
# Prohibit a local save if we are in an ExceptHandler.
if not isinstance(parent, nodes.ExceptHandler):
# mypy doesn't recognize that newnode has to be AssignAttr because it
# doesn't support ParamSpec
# See https://github.com/python/mypy/issues/8645
self._delayed_assattr.append(newnode) # type: ignore[arg-type]
else:
newnode = nodes.Attribute(
attrname=node.attr,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(self.visit(node.value, newnode))
return newnode
def visit_global(self, node: ast.Global, parent: NodeNG) -> nodes.Global:
"""Visit a Global node to become astroid."""
newnode = nodes.Global(
names=node.names,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
if self._global_names: # global at the module level, no effect
for name in node.names:
self._global_names[-1].setdefault(name, []).append(newnode)
return newnode
def visit_if(self, node: ast.If, parent: NodeNG) -> nodes.If:
"""Visit an If node by returning a fresh instance of it."""
newnode = nodes.If(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.test, newnode),
[self.visit(child, newnode) for child in node.body],
[self.visit(child, newnode) for child in node.orelse],
)
return newnode
def visit_ifexp(self, node: ast.IfExp, parent: NodeNG) -> nodes.IfExp:
"""Visit a IfExp node by returning a fresh instance of it."""
newnode = nodes.IfExp(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.test, newnode),
self.visit(node.body, newnode),
self.visit(node.orelse, newnode),
)
return newnode
def visit_import(self, node: ast.Import, parent: NodeNG) -> nodes.Import:
"""Visit a Import node by returning a fresh instance of it."""
names = [(alias.name, alias.asname) for alias in node.names]
newnode = nodes.Import(
names=names,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
# save import names in parent's locals:
for name, asname in newnode.names:
name = asname or name
parent.set_local(name.split(".")[0], newnode)
return newnode
def visit_joinedstr(self, node: ast.JoinedStr, parent: NodeNG) -> nodes.JoinedStr:
newnode = nodes.JoinedStr(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit([self.visit(child, newnode) for child in node.values])
return newnode
def visit_formattedvalue(
self, node: ast.FormattedValue, parent: NodeNG
) -> nodes.FormattedValue:
newnode = nodes.FormattedValue(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
value=self.visit(node.value, newnode),
conversion=node.conversion,
format_spec=self.visit(node.format_spec, newnode),
)
return newnode
def visit_namedexpr(self, node: ast.NamedExpr, parent: NodeNG) -> nodes.NamedExpr:
newnode = nodes.NamedExpr(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.target, newnode), self.visit(node.value, newnode)
)
return newnode
if sys.version_info < (3, 9):
# Not used in Python 3.9+.
def visit_extslice(
self, node: ast.ExtSlice, parent: nodes.Subscript
) -> nodes.Tuple:
"""Visit an ExtSlice node by returning a fresh instance of Tuple."""
# ExtSlice doesn't have lineno or col_offset information
newnode = nodes.Tuple(ctx=Context.Load, parent=parent)
newnode.postinit([self.visit(dim, newnode) for dim in node.dims])
return newnode
def visit_index(self, node: ast.Index, parent: nodes.Subscript) -> NodeNG:
"""Visit a Index node by returning a fresh instance of NodeNG."""
return self.visit(node.value, parent)
def visit_keyword(self, node: ast.keyword, parent: NodeNG) -> nodes.Keyword:
"""Visit a Keyword node by returning a fresh instance of it."""
newnode = nodes.Keyword(
arg=node.arg,
# position attributes added in 3.9
lineno=getattr(node, "lineno", None),
col_offset=getattr(node, "col_offset", None),
end_lineno=getattr(node, "end_lineno", None),
end_col_offset=getattr(node, "end_col_offset", None),
parent=parent,
)
newnode.postinit(self.visit(node.value, newnode))
return newnode
def visit_lambda(self, node: ast.Lambda, parent: NodeNG) -> nodes.Lambda:
"""Visit a Lambda node by returning a fresh instance of it."""
newnode = nodes.Lambda(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(self.visit(node.args, newnode), self.visit(node.body, newnode))
return newnode
def visit_list(self, node: ast.List, parent: NodeNG) -> nodes.List:
"""Visit a List node by returning a fresh instance of it."""
context = self._get_context(node)
newnode = nodes.List(
ctx=context,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit([self.visit(child, newnode) for child in node.elts])
return newnode
def visit_listcomp(self, node: ast.ListComp, parent: NodeNG) -> nodes.ListComp:
"""Visit a ListComp node by returning a fresh instance of it."""
newnode = nodes.ListComp(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.elt, newnode),
[self.visit(child, newnode) for child in node.generators],
)
return newnode
def visit_name(
self, node: ast.Name, parent: NodeNG
) -> nodes.Name | nodes.AssignName | nodes.DelName:
"""Visit a Name node by returning a fresh instance of it."""
context = self._get_context(node)
newnode: nodes.Name | nodes.AssignName | nodes.DelName
if context == Context.Del:
newnode = nodes.DelName(
name=node.id,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
elif context == Context.Store:
newnode = nodes.AssignName(
name=node.id,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
else:
newnode = nodes.Name(
name=node.id,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
# XXX REMOVE me :
if context in (Context.Del, Context.Store): # 'Aug' ??
newnode = cast(Union[nodes.AssignName, nodes.DelName], newnode)
self._save_assignment(newnode)
return newnode
def visit_nonlocal(self, node: ast.Nonlocal, parent: NodeNG) -> nodes.Nonlocal:
"""Visit a Nonlocal node and return a new instance of it."""
return nodes.Nonlocal(
names=node.names,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
def visit_constant(self, node: ast.Constant, parent: NodeNG) -> nodes.Const:
"""Visit a Constant node by returning a fresh instance of Const."""
return nodes.Const(
value=node.value,
kind=node.kind,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
def visit_paramspec(self, node: ast.ParamSpec, parent: NodeNG) -> nodes.ParamSpec:
"""Visit a ParamSpec node by returning a fresh instance of it."""
newnode = nodes.ParamSpec(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
# Add AssignName node for 'node.name'
# https://bugs.python.org/issue43994
newnode.postinit(name=self.visit_assignname(node, newnode, node.name))
return newnode
def visit_pass(self, node: ast.Pass, parent: NodeNG) -> nodes.Pass:
"""Visit a Pass node by returning a fresh instance of it."""
return nodes.Pass(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
def visit_raise(self, node: ast.Raise, parent: NodeNG) -> nodes.Raise:
"""Visit a Raise node by returning a fresh instance of it."""
newnode = nodes.Raise(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
# no traceback; anyway it is not used in Pylint
newnode.postinit(
exc=self.visit(node.exc, newnode),
cause=self.visit(node.cause, newnode),
)
return newnode
def visit_return(self, node: ast.Return, parent: NodeNG) -> nodes.Return:
"""Visit a Return node by returning a fresh instance of it."""
newnode = nodes.Return(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(self.visit(node.value, newnode))
return newnode
def visit_set(self, node: ast.Set, parent: NodeNG) -> nodes.Set:
"""Visit a Set node by returning a fresh instance of it."""
newnode = nodes.Set(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit([self.visit(child, newnode) for child in node.elts])
return newnode
def visit_setcomp(self, node: ast.SetComp, parent: NodeNG) -> nodes.SetComp:
"""Visit a SetComp node by returning a fresh instance of it."""
newnode = nodes.SetComp(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.elt, newnode),
[self.visit(child, newnode) for child in node.generators],
)
return newnode
def visit_slice(self, node: ast.Slice, parent: nodes.Subscript) -> nodes.Slice:
"""Visit a Slice node by returning a fresh instance of it."""
newnode = nodes.Slice(
# position attributes added in 3.9
lineno=getattr(node, "lineno", None),
col_offset=getattr(node, "col_offset", None),
end_lineno=getattr(node, "end_lineno", None),
end_col_offset=getattr(node, "end_col_offset", None),
parent=parent,
)
newnode.postinit(
lower=self.visit(node.lower, newnode),
upper=self.visit(node.upper, newnode),
step=self.visit(node.step, newnode),
)
return newnode
def visit_subscript(self, node: ast.Subscript, parent: NodeNG) -> nodes.Subscript:
"""Visit a Subscript node by returning a fresh instance of it."""
context = self._get_context(node)
newnode = nodes.Subscript(
ctx=context,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.value, newnode), self.visit(node.slice, newnode)
)
return newnode
def visit_starred(self, node: ast.Starred, parent: NodeNG) -> nodes.Starred:
"""Visit a Starred node and return a new instance of it."""
context = self._get_context(node)
newnode = nodes.Starred(
ctx=context,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(self.visit(node.value, newnode))
return newnode
def visit_try(self, node: ast.Try, parent: NodeNG) -> nodes.Try:
"""Visit a Try node by returning a fresh instance of it"""
newnode = nodes.Try(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
body=[self.visit(child, newnode) for child in node.body],
handlers=[self.visit(child, newnode) for child in node.handlers],
orelse=[self.visit(child, newnode) for child in node.orelse],
finalbody=[self.visit(child, newnode) for child in node.finalbody],
)
return newnode
def visit_trystar(self, node: ast.TryStar, parent: NodeNG) -> nodes.TryStar:
newnode = nodes.TryStar(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
body=[self.visit(n, newnode) for n in node.body],
handlers=[self.visit(n, newnode) for n in node.handlers],
orelse=[self.visit(n, newnode) for n in node.orelse],
finalbody=[self.visit(n, newnode) for n in node.finalbody],
)
return newnode
def visit_tuple(self, node: ast.Tuple, parent: NodeNG) -> nodes.Tuple:
"""Visit a Tuple node by returning a fresh instance of it."""
context = self._get_context(node)
newnode = nodes.Tuple(
ctx=context,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit([self.visit(child, newnode) for child in node.elts])
return newnode
def visit_typealias(self, node: ast.TypeAlias, parent: NodeNG) -> nodes.TypeAlias:
"""Visit a TypeAlias node by returning a fresh instance of it."""
newnode = nodes.TypeAlias(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
name=self.visit(node.name, newnode),
type_params=[self.visit(p, newnode) for p in node.type_params],
value=self.visit(node.value, newnode),
)
return newnode
def visit_typevar(self, node: ast.TypeVar, parent: NodeNG) -> nodes.TypeVar:
"""Visit a TypeVar node by returning a fresh instance of it."""
newnode = nodes.TypeVar(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
# Add AssignName node for 'node.name'
# https://bugs.python.org/issue43994
newnode.postinit(
name=self.visit_assignname(node, newnode, node.name),
bound=self.visit(node.bound, newnode),
)
return newnode
def visit_typevartuple(
self, node: ast.TypeVarTuple, parent: NodeNG
) -> nodes.TypeVarTuple:
"""Visit a TypeVarTuple node by returning a fresh instance of it."""
newnode = nodes.TypeVarTuple(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
# Add AssignName node for 'node.name'
# https://bugs.python.org/issue43994
newnode.postinit(name=self.visit_assignname(node, newnode, node.name))
return newnode
def visit_unaryop(self, node: ast.UnaryOp, parent: NodeNG) -> nodes.UnaryOp:
"""Visit a UnaryOp node by returning a fresh instance of it."""
newnode = nodes.UnaryOp(
op=self._parser_module.unary_op_classes[node.op.__class__],
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(self.visit(node.operand, newnode))
return newnode
def visit_while(self, node: ast.While, parent: NodeNG) -> nodes.While:
"""Visit a While node by returning a fresh instance of it."""
newnode = nodes.While(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
self.visit(node.test, newnode),
[self.visit(child, newnode) for child in node.body],
[self.visit(child, newnode) for child in node.orelse],
)
return newnode
@overload
def _visit_with(
self, cls: type[nodes.With], node: ast.With, parent: NodeNG
) -> nodes.With: ...
@overload
def _visit_with(
self, cls: type[nodes.AsyncWith], node: ast.AsyncWith, parent: NodeNG
) -> nodes.AsyncWith: ...
def _visit_with(
self,
cls: type[_WithT],
node: ast.With | ast.AsyncWith,
parent: NodeNG,
) -> _WithT:
col_offset = node.col_offset
if IS_PYPY and not PY39_PLUS and isinstance(node, ast.AsyncWith) and self._data:
# pylint: disable-next=unsubscriptable-object
col_offset = self._data[node.lineno - 1].index("async")
newnode = cls(
lineno=node.lineno,
col_offset=col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
def visit_child(child: ast.withitem) -> tuple[NodeNG, NodeNG | None]:
expr = self.visit(child.context_expr, newnode)
var = self.visit(child.optional_vars, newnode)
return expr, var
type_annotation = self.check_type_comment(node, parent=newnode)
newnode.postinit(
items=[visit_child(child) for child in node.items],
body=[self.visit(child, newnode) for child in node.body],
type_annotation=type_annotation,
)
return newnode
def visit_with(self, node: ast.With, parent: NodeNG) -> NodeNG:
return self._visit_with(nodes.With, node, parent)
def visit_yield(self, node: ast.Yield, parent: NodeNG) -> NodeNG:
"""Visit a Yield node by returning a fresh instance of it."""
newnode = nodes.Yield(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(self.visit(node.value, newnode))
return newnode
def visit_yieldfrom(self, node: ast.YieldFrom, parent: NodeNG) -> NodeNG:
newnode = nodes.YieldFrom(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(self.visit(node.value, newnode))
return newnode
if sys.version_info >= (3, 10):
def visit_match(self, node: ast.Match, parent: NodeNG) -> nodes.Match:
newnode = nodes.Match(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
subject=self.visit(node.subject, newnode),
cases=[self.visit(case, newnode) for case in node.cases],
)
return newnode
def visit_matchcase(
self, node: ast.match_case, parent: NodeNG
) -> nodes.MatchCase:
newnode = nodes.MatchCase(parent=parent)
newnode.postinit(
pattern=self.visit(node.pattern, newnode),
guard=self.visit(node.guard, newnode),
body=[self.visit(child, newnode) for child in node.body],
)
return newnode
def visit_matchvalue(
self, node: ast.MatchValue, parent: NodeNG
) -> nodes.MatchValue:
newnode = nodes.MatchValue(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(value=self.visit(node.value, newnode))
return newnode
def visit_matchsingleton(
self, node: ast.MatchSingleton, parent: NodeNG
) -> nodes.MatchSingleton:
return nodes.MatchSingleton(
value=node.value,
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
def visit_matchsequence(
self, node: ast.MatchSequence, parent: NodeNG
) -> nodes.MatchSequence:
newnode = nodes.MatchSequence(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
patterns=[self.visit(pattern, newnode) for pattern in node.patterns]
)
return newnode
def visit_matchmapping(
self, node: ast.MatchMapping, parent: NodeNG
) -> nodes.MatchMapping:
newnode = nodes.MatchMapping(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
# Add AssignName node for 'node.name'
# https://bugs.python.org/issue43994
newnode.postinit(
keys=[self.visit(child, newnode) for child in node.keys],
patterns=[self.visit(pattern, newnode) for pattern in node.patterns],
rest=self.visit_assignname(node, newnode, node.rest),
)
return newnode
def visit_matchclass(
self, node: ast.MatchClass, parent: NodeNG
) -> nodes.MatchClass:
newnode = nodes.MatchClass(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
cls=self.visit(node.cls, newnode),
patterns=[self.visit(pattern, newnode) for pattern in node.patterns],
kwd_attrs=node.kwd_attrs,
kwd_patterns=[
self.visit(pattern, newnode) for pattern in node.kwd_patterns
],
)
return newnode
def visit_matchstar(
self, node: ast.MatchStar, parent: NodeNG
) -> nodes.MatchStar:
newnode = nodes.MatchStar(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
# Add AssignName node for 'node.name'
# https://bugs.python.org/issue43994
newnode.postinit(name=self.visit_assignname(node, newnode, node.name))
return newnode
def visit_matchas(self, node: ast.MatchAs, parent: NodeNG) -> nodes.MatchAs:
newnode = nodes.MatchAs(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
# Add AssignName node for 'node.name'
# https://bugs.python.org/issue43994
newnode.postinit(
pattern=self.visit(node.pattern, newnode),
name=self.visit_assignname(node, newnode, node.name),
)
return newnode
def visit_matchor(self, node: ast.MatchOr, parent: NodeNG) -> nodes.MatchOr:
newnode = nodes.MatchOr(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=parent,
)
newnode.postinit(
patterns=[self.visit(pattern, newnode) for pattern in node.patterns]
)
return newnode