summaryrefslogtreecommitdiff
path: root/astroid/builder.py
diff options
context:
space:
mode:
authorDaniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>2022-09-22 22:23:44 +0200
committerGitHub <noreply@github.com>2022-09-22 22:23:44 +0200
commite5f7488f9370b8f69c3605aa1be8650c7a3d76e4 (patch)
tree65f6fca5efae85800370f4c90926e8c16fd05e09 /astroid/builder.py
parentb867508d903719378e439ec48686565a497ec312 (diff)
downloadastroid-git-e5f7488f9370b8f69c3605aa1be8650c7a3d76e4.tar.gz
Finish typing of ``astroid.builder.py`` (#1807)
Diffstat (limited to 'astroid/builder.py')
-rw-r--r--astroid/builder.py90
1 files changed, 60 insertions, 30 deletions
diff --git a/astroid/builder.py b/astroid/builder.py
index 0b8755a3..a3b87faa 100644
--- a/astroid/builder.py
+++ b/astroid/builder.py
@@ -10,19 +10,25 @@ at the same time.
from __future__ import annotations
+import ast
import os
import textwrap
import types
-from collections.abc import Sequence
+from collections.abc import Iterator, Sequence
+from io import TextIOWrapper
from tokenize import detect_encoding
+from typing import TYPE_CHECKING
from astroid import bases, modutils, nodes, raw_building, rebuilder, util
-from astroid._ast import get_parser_module
+from astroid._ast import ParserModule, get_parser_module
from astroid.exceptions import AstroidBuildingError, AstroidSyntaxError, InferenceError
from astroid.manager import AstroidManager
-from astroid.nodes.node_classes import NodeNG
-objects = util.lazy_import("objects")
+if TYPE_CHECKING:
+ from astroid import objects
+else:
+ objects = util.lazy_import("objects")
+
# The name of the transient function that is used to
# wrap expressions to be extracted when calling
@@ -35,7 +41,7 @@ _STATEMENT_SELECTOR = "#@"
MISPLACED_TYPE_ANNOTATION_ERROR = "misplaced type annotation"
-def open_source_file(filename):
+def open_source_file(filename: str) -> tuple[TextIOWrapper, str, str]:
# pylint: disable=consider-using-with
with open(filename, "rb") as byte_stream:
encoding = detect_encoding(byte_stream.readline)[0]
@@ -44,7 +50,7 @@ def open_source_file(filename):
return stream, encoding, data
-def _can_assign_attr(node, attrname):
+def _can_assign_attr(node: nodes.ClassDef, attrname: str | None) -> bool:
try:
slots = node.slots()
except NotImplementedError:
@@ -65,7 +71,9 @@ class AstroidBuilder(raw_building.InspectBuilder):
by default being True.
"""
- def __init__(self, manager=None, apply_transforms=True):
+ def __init__(
+ self, manager: AstroidManager | None = None, apply_transforms: bool = True
+ ) -> None:
super().__init__(manager)
self._apply_transforms = apply_transforms
@@ -95,9 +103,10 @@ class AstroidBuilder(raw_building.InspectBuilder):
# We have to handle transformation by ourselves since the
# rebuilder isn't called for builtin nodes
node = self._manager.visit_transforms(node)
+ assert isinstance(node, nodes.Module)
return node
- def file_build(self, path, modname=None):
+ def file_build(self, path: str, modname: str | None = None) -> nodes.Module:
"""Build astroid from a source code file (i.e. from an ast)
*path* is expected to be a python source file
@@ -135,7 +144,9 @@ class AstroidBuilder(raw_building.InspectBuilder):
module, builder = self._data_build(data, modname, path)
return self._post_build(module, builder, encoding)
- def string_build(self, data, modname="", path=None):
+ def string_build(
+ self, data: str, modname: str = "", path: str | None = None
+ ) -> nodes.Module:
"""Build astroid from source code string."""
module, builder = self._data_build(data, modname, path)
module.file_bytes = data.encode("utf-8")
@@ -163,7 +174,7 @@ class AstroidBuilder(raw_building.InspectBuilder):
return module
def _data_build(
- self, data: str, modname, path
+ self, data: str, modname: str, path: str | None
) -> tuple[nodes.Module, rebuilder.TreeRebuilder]:
"""Build tree node from data and add some informations"""
try:
@@ -193,18 +204,19 @@ class AstroidBuilder(raw_building.InspectBuilder):
module = builder.visit_module(node, modname, node_file, package)
return module, builder
- def add_from_names_to_locals(self, node):
+ def add_from_names_to_locals(self, node: nodes.ImportFrom) -> None:
"""Store imported names to the locals
Resort the locals if coming from a delayed node
"""
- def _key_func(node):
- return node.fromlineno
+ def _key_func(node: nodes.NodeNG) -> int:
+ return node.fromlineno or 0
- def sort_locals(my_list):
+ def sort_locals(my_list: list[nodes.NodeNG]) -> None:
my_list.sort(key=_key_func)
+ assert node.parent # It should always default to the module
for (name, asname) in node.names:
if name == "*":
try:
@@ -218,7 +230,7 @@ class AstroidBuilder(raw_building.InspectBuilder):
node.parent.set_local(asname or name, node)
sort_locals(node.parent.scope().locals[asname or name])
- def delayed_assattr(self, node):
+ def delayed_assattr(self, node: nodes.AssignAttr) -> None:
"""Visit a AssAttr node
This adds name to locals and handle members definition.
@@ -229,8 +241,12 @@ class AstroidBuilder(raw_building.InspectBuilder):
if inferred is util.Uninferable:
continue
try:
- cls = inferred.__class__
- if cls is bases.Instance or cls is objects.ExceptionInstance:
+ # pylint: disable=unidiomatic-typecheck # We want a narrow check on the
+ # parent type, not all of its subclasses
+ if (
+ type(inferred) == bases.Instance
+ or type(inferred) == objects.ExceptionInstance
+ ):
inferred = inferred._proxied
iattrs = inferred.instance_attrs
if not _can_assign_attr(inferred, node.attrname):
@@ -239,6 +255,11 @@ class AstroidBuilder(raw_building.InspectBuilder):
# Const, Tuple or other containers that inherit from
# `Instance`
continue
+ elif (
+ isinstance(inferred, bases.Proxy)
+ or inferred is util.Uninferable
+ ):
+ continue
elif inferred.is_function:
iattrs = inferred.instance_attrs
else:
@@ -267,7 +288,12 @@ def build_namespace_package_module(name: str, path: Sequence[str]) -> nodes.Modu
return nodes.Module(name, path=list(path), package=True)
-def parse(code, module_name="", path=None, apply_transforms=True):
+def parse(
+ code: str,
+ module_name: str = "",
+ path: str | None = None,
+ apply_transforms: bool = True,
+) -> nodes.Module:
"""Parses a source string in order to obtain an astroid AST from it
:param str code: The code for the module.
@@ -284,7 +310,7 @@ def parse(code, module_name="", path=None, apply_transforms=True):
return builder.string_build(code, modname=module_name, path=path)
-def _extract_expressions(node):
+def _extract_expressions(node: nodes.NodeNG) -> Iterator[nodes.NodeNG]:
"""Find expressions in a call to _TRANSIENT_FUNCTION and extract them.
The function walks the AST recursively to search for expressions that
@@ -303,6 +329,7 @@ def _extract_expressions(node):
and node.func.name == _TRANSIENT_FUNCTION
):
real_expr = node.args[0]
+ assert node.parent
real_expr.parent = node.parent
# Search for node in all _astng_fields (the fields checked when
# get_children is called) of its parent. Some of those fields may
@@ -311,7 +338,7 @@ def _extract_expressions(node):
# like no call to _TRANSIENT_FUNCTION ever took place.
for name in node.parent._astroid_fields:
child = getattr(node.parent, name)
- if isinstance(child, (list, tuple)):
+ if isinstance(child, list):
for idx, compound_child in enumerate(child):
if compound_child is node:
child[idx] = real_expr
@@ -323,7 +350,7 @@ def _extract_expressions(node):
yield from _extract_expressions(child)
-def _find_statement_by_line(node, line):
+def _find_statement_by_line(node: nodes.NodeNG, line: int) -> nodes.NodeNG | None:
"""Extracts the statement on a specific line from an AST.
If the line number of node matches line, it will be returned;
@@ -358,7 +385,7 @@ def _find_statement_by_line(node, line):
return None
-def extract_node(code: str, module_name: str = "") -> NodeNG | list[NodeNG]:
+def extract_node(code: str, module_name: str = "") -> nodes.NodeNG | list[nodes.NodeNG]:
"""Parses some Python code as a module and extracts a designated AST node.
Statements:
@@ -412,13 +439,13 @@ def extract_node(code: str, module_name: str = "") -> NodeNG | list[NodeNG]:
:returns: The designated node from the parse tree, or a list of nodes.
"""
- def _extract(node):
+ def _extract(node: nodes.NodeNG | None) -> nodes.NodeNG | None:
if isinstance(node, nodes.Expr):
return node.value
return node
- requested_lines = []
+ requested_lines: list[int] = []
for idx, line in enumerate(code.splitlines()):
if line.strip().endswith(_STATEMENT_SELECTOR):
requested_lines.append(idx + 1)
@@ -427,7 +454,7 @@ def extract_node(code: str, module_name: str = "") -> NodeNG | list[NodeNG]:
if not tree.body:
raise ValueError("Empty tree, cannot extract from it")
- extracted = []
+ extracted: list[nodes.NodeNG | None] = []
if requested_lines:
extracted = [_find_statement_by_line(tree, line) for line in requested_lines]
@@ -438,12 +465,13 @@ def extract_node(code: str, module_name: str = "") -> NodeNG | list[NodeNG]:
extracted.append(tree.body[-1])
extracted = [_extract(node) for node in extracted]
- if len(extracted) == 1:
- return extracted[0]
- return extracted
+ extracted_without_none = [node for node in extracted if node is not None]
+ if len(extracted_without_none) == 1:
+ return extracted_without_none[0]
+ return extracted_without_none
-def _extract_single_node(code: str, module_name: str = "") -> NodeNG:
+def _extract_single_node(code: str, module_name: str = "") -> nodes.NodeNG:
"""Call extract_node while making sure that only one value is returned."""
ret = extract_node(code, module_name)
if isinstance(ret, list):
@@ -451,7 +479,9 @@ def _extract_single_node(code: str, module_name: str = "") -> NodeNG:
return ret
-def _parse_string(data, type_comments=True):
+def _parse_string(
+ data: str, type_comments: bool = True
+) -> tuple[ast.Module, ParserModule]:
parser_module = get_parser_module(type_comments=type_comments)
try:
parsed = parser_module.parse(data + "\n", type_comments=type_comments)