diff options
author | Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> | 2022-09-22 22:23:44 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-22 22:23:44 +0200 |
commit | e5f7488f9370b8f69c3605aa1be8650c7a3d76e4 (patch) | |
tree | 65f6fca5efae85800370f4c90926e8c16fd05e09 /astroid/builder.py | |
parent | b867508d903719378e439ec48686565a497ec312 (diff) | |
download | astroid-git-e5f7488f9370b8f69c3605aa1be8650c7a3d76e4.tar.gz |
Finish typing of ``astroid.builder.py`` (#1807)
Diffstat (limited to 'astroid/builder.py')
-rw-r--r-- | astroid/builder.py | 90 |
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) |